Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,24 @@ Regression guards include `tests/playback-store.test.mjs`, `tests/station-pollin

See `docs/instant-audio-sync.md`.

## Licensing and rights guardrails

- The repository `LICENSE` grants MIT rights only for **original project-owned
source code**, not broadcaster audio, stream rights, provider data/artwork,
third-party icons/packages or ePlus OnAir branding. Preserve the
[content-rights policy](./CONTENT_AND_THIRD_PARTY_RIGHTS.md).
- Do not treat a stream URL, public provider page, locally copied logo or an
`artwork_source_url` as proof of a redistribution/playback license. Review
provenance and permissions before importing assets or catalogs.
- Do not relicense `public/radio/**`, `public/catalog/providers/**`,
`public/img/**`, `public/brand/**` or derived third-party catalog data as
MIT just because they live in the same GitHub repository.
- Private `.env`, backend credentials and GitHub Environment Secrets are
never part of the public source grant. `NUXT_PUBLIC_*` settings are
browser-visible and must not contain secrets.
- License documentation changes must not alter audio behavior, station data,
production SEO/GA4, images or deployment configuration.

## i18n and text direction

Station names/descriptions may use Arabic or other RTL/mixed scripts.
Expand Down
88 changes: 88 additions & 0 deletions CONTENT_AND_THIRD_PARTY_RIGHTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Content, data and third-party rights

ePlus OnAir is a radio discovery and listening application. Its original
application code and the radio programs accessible through it are **different
works with different rights holders**.

This document clarifies the scope of the root [MIT License](./LICENSE). It
does **not** change or add conditions to MIT-covered code, nor does it claim
ownership of third-party works or facts that are not protected by copyright.

## MIT-licensed source code

The MIT License covers **original source code and accompanying documentation**
that David Nguyen / ePlus OnAir contributors have the right to license,
including the first-party Nuxt/Vue app, UI/player logic, tests, scripts, and
deployment workflows. This includes original code in `components/`,
`composables/`, `stores/`, `pages/`, `plugins/`, `server/`, `utils/`,
`types/`, `config/`, `build/`, `scripts/`, `tests/`, `assets/css/`,
and first-party source configuration, to the extent owned by or licensed to
these contributors.

The MIT License permits copying, modification, redistribution, and
commercial use of covered code when its conditions are met. A fork may use
that code but receives **no license to the ePlus OnAir branding or any
third-party radio content** from this repository.

Any code incorporated from another project retains its own applicable
license and notices. The root MIT License does not relicense material whose
rights are held by unrelated third parties.

## Not included in the MIT grant

| Repository content | Scope and rights |
| --- | --- |
| `public/radio/**` | Geographic station directory data, names, schedules, streaming endpoints and associated provider metadata. Sources and permissions vary; no blanket rights grant or guarantee of redistribution, scraping or playback rights. |
| `public/catalog/providers/**` | Third-party provider directories, including Apple Music/TuneIn names, links and metadata; provider rights and terms remain applicable. |
| `public/img/**` | Station logos/artwork and other image assets, including images synced from InstantAudio/Radio Browser and seven Apple Music radio images sourced from TuneIn. No blanket MIT license; assess each image's provenance. |
| `public/brand/**`, `public/favicon.ico` | ePlus OnAir marks, logos, branding and associated visual assets. No trademark, endorsement or brand-asset license is granted by the MIT source-code license. |
| `data/instant-audio-countries.json`, `data/world.ts` | Imported or derived station/world metadata, to the extent it contains third-party material. Treat as data, not automatically MIT-licensed software. |
| Third-party packages, libraries, fonts, icons and other embedded materials | Their own license terms and attribution requirements apply, whether installed via npm or included elsewhere in the tree. |
| Live broadcasts, music, programs, stream segments, playlists, provider access credentials and signed stream URLs | Rights are held by broadcasters, artists, platforms or other licensors; they are **not** owned or licensed by ePlus OnAir. A public URL is not an authorization to rebroadcast, record, cache, redistribute or use a stream commercially. |

Some station directory fields are public facts or links that may not themselves
be copyrightable. This notice does not assert exclusive rights over such
facts; it only avoids promising permissions ePlus OnAir does not control.
The repository's public availability and any permitted playback functionality
are not evidence of rights to republish third-party audio or artwork.

`public/img/holiday/**` is also outside the default code grant pending a
per-asset provenance and permission review. New images should carry documented
ownership and applicable license information before redistribution.

## Secrets, environment configuration and private services

- Private `.env` files, credentials, API keys, deployment tokens and GitHub
Environment Secrets are **not distributed** or licensed by this repository.
`.env.example` describes public-safe configuration placeholders only.
- `NUXT_PUBLIC_*` values and `runtimeConfig.public` are **browser-visible** in
Nuxt builds. They must not contain private credentials, even when set from
a protected GitHub Environment.
- Private backends, stream resolvers and infrastructure not published here
are not made open source by the MIT License on this repository.
- Never commit, log or redistribute expiring HLS `accessKey` values.

## Adding or correcting a station

Contributors must identify the official station/provider page, verify source
provenance, and document artwork and playback permission when available. Do
not upload a third party's image or copy a proprietary catalog just because
it is publicly reachable. Do not record, mirror or redistribute third-party
audio without appropriate permission.

A maintainer may remove or replace a station link, metadata or artwork if
the source is unauthorized or a rights holder requests correction.

## Rights-holder contact

If you own or represent a station, artwork, trademark, metadata or audio
program and have a rights concern, please contact **report@eplus.dev**
with the specific ePlus OnAir URL, the material involved, a description of
your rights and your requested correction or removal. Do **not** send API
keys, access tokens or other secrets.

A source-code issue report is not a substitute for permission from the
relevant station, rights holder or stream provider.

This document is an explanation of licensing scope, not legal clearance
of any individual station, image, provider API or stream.
22 changes: 19 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ and [coding guide](./AGENTS.md) before proposing changes.
7. If a security issue is discovered, report it **privately** as described
in [SECURITY.md](./SECURITY.md), not in a public issue.

Source code publication does not itself grant permission to redistribute
radio station logos, audio streams, third-party media or proprietary metadata.
A repository source license will need to be selected and published separately.
## Licensing and content provenance

Original code you contribute must be your own or material you have the right
to submit under the project's [MIT License](./LICENSE). Contributors retain
their own copyright; submission does not transfer ownership to ePlus OnAir.
Keep attribution and license notices required by upstream dependencies.

The [Content & third-party rights](./CONTENT_AND_THIRD_PARTY_RIGHTS.md)
policy excludes broadcasters' audio, third-party station logos/artwork,
provider metadata and ePlus branding from the default MIT code grant.
A publicly accessible stream URL is **not** permission to record, mirror,
rebroadcast or redistribute audio. Explain the provenance of any new
catalog entry, logo, image or provider integration in its PR and include
evidence of permission when available.

Never place private environment values, tokens or service credentials in
source, screenshots, test fixtures, issue comments or PR descriptions.
Browser-visible `NUXT_PUBLIC_*` values are not a safe place for secrets.
For copyright or takedown requests, contact **report@eplus.dev** directly.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 David Nguyen and ePlus OnAir contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,7 @@ For contributions, see [CONTRIBUTING.md](./CONTRIBUTING.md), [SECURITY.md](./SEC

## License

A source license has **not yet been selected**. Public visibility alone does not grant permission to reuse or redistribute the code, station artwork or third-party streams.
Original ePlus OnAir source code is available under the [MIT License](./LICENSE).
**Radio streams, station artwork, provider metadata and logos are not included**
in that grant; see [Content & third-party rights](./CONTENT_AND_THIRD_PARTY_RIGHTS.md).
Private environment secrets are never part of the published source.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"repository": "https://github.com/ePlus-DEV/radio.eplus.dev",
"description": "ePlus OnAir — Listen Beyond Borders. Live radio from around the world.",
"author": "David Nguyen",
"license": "MIT",
"private": true,
"type": "module",
"scripts": {
Expand Down
43 changes: 43 additions & 0 deletions tests/license-scope.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';

const read = file => readFile(new URL('../' + file, import.meta.url), 'utf8');

test('original application software declares MIT without changing the standard permission grant', async () => {
const [license, manifest] = await Promise.all([read('LICENSE'), read('package.json')]);
assert.match(license, /^MIT License\n/);
assert.match(license, /Permission is hereby granted, free of charge/);
assert.match(license, /use, copy, modify, merge, publish, distribute, sublicense, and\/or sell/);
assert.match(license, /THE SOFTWARE IS PROVIDED "AS IS"/);
assert.equal(JSON.parse(manifest).license, 'MIT');
assert.equal(JSON.parse(manifest).private, true, 'publishing npm package is unrelated to open-source license');
});

test('license scope explicitly excludes streams, provider catalogs, station images, and marks', async () => {
const [readme, rights, contributing, agents] = await Promise.all([
read('README.md'),
read('CONTENT_AND_THIRD_PARTY_RIGHTS.md'),
read('CONTRIBUTING.md'),
read('AGENTS.md'),
]);
assert.match(readme, /\[MIT License\]\(\.\/LICENSE\)/);
assert.match(readme, /\[Content & third-party rights\]\(\.\/CONTENT_AND_THIRD_PARTY_RIGHTS\.md\)/);
for (const pattern of [
'public/radio/**', 'public/catalog/providers/**', 'public/img/**',
'public/brand/**', 'data/instant-audio-countries.json', 'data/world.ts',
]) assert.ok(rights.includes(pattern), `rights policy should mention ${pattern}`);
assert.match(rights, /not.*licensed by ePlus OnAir/i);
assert.match(rights, /report@eplus\.dev/);
assert.match(contributing, /MIT License/);
assert.match(agents, /Licensing and rights guardrails/);
});

test('environment files remain ignored and public Nuxt settings are not described as secret', async () => {
const [ignore, rights] = await Promise.all([read('.gitignore'), read('CONTENT_AND_THIRD_PARTY_RIGHTS.md')]);
assert.match(ignore, /^\.env\n/m);
assert.match(ignore, /^\.env\.\*\n/m);
assert.match(ignore, /^!\.env\.example\n/m);
assert.match(rights, /browser-visible/);
assert.match(rights, /not distributed/);
});
12 changes: 12 additions & 0 deletions tests/online-duration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,15 @@ test('all seven locales explain online time and independent listening metrics',
}
}
});

test('foreground activity checkpoints a delayed timer before later time is lost', () => {
let now = 0;
const commits = [];
const tracker = createOnlineDurationTracker({ now: () => now, onDuration: ms => commits.push(ms) });
tracker.start();
now = MAX_ONLINE_CHECKPOINT_MS;
tracker.activity(); // Interval callback has not fired yet.
now += 5_000;
tracker.stop();
assert.deepEqual(commits, [MAX_ONLINE_CHECKPOINT_MS, 5_000]);
});
2 changes: 1 addition & 1 deletion utils/online-duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function createOnlineDurationTracker({
const current = now();
// An input event after the idle deadline must not retroactively make
// the entire unattended gap count as foreground activity.
if (active && current >= lastActivityAt + idleMs) flush();
if (active && (current >= lastActivityAt + idleMs || current - lastAt >= MAX_ONLINE_CHECKPOINT_MS)) flush();
if (!active) { active = true; lastAt = current; }
lastActivityAt = current;
},
Expand Down
Loading