openvibers/openvibe.shared Commits
4.107.0: the public and service legal profiles (#274)
Changes
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking change to any exported module, browser global or served file name is a new major.A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).++## 4.107.0 — 2026-11-03++++- **Two legal profiles for sites the old ones did not fit.** Ten sites served no `/terms`, `/privacy` or `/dmca`++ because no profile told the truth about them: `info` says the site is not open yet, and `tools` promises uploads++ deleted within the hour. `public` is an open, read-only site with no account (search, reports, domain prices).++ `service` is a personal tool or API that keeps what you make with your account. A service site says in its own++ words what it does (`what`) and keeps (`makes`), whether it fetches addresses people give it (`fetches`) and whether++ it sends what they give it to AI models (`ai`), and names any extra processors (`processors`). `legal.PROFILES` lists++ every profile. `test/legal-profiles.test.js` covers them.++## 4.106.3 — 2026-11-02- **A site on its own open.<tld> domain is named for it.** open.shoes read "OpenVibe.Shoes" in the bar and its name@@@ -2,7 +2,7 @@> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.--**Status:** alpha, v4.106.3 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.107.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployedconsumer installs a tagged release and none keeps a vendored copy([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` namesthe tag it pins.@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.106.3",++ "version": "4.107.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)","deployed": false,"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",@@@ -7,7 +7,16 @@// streaming live video, chat, payments tools files processed, then deleted// ugc posts, pastes, comments games accounts and game state// hosting stored user media account the identity provider--// info read-only / placeholder site++// info a placeholder site, not open yet++// public an open, read-only site: public information, no account needed (search, reports, domain prices)++// service a personal tool or API: what you make is kept with your account (AI runs, events, watches, designs)++//++// public and service sites say what they do and keep in their own words (handler options, all optional):++// what one sentence, what the site does (its manifest's site.what)++// makes what it keeps for you, a list: ['the watches you set and their history', …]++// fetches true when it fetches addresses people give it (pages, feeds, webhooks)++// ai true when it sends what people give it to AI models++// processors extra third parties, a list: ['the streaming platforms you send to']//// const legal = require('openvibe-shared/legal');// app.get(legal.PATHS, legal.handler({ host: 'openvibe.tools', name: 'OpenVibe.Tools', profile: 'tools' }));@@@ -25,7 +34,9 @@ const TITLES = { terms: 'Terms of Service', privacy: 'Privacy Policy', dmca: 'DMconst esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));const has = (site, ...profiles) => profiles.includes(site.profile);--const userContent = (site) => has(site, 'streaming', 'ugc', 'hosting', 'games');++const userContent = (site) => has(site, 'streaming', 'ugc', 'hosting', 'games', 'service');++const list = (v) => (Array.isArray(v) ? v : v ? [v] : []).map((x) => String(x)).filter(Boolean);++const joinAnd = (xs) => xs.length < 2 ? xs.join('') : `${xs.slice(0, -1).join(', ')} and ${xs[xs.length - 1]}`;// Clause helpers. Every statement of fact below is backed by code in one of the repositories:// retention windows (OpenVibe.Tools apps/*/server/config.js), cookies (server/auth/*), the anonymous@@@ -40,8 +51,10 @@ function terms(site) {S.push(['The short version', P(`${n} is part of OpenVibe, an open source, community-run network. Use it for lawful things, be decent to other people, and do not try to break it. Most of it works without an account. We can remove content or accounts that break these terms. The service is provided as it is, without warranties.`,`These terms cover ${host}, its subdomains, and any other domain that serves the same service. Other OpenVibe sites have their own terms, written for what those sites do.`)]);S.push(['Who can use it', P(`You must be at least 13 years old, or the minimum age of digital consent where you live. If you are under 18, a parent or guardian must agree to these terms for you. You may not use ${n} if you were banned from the network, or if the law where you live forbids it.`)]);++ if (site.what && has(site, 'public', 'service')) S.push([`What ${n} does`, P(esc(site.what))]);if (has(site, 'info')) S.push(['This site is not open yet', P(`${n} is a reserved address that currently shows a preview page. There is nothing to sign up for or post here yet. When it opens, these terms will be replaced with ones written for what it does, and the date above will change.`)]);-- if (!has(site, 'info')) S.push(['Your account', P(`An OpenVibe account is managed at <a href="https://openvibe.network/">openvibe.network</a> and works on every OpenVibe site. One sign-in can quietly sign you in on the other sites you visit; you can sign out everywhere from your account page.`,++ if (has(site, 'public')) S.push(['No account needed', P(`${n} shows public information and needs no account. Signing in with OpenVibe, where it is offered, only adds conveniences such as your theme and your cross-site history.`)]);++ if (!has(site, 'info', 'public')) S.push(['Your account', P(`An OpenVibe account is managed at <a href="https://openvibe.network/">openvibe.network</a> and works on every OpenVibe site. One sign-in can quietly sign you in on the other sites you visit; you can sign out everywhere from your account page.`,`You are responsible for what happens under your account. Keep your password to yourself, use a working email address so you can recover access, and tell us if you think someone else has got in. One person per account; do not sell, lend or share it. We may reclaim usernames that impersonate someone or sit unused.`)]);if (has(site, 'account')) S.push(['What the account does', P(`The account holds your sign-in, profile, theme, display and notification preferences, linked services, cross-site history and any balances shown in your wallet. Linking a third-party service (for example Discord) lets us read the basic profile that service shares and nothing you did not approve. You can unlink a service at any time.`,`Themes you publish to the theme gallery are shared with other users under the licence shown when you publish; do not publish a theme that copies someone else's work.`,@@@ -57,6 +70,13 @@ function terms(site) {`<b>Converters and PDF tools</b>: do not upload files you have no right to process, and do not use the unlock or protect tools to defeat protection on documents that are not yours.`,`<b>Automation</b>: the tool catalog is public JSON and crawlers are welcome. Automated use of the tools themselves is rate limited; heavy or commercial automation needs our agreement first.`])]);}++ if (has(site, 'service')) {++ const makes = list(site.makes).map(esc);++ S.push(['What you make here', P(`${n} keeps what you make with it${makes.length ? `: ${joinAnd(makes)}` : ''}, linked to your OpenVibe account, so it can do its work for you. It stays until you delete it or your account. What you make is private to you unless you choose to share or publish it.`,++ `Keys, tokens and secrets you create or store here are yours to keep secret. Treat them like passwords, and replace one at once if you think it leaked. Automated use is welcome within the limits the site shows; heavy or commercial use needs our agreement first.`)]);++ if (site.fetches) S.push(['Requests it makes for you', P(`${n} fetches the addresses you give it, on your behalf, from our servers. Only point it at things you are allowed to read or send to, and respect the rules of the sites involved. It checks politely and backs off when a site asks it to; do not use it to load-test or probe systems that are not yours.`)]);++ if (site.ai) S.push(['AI models', P(`Some of what ${n} does sends what you give it to an AI model, which writes the answer. The answer says which model wrote it where the site shows that. Models make mistakes: check anything that matters before you rely on it.`)]);++ }if (has(site, 'ugc')) S.push(['Pastes, posts and comments', P(`You choose whether an item is public or unlisted. Public items appear in listings and feeds, can be indexed by search engines, and may be copied by other people while they are public. Unlisted items are reachable by anyone who has the link; they are not private. Mark adult content as such.`,`Never post passwords, API keys, private keys, payment details or other people's personal information. We remove leaked credentials, malware, phishing kits and doxxing when we find them, and may do so without notice to limit harm.`)]);if (has(site, 'hosting')) S.push(['Stored media', P(`${n} stores recorded streams, clips, thumbnails, images and files for the OpenVibe sites that use them. Whether an item is public follows the setting on the site it came from. Public media can be embedded and linked from elsewhere. Media may be kept on third-party object storage and served through a content delivery network.`,@@@ -78,7 +98,7 @@ function terms(site) {if (has(site, 'streaming')) S.push(['Money', P(`Donations, tips and purchases of virtual currency are voluntary and final except where the law requires a refund. Payment processors handle the payment under their own terms. Virtual currencies have no cash value outside the payout rules shown on the site. Creators are responsible for their own taxes, and we may withhold payouts connected to fraud or chargebacks.`)]);S.push(['Automated and AI-written text', P(`Some descriptive text on OpenVibe, such as footer blurbs, summaries and suggestions, is written by automated systems and checked by rules rather than by a person. It can be wrong. Do not rely on it for decisions that matter.`)]);S.push(['Reporting and moderation', P(`Report content with the report controls where they exist, or write to <a href="mailto:${CONTACT}">${CONTACT}</a>. Copyright complaints follow the <a href="/dmca">DMCA process</a>. We may remove content, limit features, or suspend accounts that break these terms or put other people or the service at risk, and we may act without notice when the harm is urgent. If you think we got it wrong, reply to the notice or write to the same address and a person will look again.`)]);-- S.push(['Leaving', P(`You can stop using ${n} at any time${has(site, 'info') ? '' : ' and delete your account from your account settings. Deleting the account removes your access on every OpenVibe site'}. Sections that by their nature should survive (licences for content already shared, liability, governing law) continue after you leave.`)]);++ S.push(['Leaving', P(`You can stop using ${n} at any time${has(site, 'info', 'public') ? '' : ' and delete your account from your account settings. Deleting the account removes your access on every OpenVibe site'}. Sections that by their nature should survive (licences for content already shared, liability, governing law) continue after you leave.`)]);S.push(['Open source, links and feedback', P(`OpenVibe's code is public. Each repository's licence governs your use of the code; these terms govern your use of the hosted service at ${host}. Links to other sites are provided for convenience and we are not responsible for them. If you send us ideas or fixes, we may use them without owing you anything, and contributions to the code are made under the repository's licence.`)]);S.push(['No warranty, limited liability', P(`The service is provided "as is" and "as available", without warranties of any kind, including fitness for a particular purpose, accuracy of results, or uninterrupted availability. To the extent the law allows, OpenVibe and the people who run it are not liable for indirect, incidental or consequential losses, lost data or lost profits arising from your use of ${n}, and our total liability is limited to the greater of what you paid us in the past twelve months or fifty US dollars. Nothing here limits liability that cannot be limited by law, and consumer rights you have under the law of your country remain.`,`If your use of ${n} breaks these terms or the law and someone brings a claim against us because of it, you agree to cover our reasonable costs.`)]);@@@ -95,6 +115,10 @@ function privacy(site) {'<b>Request logs.</b> Like every web server, ours record your IP address, browser type, the address requested and the time. We use them for security, rate limiting, debugging and aggregate traffic statistics.','<b>Usage statistics.</b> First-party page views with a random session identifier, kept on our own servers, so we can see which parts of the network are used.','<b>An anonymous page count.</b> The shared navigation bar reports one count per page load containing only the site\'s hostname and the day. No user, IP address or page is stored with it. It decides the order of sites in our menus. It is skipped when your browser sends Global Privacy Control or Do Not Track.',++ has(site, 'public') ? '<b>What you look up.</b> Searches and lookups are answered from our own servers. They are kept with the request logs above for security and to see, in aggregate, what people look for; they are not tied to an account and are not sold or shared.' : '',++ has(site, 'service') ? `<b>What you make here.</b> ${list(site.makes).length ? `${joinAnd(list(site.makes).map(esc)).replace(/^./, (c) => c.toUpperCase())}, linked` : 'What you create and configure here, linked'} to your account and kept until you delete it or your account.` : '',++ has(site, 'service') && site.fetches ? '<b>Addresses you give it.</b> The pages, feeds and endpoints you point it at, and what it found there, so it can show you results and history. The sites it reaches see our servers\' address, not yours.' : '',++ has(site, 'service') && site.ai ? '<b>What you send to AI models.</b> Text and files you give an AI feature are sent to the provider of the model that answers (or to your own computer, when you choose it), which processes them to write the answer under its own terms.' : '',has(site, 'info') ? '' : `<b>Account information, if you sign in.</b> Your OpenVibe account is held by <a href="https://openvibe.network/privacy">openvibe.network</a>. ${has(site, 'account') ? 'See the next section.' : `${n} receives your user id, username, avatar and role so it can show you as signed in. It does not receive your password or your email address.`}`,has(site, 'account') ? '<b>Your account.</b> Username, email address, a salted hash of your password, profile and avatar, theme, display and notification preferences, push-notification subscriptions for devices where you switched them on, linked services and the basic profile they share, sign-in sessions, wallet balances and transactions, and your cross-site history while it is switched on.' : '',has(site, 'tools') ? '<b>What you give a tool.</b> Files, text, links, domain names and IP addresses you submit are processed to produce your result. Uploads and results are deleted automatically, normally within one hour for visitors and 24 hours for signed-in users. Files can contain hidden metadata such as location or author; the tools only read what they need, but consider removing it first.' : '',@@@ -108,11 +132,12 @@ function privacy(site) {S.push(['Cookies and storage in your browser', P(`We use a small number of first-party cookies: a sign-in token (<code>ov_token</code>), a refresh token limited to the sign-in paths, a short-lived state cookie during sign-in, and a hint (<code>ov_sso_hint</code>) that remembers whether you were signed in. openvibe.network also keeps a sign-in cookie that lets other OpenVibe sites check, in a hidden frame or through your browser's built-in federated sign-in, whether to sign you in quietly. It is never used for anything except that check.`,`Your theme, display settings (text size, reduced motion), recent pages, a cached copy of the navigation and similar conveniences are kept in your browser's local storage. There are no third-party advertising or social cookies. Our sites sit behind Cloudflare, which may set its own security cookies and measure performance.`)]);S.push(['Across OpenVibe sites', P(`OpenVibe sites share one account. When you are signed in, the site you visit learns who you are from openvibe.network. While cross-site history is switched on, the pages and tools you open while signed in are listed for you, and only you, at <a href="https://openvibe.network/history">openvibe.network/history</a>, where you can clear the list or pause it.`)]);-- S.push(['Who we share with', P(`Only the services needed to run the network: hosting (OVH, servers in the United States), network security and delivery (Cloudflare), object storage for media (Backblaze B2 or Cloudflare R2)${has(site, 'streaming') ? ', payment processors, AI providers used for transcription and summaries' : ''}, and email delivery for account messages. They process data on our instructions. We disclose information when the law requires it, and we tell the person affected when we are allowed to. If OpenVibe is ever reorganised or handed to new maintainers, the data moves under the same promises.`)]);++ const extra = list(site.processors).map(esc).concat(has(site, 'service') && site.ai ? ['the AI providers whose models answer your requests'] : []);++ S.push(['Who we share with', P(`Only the services needed to run the network: hosting (OVH, servers in the United States), network security and delivery (Cloudflare), object storage for media (Backblaze B2 or Cloudflare R2)${has(site, 'streaming') ? ', payment processors, AI providers used for transcription and summaries' : ''}${extra.map((x) => `, ${x}`).join('')}, and email delivery for account messages. They process data on our instructions. We disclose information when the law requires it, and we tell the person affected when we are allowed to. If OpenVibe is ever reorganised or handed to new maintainers, the data moves under the same promises.`)]);S.push(['How long we keep things', UL(['Request logs and raw usage events: a limited rolling period, then totals only.', 'The anonymous page count: totals per site and day, nothing personal to delete.',has(site, 'tools') ? 'Tool uploads and results: one hour for visitors, 24 hours for signed-in users, temporary upload fragments about ten minutes.' : '',userContent(site) ? 'Content you post: until you delete it or your account, plus short-lived caches and backups.' : '',-- has(site, 'info') ? '' : 'Account data: until you delete your account. Backups expire on their own shortly afterwards. Records we must keep by law, such as payment records, are kept for the required period.'])]);++ has(site, 'info', 'public') ? '' : 'Account data: until you delete your account. Backups expire on their own shortly afterwards. Records we must keep by law, such as payment records, are kept for the required period.'])]);S.push(['Where your data is', P(`Our servers are in the United States. If you use OpenVibe from elsewhere, your information is transferred to and processed in the United States, which may have different data protection rules from your country.`)]);S.push(['Security', P(`Traffic is encrypted in transit. Passwords are stored as salted hashes, sign-in tokens are signed and short-lived, and internal service routes are not reachable from the internet. No system is perfectly secure; if a breach affects your personal information we will tell you and the relevant authorities as the law requires.`)]);S.push(['Your choices and rights', P(`You can use most of ${n} without an account, block cookies, and switch on Global Privacy Control to skip the anonymous page count. With an account you can view, correct, export and delete your data from your account settings, or ask us to do it.`,@@@ -127,13 +152,15 @@ function dmca(site) {const S = [];const position = has(site, 'tools') ? ` The tools here process material at a user's request and do not keep a library of it: uploads and results are deleted automatically within hours, and downloaders neither host nor index what they fetch. If the material you are concerned about lives on another platform, a notice to that platform removes it at the source.`: has(site, 'info') ? ` ${n} currently publishes only its own material and does not host uploads from the public.`++ : has(site, 'public') ? ` ${n} shows public information gathered from OpenVibe and other public sources and does not host uploads from the public. Material that lives on another OpenVibe site is removed through that site's notice process, which removes it here too.`++ : has(site, 'service') ? ` ${n} keeps what its users make for themselves, and most of it is private to them. A notice about something they chose to publish works as below.`: has(site, 'hosting') ? ` ${n} stores media for other OpenVibe sites. Removing an item here removes it from the sites that embed it.`: has(site, 'account') ? ` ${n} hosts account profiles, avatars and published themes; content on the other OpenVibe sites is handled by the notice process on those sites.` : '';S.push(['Our position', P(`${n} respects copyright and responds to clear notices of alleged infringement under the Digital Millennium Copyright Act (17 U.S.C. § 512).${position}`)]);S.push(['Before you send a notice', P(`Consider whether the use is permitted, for example as fair use, commentary, criticism, or under a licence. Notices must come from the copyright owner or someone authorised to act for them. Misrepresentations in a notice carry legal liability.`)]);S.push(['Send a takedown notice', P(`Email <a href="mailto:${CONTACT}">${CONTACT}</a> with all of the following:`) + `<ol><li>Your name, postal address, phone number and email address, and whom you represent.</li><li>The copyrighted work you say was infringed, or a representative list if there are several.</li><li>The exact URL on ${host} of each item you want removed. General descriptions, search results and channel links are not enough to find it.</li><li>A statement that you have a good-faith belief the use is not authorised by the copyright owner, its agent or the law.</li><li>A statement, under penalty of perjury, that the information is accurate and that you are the owner or authorised to act for the owner.</li><li>Your physical or electronic signature.</li></ol>`]);S.push(['What happens next', P(`We remove or disable access to the material, tell the person who posted it, and give them a copy of your notice, including your contact details. We act on complete notices as quickly as we can, normally within a few business days. Incomplete notices get a reply saying what is missing.${userContent(site) ? ' Copies held in caches and in third-party archives are outside our control and expire or must be requested separately.' : ''}`)]);-- if (!has(site, 'info', 'tools')) S.push(['Counter-notice', P(`If your material was removed by mistake or misidentification, send a counter-notice to the same address with: your name, address, phone number and email; the material and where it appeared before removal; a statement under penalty of perjury that you believe in good faith it was removed by mistake or misidentification; your consent to the jurisdiction of the federal district court for your address (or for Washington State if you are outside the United States) and to accept service from the complainant; and your signature.`,++ if (!has(site, 'info', 'tools', 'public')) S.push(['Counter-notice', P(`If your material was removed by mistake or misidentification, send a counter-notice to the same address with: your name, address, phone number and email; the material and where it appeared before removal; a statement under penalty of perjury that you believe in good faith it was removed by mistake or misidentification; your consent to the jurisdiction of the federal district court for your address (or for Washington State if you are outside the United States) and to accept service from the complainant; and your signature.`,`We forward the counter-notice to the complainant and may restore the material after 10 to 14 business days unless they tell us they have filed a court action.`)]);S.push(['Repeat infringers and false claims', P(`Accounts that receive repeated valid notices lose the ability to post and, in appropriate circumstances, are terminated across the network. Knowingly false notices and counter-notices carry liability under § 512(f); we refuse notices that are clearly abusive or automated without review.`)]);S.push(['Other complaints', P(`Trademark, privacy, impersonation, harassment and other non-copyright complaints go to the same address; say which kind of complaint it is and give the exact URL. Content that sexually exploits children should be reported to us immediately and to the <a href="https://report.cybertip.org/" rel="noopener">NCMEC CyberTipline</a> or your national hotline; we remove it and report it to the authorities.`)]);@@@ -202,4 +229,6 @@ function handler(site) {};}--module.exports = { PATHS, TITLES, UPDATED, CONTACT, build, article, page, handler, CSS };++const PROFILES = ['streaming', 'tools', 'ugc', 'games', 'hosting', 'account', 'info', 'public', 'service'];++++module.exports = { PATHS, TITLES, UPDATED, CONTACT, PROFILES, build, article, page, handler, CSS };@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.106.3",++ "version": "4.107.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.106.3",++ "version": "4.107.0","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.106.3",++ "version": "4.107.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {nnew file mode 100644@@@ -0,0 +1,59 @@++'use strict';++// legal.js — the public and service profiles (11-03): ten sites served no /terms, /privacy or /dmca because no profile++// told the truth about them ("info" says the site is not open yet; "tools" promises uploads deleted within the hour).++// node test/legal-profiles.test.js++const assert = require('assert');++const legal = require('../legal');++++const text = (kind, site) => legal.build(kind, site).sections.map(([h, html]) => `${h}\n${html}`).join('\n');++++// public: open, read-only, no account; never "not open yet", never an account to delete, no counter-notice.++{++ const site = { host: 'openvibe.report', name: 'OpenVibe.Report', profile: 'public', what: 'The network\'s numbers open to everyone.' };++ const t = text('terms', site);++ assert.match(t, /What OpenVibe\.Report does\n<p>The network's numbers open to everyone\.<\/p>/);++ assert.match(t, /No account needed/);++ assert.doesNotMatch(t, /not open yet|delete your account|Your account\n/);++ const p = text('privacy', site);++ assert.match(p, /What you look up/);++ assert.doesNotMatch(p, /deleted automatically|Account data: until/);++ const d = text('dmca', site);++ assert.match(d, /does not host uploads from the public/);++ assert.doesNotMatch(d, /Counter-notice/);++}++++// service: what it keeps, fetches and AI only when the site says so; its own processors named.++{++ const site = { host: 'openvibe.watch', name: 'OpenVibe.Watch', profile: 'service', what: 'Watch a page and get told when it changes.', makes: ['the watches you set', 'what each check found'], fetches: true };++ const t = text('terms', site);++ assert.match(t, /What you make here\n<p>OpenVibe\.Watch keeps what you make with it: the watches you set and what each check found/);++ assert.match(t, /Requests it makes for you/);++ assert.doesNotMatch(t, /AI models\n/, 'no AI unless the site says so');++ assert.match(t, /Your content and the licence you give us/);++ assert.doesNotMatch(t, /deleted automatically|not open yet/);++ const p = text('privacy', site);++ assert.match(p, /The watches you set and what each check found, linked to your account/);++ assert.match(p, /Addresses you give it/);++ assert.doesNotMatch(p, /What you send to AI models/);++ assert.match(text('dmca', site), /keeps what its users make for themselves/);++}++{++ const site = { host: 'ai.openvibe.services', name: 'OpenVibe AI', profile: 'service', ai: true, makes: ['your runs and their costs'], processors: ['your <own> list'] };++ assert.match(text('terms', site), /AI models\n<p>Some of what OpenVibe AI does sends what you give it to an AI model/);++ const p = text('privacy', site);++ assert.match(p, /What you send to AI models/);++ assert.match(p, /the AI providers whose models answer your requests/);++ assert.match(p, /your <own> list/, 'escaped');++ assert.doesNotMatch(p, /Addresses you give it/);++}++++// The older profiles read as before.++{++ assert.match(text('terms', { host: 'x.openvibe.tools', name: 'X', profile: 'info' }), /This site is not open yet/);++ assert.match(text('privacy', { host: 'openvibe.tools', name: 'OpenVibe.Tools', profile: 'tools' }), /deleted automatically/);++ assert.doesNotMatch(text('terms', { host: 'openvibe.live', name: 'OpenVibe.Live', profile: 'streaming' }), /What you make here|No account needed/);++}++++assert.deepStrictEqual(legal.PROFILES, ['streaming', 'tools', 'ugc', 'games', 'hosting', 'account', 'info', 'public', 'service']);++for (const profile of legal.PROFILES) for (const kind of Object.keys(legal.TITLES)) assert.ok(legal.page(kind, { host: 'a.openvibe.network', name: 'A', profile, id: 'network' }).includes('</html>'));++console.log('legal profiles: all checks passed');
@@ -4,6 +4,16 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,16 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
+## 4.107.0 — 2026-11-03
+
+- **Two legal profiles for sites the old ones did not fit.** Ten sites served no `/terms`, `/privacy` or `/dmca`
+ because no profile told the truth about them: `info` says the site is not open yet, and `tools` promises uploads
+ deleted within the hour. `public` is an open, read-only site with no account (search, reports, domain prices).
+ `service` is a personal tool or API that keeps what you make with your account. A service site says in its own
+ words what it does (`what`) and keeps (`makes`), whether it fetches addresses people give it (`fetches`) and whether
+ it sends what they give it to AI models (`ai`), and names any extra processors (`processors`). `legal.PROFILES` lists
+ every profile. `test/legal-profiles.test.js` covers them.
+
## 4.106.3 — 2026-11-02
## 4.106.3 — 2026-11-02
- **A site on its own open.<tld> domain is named for it.** open.shoes read "OpenVibe.Shoes" in the bar and its name
- **A site on its own open.<tld> domain is named for it.** open.shoes read "OpenVibe.Shoes" in the bar and its name
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
-**Status:** alpha, v4.106.3 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.107.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.106.3",
+ "version": "4.107.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -7,7 +7,16 @@
@@ -7,7 +7,16 @@
// streaming live video, chat, payments tools files processed, then deleted
// streaming live video, chat, payments tools files processed, then deleted
// ugc posts, pastes, comments games accounts and game state
// ugc posts, pastes, comments games accounts and game state
// hosting stored user media account the identity provider
// hosting stored user media account the identity provider
-// info read-only / placeholder site
+// info a placeholder site, not open yet
+// public an open, read-only site: public information, no account needed (search, reports, domain prices)
+// service a personal tool or API: what you make is kept with your account (AI runs, events, watches, designs)
+//
+// public and service sites say what they do and keep in their own words (handler options, all optional):
+// what one sentence, what the site does (its manifest's site.what)
+// makes what it keeps for you, a list: ['the watches you set and their history', …]
+// fetches true when it fetches addresses people give it (pages, feeds, webhooks)
+// ai true when it sends what people give it to AI models
+// processors extra third parties, a list: ['the streaming platforms you send to']
//
//
// const legal = require('openvibe-shared/legal');
// const legal = require('openvibe-shared/legal');
// app.get(legal.PATHS, legal.handler({ host: 'openvibe.tools', name: 'OpenVibe.Tools', profile: 'tools' }));
// app.get(legal.PATHS, legal.handler({ host: 'openvibe.tools', name: 'OpenVibe.Tools', profile: 'tools' }));
@@ -25,7 +34,9 @@ const TITLES = { terms: 'Terms of Service', privacy: 'Privacy Policy', dmca: 'DM
@@ -25,7 +34,9 @@ const TITLES = { terms: 'Terms of Service', privacy: 'Privacy Policy', dmca: 'DM
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
const has = (site, ...profiles) => profiles.includes(site.profile);
const has = (site, ...profiles) => profiles.includes(site.profile);
-const userContent = (site) => has(site, 'streaming', 'ugc', 'hosting', 'games');
+const userContent = (site) => has(site, 'streaming', 'ugc', 'hosting', 'games', 'service');
+const list = (v) => (Array.isArray(v) ? v : v ? [v] : []).map((x) => String(x)).filter(Boolean);
+const joinAnd = (xs) => xs.length < 2 ? xs.join('') : `${xs.slice(0, -1).join(', ')} and ${xs[xs.length - 1]}`;
// Clause helpers. Every statement of fact below is backed by code in one of the repositories:
// Clause helpers. Every statement of fact below is backed by code in one of the repositories:
// retention windows (OpenVibe.Tools apps/*/server/config.js), cookies (server/auth/*), the anonymous
// retention windows (OpenVibe.Tools apps/*/server/config.js), cookies (server/auth/*), the anonymous
@@ -40,8 +51,10 @@ function terms(site) {
@@ -40,8 +51,10 @@ function terms(site) {
S.push(['The short version', P(`${n} is part of OpenVibe, an open source, community-run network. Use it for lawful things, be decent to other people, and do not try to break it. Most of it works without an account. We can remove content or accounts that break these terms. The service is provided as it is, without warranties.`,
S.push(['The short version', P(`${n} is part of OpenVibe, an open source, community-run network. Use it for lawful things, be decent to other people, and do not try to break it. Most of it works without an account. We can remove content or accounts that break these terms. The service is provided as it is, without warranties.`,
`These terms cover ${host}, its subdomains, and any other domain that serves the same service. Other OpenVibe sites have their own terms, written for what those sites do.`)]);
`These terms cover ${host}, its subdomains, and any other domain that serves the same service. Other OpenVibe sites have their own terms, written for what those sites do.`)]);
S.push(['Who can use it', P(`You must be at least 13 years old, or the minimum age of digital consent where you live. If you are under 18, a parent or guardian must agree to these terms for you. You may not use ${n} if you were banned from the network, or if the law where you live forbids it.`)]);
S.push(['Who can use it', P(`You must be at least 13 years old, or the minimum age of digital consent where you live. If you are under 18, a parent or guardian must agree to these terms for you. You may not use ${n} if you were banned from the network, or if the law where you live forbids it.`)]);
+ if (site.what && has(site, 'public', 'service')) S.push([`What ${n} does`, P(esc(site.what))]);
if (has(site, 'info')) S.push(['This site is not open yet', P(`${n} is a reserved address that currently shows a preview page. There is nothing to sign up for or post here yet. When it opens, these terms will be replaced with ones written for what it does, and the date above will change.`)]);
if (has(site, 'info')) S.push(['This site is not open yet', P(`${n} is a reserved address that currently shows a preview page. There is nothing to sign up for or post here yet. When it opens, these terms will be replaced with ones written for what it does, and the date above will change.`)]);
- if (!has(site, 'info')) S.push(['Your account', P(`An OpenVibe account is managed at <a href="https://openvibe.network/">openvibe.network</a> and works on every OpenVibe site. One sign-in can quietly sign you in on the other sites you visit; you can sign out everywhere from your account page.`,
+ if (has(site, 'public')) S.push(['No account needed', P(`${n} shows public information and needs no account. Signing in with OpenVibe, where it is offered, only adds conveniences such as your theme and your cross-site history.`)]);
+ if (!has(site, 'info', 'public')) S.push(['Your account', P(`An OpenVibe account is managed at <a href="https://openvibe.network/">openvibe.network</a> and works on every OpenVibe site. One sign-in can quietly sign you in on the other sites you visit; you can sign out everywhere from your account page.`,
`You are responsible for what happens under your account. Keep your password to yourself, use a working email address so you can recover access, and tell us if you think someone else has got in. One person per account; do not sell, lend or share it. We may reclaim usernames that impersonate someone or sit unused.`)]);
`You are responsible for what happens under your account. Keep your password to yourself, use a working email address so you can recover access, and tell us if you think someone else has got in. One person per account; do not sell, lend or share it. We may reclaim usernames that impersonate someone or sit unused.`)]);
if (has(site, 'account')) S.push(['What the account does', P(`The account holds your sign-in, profile, theme, display and notification preferences, linked services, cross-site history and any balances shown in your wallet. Linking a third-party service (for example Discord) lets us read the basic profile that service shares and nothing you did not approve. You can unlink a service at any time.`,
if (has(site, 'account')) S.push(['What the account does', P(`The account holds your sign-in, profile, theme, display and notification preferences, linked services, cross-site history and any balances shown in your wallet. Linking a third-party service (for example Discord) lets us read the basic profile that service shares and nothing you did not approve. You can unlink a service at any time.`,
`Themes you publish to the theme gallery are shared with other users under the licence shown when you publish; do not publish a theme that copies someone else's work.`,
`Themes you publish to the theme gallery are shared with other users under the licence shown when you publish; do not publish a theme that copies someone else's work.`,
@@ -57,6 +70,13 @@ function terms(site) {
@@ -57,6 +70,13 @@ function terms(site) {
`<b>Converters and PDF tools</b>: do not upload files you have no right to process, and do not use the unlock or protect tools to defeat protection on documents that are not yours.`,
`<b>Converters and PDF tools</b>: do not upload files you have no right to process, and do not use the unlock or protect tools to defeat protection on documents that are not yours.`,
`<b>Automation</b>: the tool catalog is public JSON and crawlers are welcome. Automated use of the tools themselves is rate limited; heavy or commercial automation needs our agreement first.`])]);
`<b>Automation</b>: the tool catalog is public JSON and crawlers are welcome. Automated use of the tools themselves is rate limited; heavy or commercial automation needs our agreement first.`])]);
}
}
+ if (has(site, 'service')) {
+ const makes = list(site.makes).map(esc);
+ S.push(['What you make here', P(`${n} keeps what you make with it${makes.length ? `: ${joinAnd(makes)}` : ''}, linked to your OpenVibe account, so it can do its work for you. It stays until you delete it or your account. What you make is private to you unless you choose to share or publish it.`,
+ `Keys, tokens and secrets you create or store here are yours to keep secret. Treat them like passwords, and replace one at once if you think it leaked. Automated use is welcome within the limits the site shows; heavy or commercial use needs our agreement first.`)]);
+ if (site.fetches) S.push(['Requests it makes for you', P(`${n} fetches the addresses you give it, on your behalf, from our servers. Only point it at things you are allowed to read or send to, and respect the rules of the sites involved. It checks politely and backs off when a site asks it to; do not use it to load-test or probe systems that are not yours.`)]);
+ if (site.ai) S.push(['AI models', P(`Some of what ${n} does sends what you give it to an AI model, which writes the answer. The answer says which model wrote it where the site shows that. Models make mistakes: check anything that matters before you rely on it.`)]);
+ }
if (has(site, 'ugc')) S.push(['Pastes, posts and comments', P(`You choose whether an item is public or unlisted. Public items appear in listings and feeds, can be indexed by search engines, and may be copied by other people while they are public. Unlisted items are reachable by anyone who has the link; they are not private. Mark adult content as such.`,
if (has(site, 'ugc')) S.push(['Pastes, posts and comments', P(`You choose whether an item is public or unlisted. Public items appear in listings and feeds, can be indexed by search engines, and may be copied by other people while they are public. Unlisted items are reachable by anyone who has the link; they are not private. Mark adult content as such.`,
`Never post passwords, API keys, private keys, payment details or other people's personal information. We remove leaked credentials, malware, phishing kits and doxxing when we find them, and may do so without notice to limit harm.`)]);
`Never post passwords, API keys, private keys, payment details or other people's personal information. We remove leaked credentials, malware, phishing kits and doxxing when we find them, and may do so without notice to limit harm.`)]);
if (has(site, 'hosting')) S.push(['Stored media', P(`${n} stores recorded streams, clips, thumbnails, images and files for the OpenVibe sites that use them. Whether an item is public follows the setting on the site it came from. Public media can be embedded and linked from elsewhere. Media may be kept on third-party object storage and served through a content delivery network.`,
if (has(site, 'hosting')) S.push(['Stored media', P(`${n} stores recorded streams, clips, thumbnails, images and files for the OpenVibe sites that use them. Whether an item is public follows the setting on the site it came from. Public media can be embedded and linked from elsewhere. Media may be kept on third-party object storage and served through a content delivery network.`,
@@ -78,7 +98,7 @@ function terms(site) {
@@ -78,7 +98,7 @@ function terms(site) {
if (has(site, 'streaming')) S.push(['Money', P(`Donations, tips and purchases of virtual currency are voluntary and final except where the law requires a refund. Payment processors handle the payment under their own terms. Virtual currencies have no cash value outside the payout rules shown on the site. Creators are responsible for their own taxes, and we may withhold payouts connected to fraud or chargebacks.`)]);
if (has(site, 'streaming')) S.push(['Money', P(`Donations, tips and purchases of virtual currency are voluntary and final except where the law requires a refund. Payment processors handle the payment under their own terms. Virtual currencies have no cash value outside the payout rules shown on the site. Creators are responsible for their own taxes, and we may withhold payouts connected to fraud or chargebacks.`)]);
S.push(['Automated and AI-written text', P(`Some descriptive text on OpenVibe, such as footer blurbs, summaries and suggestions, is written by automated systems and checked by rules rather than by a person. It can be wrong. Do not rely on it for decisions that matter.`)]);
S.push(['Automated and AI-written text', P(`Some descriptive text on OpenVibe, such as footer blurbs, summaries and suggestions, is written by automated systems and checked by rules rather than by a person. It can be wrong. Do not rely on it for decisions that matter.`)]);
S.push(['Reporting and moderation', P(`Report content with the report controls where they exist, or write to <a href="mailto:${CONTACT}">${CONTACT}</a>. Copyright complaints follow the <a href="/dmca">DMCA process</a>. We may remove content, limit features, or suspend accounts that break these terms or put other people or the service at risk, and we may act without notice when the harm is urgent. If you think we got it wrong, reply to the notice or write to the same address and a person will look again.`)]);
S.push(['Reporting and moderation', P(`Report content with the report controls where they exist, or write to <a href="mailto:${CONTACT}">${CONTACT}</a>. Copyright complaints follow the <a href="/dmca">DMCA process</a>. We may remove content, limit features, or suspend accounts that break these terms or put other people or the service at risk, and we may act without notice when the harm is urgent. If you think we got it wrong, reply to the notice or write to the same address and a person will look again.`)]);
- S.push(['Leaving', P(`You can stop using ${n} at any time${has(site, 'info') ? '' : ' and delete your account from your account settings. Deleting the account removes your access on every OpenVibe site'}. Sections that by their nature should survive (licences for content already shared, liability, governing law) continue after you leave.`)]);
+ S.push(['Leaving', P(`You can stop using ${n} at any time${has(site, 'info', 'public') ? '' : ' and delete your account from your account settings. Deleting the account removes your access on every OpenVibe site'}. Sections that by their nature should survive (licences for content already shared, liability, governing law) continue after you leave.`)]);
S.push(['Open source, links and feedback', P(`OpenVibe's code is public. Each repository's licence governs your use of the code; these terms govern your use of the hosted service at ${host}. Links to other sites are provided for convenience and we are not responsible for them. If you send us ideas or fixes, we may use them without owing you anything, and contributions to the code are made under the repository's licence.`)]);
S.push(['Open source, links and feedback', P(`OpenVibe's code is public. Each repository's licence governs your use of the code; these terms govern your use of the hosted service at ${host}. Links to other sites are provided for convenience and we are not responsible for them. If you send us ideas or fixes, we may use them without owing you anything, and contributions to the code are made under the repository's licence.`)]);
S.push(['No warranty, limited liability', P(`The service is provided "as is" and "as available", without warranties of any kind, including fitness for a particular purpose, accuracy of results, or uninterrupted availability. To the extent the law allows, OpenVibe and the people who run it are not liable for indirect, incidental or consequential losses, lost data or lost profits arising from your use of ${n}, and our total liability is limited to the greater of what you paid us in the past twelve months or fifty US dollars. Nothing here limits liability that cannot be limited by law, and consumer rights you have under the law of your country remain.`,
S.push(['No warranty, limited liability', P(`The service is provided "as is" and "as available", without warranties of any kind, including fitness for a particular purpose, accuracy of results, or uninterrupted availability. To the extent the law allows, OpenVibe and the people who run it are not liable for indirect, incidental or consequential losses, lost data or lost profits arising from your use of ${n}, and our total liability is limited to the greater of what you paid us in the past twelve months or fifty US dollars. Nothing here limits liability that cannot be limited by law, and consumer rights you have under the law of your country remain.`,
`If your use of ${n} breaks these terms or the law and someone brings a claim against us because of it, you agree to cover our reasonable costs.`)]);
`If your use of ${n} breaks these terms or the law and someone brings a claim against us because of it, you agree to cover our reasonable costs.`)]);
@@ -95,6 +115,10 @@ function privacy(site) {
@@ -95,6 +115,10 @@ function privacy(site) {
'<b>Request logs.</b> Like every web server, ours record your IP address, browser type, the address requested and the time. We use them for security, rate limiting, debugging and aggregate traffic statistics.',
'<b>Request logs.</b> Like every web server, ours record your IP address, browser type, the address requested and the time. We use them for security, rate limiting, debugging and aggregate traffic statistics.',
'<b>Usage statistics.</b> First-party page views with a random session identifier, kept on our own servers, so we can see which parts of the network are used.',
'<b>Usage statistics.</b> First-party page views with a random session identifier, kept on our own servers, so we can see which parts of the network are used.',
'<b>An anonymous page count.</b> The shared navigation bar reports one count per page load containing only the site\'s hostname and the day. No user, IP address or page is stored with it. It decides the order of sites in our menus. It is skipped when your browser sends Global Privacy Control or Do Not Track.',
'<b>An anonymous page count.</b> The shared navigation bar reports one count per page load containing only the site\'s hostname and the day. No user, IP address or page is stored with it. It decides the order of sites in our menus. It is skipped when your browser sends Global Privacy Control or Do Not Track.',
+ has(site, 'public') ? '<b>What you look up.</b> Searches and lookups are answered from our own servers. They are kept with the request logs above for security and to see, in aggregate, what people look for; they are not tied to an account and are not sold or shared.' : '',
+ has(site, 'service') ? `<b>What you make here.</b> ${list(site.makes).length ? `${joinAnd(list(site.makes).map(esc)).replace(/^./, (c) => c.toUpperCase())}, linked` : 'What you create and configure here, linked'} to your account and kept until you delete it or your account.` : '',
+ has(site, 'service') && site.fetches ? '<b>Addresses you give it.</b> The pages, feeds and endpoints you point it at, and what it found there, so it can show you results and history. The sites it reaches see our servers\' address, not yours.' : '',
+ has(site, 'service') && site.ai ? '<b>What you send to AI models.</b> Text and files you give an AI feature are sent to the provider of the model that answers (or to your own computer, when you choose it), which processes them to write the answer under its own terms.' : '',
has(site, 'info') ? '' : `<b>Account information, if you sign in.</b> Your OpenVibe account is held by <a href="https://openvibe.network/privacy">openvibe.network</a>. ${has(site, 'account') ? 'See the next section.' : `${n} receives your user id, username, avatar and role so it can show you as signed in. It does not receive your password or your email address.`}`,
has(site, 'info') ? '' : `<b>Account information, if you sign in.</b> Your OpenVibe account is held by <a href="https://openvibe.network/privacy">openvibe.network</a>. ${has(site, 'account') ? 'See the next section.' : `${n} receives your user id, username, avatar and role so it can show you as signed in. It does not receive your password or your email address.`}`,
has(site, 'account') ? '<b>Your account.</b> Username, email address, a salted hash of your password, profile and avatar, theme, display and notification preferences, push-notification subscriptions for devices where you switched them on, linked services and the basic profile they share, sign-in sessions, wallet balances and transactions, and your cross-site history while it is switched on.' : '',
has(site, 'account') ? '<b>Your account.</b> Username, email address, a salted hash of your password, profile and avatar, theme, display and notification preferences, push-notification subscriptions for devices where you switched them on, linked services and the basic profile they share, sign-in sessions, wallet balances and transactions, and your cross-site history while it is switched on.' : '',
has(site, 'tools') ? '<b>What you give a tool.</b> Files, text, links, domain names and IP addresses you submit are processed to produce your result. Uploads and results are deleted automatically, normally within one hour for visitors and 24 hours for signed-in users. Files can contain hidden metadata such as location or author; the tools only read what they need, but consider removing it first.' : '',
has(site, 'tools') ? '<b>What you give a tool.</b> Files, text, links, domain names and IP addresses you submit are processed to produce your result. Uploads and results are deleted automatically, normally within one hour for visitors and 24 hours for signed-in users. Files can contain hidden metadata such as location or author; the tools only read what they need, but consider removing it first.' : '',
@@ -108,11 +132,12 @@ function privacy(site) {
@@ -108,11 +132,12 @@ function privacy(site) {
S.push(['Cookies and storage in your browser', P(`We use a small number of first-party cookies: a sign-in token (<code>ov_token</code>), a refresh token limited to the sign-in paths, a short-lived state cookie during sign-in, and a hint (<code>ov_sso_hint</code>) that remembers whether you were signed in. openvibe.network also keeps a sign-in cookie that lets other OpenVibe sites check, in a hidden frame or through your browser's built-in federated sign-in, whether to sign you in quietly. It is never used for anything except that check.`,
S.push(['Cookies and storage in your browser', P(`We use a small number of first-party cookies: a sign-in token (<code>ov_token</code>), a refresh token limited to the sign-in paths, a short-lived state cookie during sign-in, and a hint (<code>ov_sso_hint</code>) that remembers whether you were signed in. openvibe.network also keeps a sign-in cookie that lets other OpenVibe sites check, in a hidden frame or through your browser's built-in federated sign-in, whether to sign you in quietly. It is never used for anything except that check.`,
`Your theme, display settings (text size, reduced motion), recent pages, a cached copy of the navigation and similar conveniences are kept in your browser's local storage. There are no third-party advertising or social cookies. Our sites sit behind Cloudflare, which may set its own security cookies and measure performance.`)]);
`Your theme, display settings (text size, reduced motion), recent pages, a cached copy of the navigation and similar conveniences are kept in your browser's local storage. There are no third-party advertising or social cookies. Our sites sit behind Cloudflare, which may set its own security cookies and measure performance.`)]);
S.push(['Across OpenVibe sites', P(`OpenVibe sites share one account. When you are signed in, the site you visit learns who you are from openvibe.network. While cross-site history is switched on, the pages and tools you open while signed in are listed for you, and only you, at <a href="https://openvibe.network/history">openvibe.network/history</a>, where you can clear the list or pause it.`)]);
S.push(['Across OpenVibe sites', P(`OpenVibe sites share one account. When you are signed in, the site you visit learns who you are from openvibe.network. While cross-site history is switched on, the pages and tools you open while signed in are listed for you, and only you, at <a href="https://openvibe.network/history">openvibe.network/history</a>, where you can clear the list or pause it.`)]);
- S.push(['Who we share with', P(`Only the services needed to run the network: hosting (OVH, servers in the United States), network security and delivery (Cloudflare), object storage for media (Backblaze B2 or Cloudflare R2)${has(site, 'streaming') ? ', payment processors, AI providers used for transcription and summaries' : ''}, and email delivery for account messages. They process data on our instructions. We disclose information when the law requires it, and we tell the person affected when we are allowed to. If OpenVibe is ever reorganised or handed to new maintainers, the data moves under the same promises.`)]);
+ const extra = list(site.processors).map(esc).concat(has(site, 'service') && site.ai ? ['the AI providers whose models answer your requests'] : []);
+ S.push(['Who we share with', P(`Only the services needed to run the network: hosting (OVH, servers in the United States), network security and delivery (Cloudflare), object storage for media (Backblaze B2 or Cloudflare R2)${has(site, 'streaming') ? ', payment processors, AI providers used for transcription and summaries' : ''}${extra.map((x) => `, ${x}`).join('')}, and email delivery for account messages. They process data on our instructions. We disclose information when the law requires it, and we tell the person affected when we are allowed to. If OpenVibe is ever reorganised or handed to new maintainers, the data moves under the same promises.`)]);
S.push(['How long we keep things', UL(['Request logs and raw usage events: a limited rolling period, then totals only.', 'The anonymous page count: totals per site and day, nothing personal to delete.',
S.push(['How long we keep things', UL(['Request logs and raw usage events: a limited rolling period, then totals only.', 'The anonymous page count: totals per site and day, nothing personal to delete.',
has(site, 'tools') ? 'Tool uploads and results: one hour for visitors, 24 hours for signed-in users, temporary upload fragments about ten minutes.' : '',
has(site, 'tools') ? 'Tool uploads and results: one hour for visitors, 24 hours for signed-in users, temporary upload fragments about ten minutes.' : '',
userContent(site) ? 'Content you post: until you delete it or your account, plus short-lived caches and backups.' : '',
userContent(site) ? 'Content you post: until you delete it or your account, plus short-lived caches and backups.' : '',
- has(site, 'info') ? '' : 'Account data: until you delete your account. Backups expire on their own shortly afterwards. Records we must keep by law, such as payment records, are kept for the required period.'])]);
+ has(site, 'info', 'public') ? '' : 'Account data: until you delete your account. Backups expire on their own shortly afterwards. Records we must keep by law, such as payment records, are kept for the required period.'])]);
S.push(['Where your data is', P(`Our servers are in the United States. If you use OpenVibe from elsewhere, your information is transferred to and processed in the United States, which may have different data protection rules from your country.`)]);
S.push(['Where your data is', P(`Our servers are in the United States. If you use OpenVibe from elsewhere, your information is transferred to and processed in the United States, which may have different data protection rules from your country.`)]);
S.push(['Security', P(`Traffic is encrypted in transit. Passwords are stored as salted hashes, sign-in tokens are signed and short-lived, and internal service routes are not reachable from the internet. No system is perfectly secure; if a breach affects your personal information we will tell you and the relevant authorities as the law requires.`)]);
S.push(['Security', P(`Traffic is encrypted in transit. Passwords are stored as salted hashes, sign-in tokens are signed and short-lived, and internal service routes are not reachable from the internet. No system is perfectly secure; if a breach affects your personal information we will tell you and the relevant authorities as the law requires.`)]);
S.push(['Your choices and rights', P(`You can use most of ${n} without an account, block cookies, and switch on Global Privacy Control to skip the anonymous page count. With an account you can view, correct, export and delete your data from your account settings, or ask us to do it.`,
S.push(['Your choices and rights', P(`You can use most of ${n} without an account, block cookies, and switch on Global Privacy Control to skip the anonymous page count. With an account you can view, correct, export and delete your data from your account settings, or ask us to do it.`,
@@ -127,13 +152,15 @@ function dmca(site) {
@@ -127,13 +152,15 @@ function dmca(site) {
const S = [];
const S = [];
const position = has(site, 'tools') ? ` The tools here process material at a user's request and do not keep a library of it: uploads and results are deleted automatically within hours, and downloaders neither host nor index what they fetch. If the material you are concerned about lives on another platform, a notice to that platform removes it at the source.`
const position = has(site, 'tools') ? ` The tools here process material at a user's request and do not keep a library of it: uploads and results are deleted automatically within hours, and downloaders neither host nor index what they fetch. If the material you are concerned about lives on another platform, a notice to that platform removes it at the source.`
: has(site, 'info') ? ` ${n} currently publishes only its own material and does not host uploads from the public.`
: has(site, 'info') ? ` ${n} currently publishes only its own material and does not host uploads from the public.`
+ : has(site, 'public') ? ` ${n} shows public information gathered from OpenVibe and other public sources and does not host uploads from the public. Material that lives on another OpenVibe site is removed through that site's notice process, which removes it here too.`
+ : has(site, 'service') ? ` ${n} keeps what its users make for themselves, and most of it is private to them. A notice about something they chose to publish works as below.`
: has(site, 'hosting') ? ` ${n} stores media for other OpenVibe sites. Removing an item here removes it from the sites that embed it.`
: has(site, 'hosting') ? ` ${n} stores media for other OpenVibe sites. Removing an item here removes it from the sites that embed it.`
: has(site, 'account') ? ` ${n} hosts account profiles, avatars and published themes; content on the other OpenVibe sites is handled by the notice process on those sites.` : '';
: has(site, 'account') ? ` ${n} hosts account profiles, avatars and published themes; content on the other OpenVibe sites is handled by the notice process on those sites.` : '';
S.push(['Our position', P(`${n} respects copyright and responds to clear notices of alleged infringement under the Digital Millennium Copyright Act (17 U.S.C. § 512).${position}`)]);
S.push(['Our position', P(`${n} respects copyright and responds to clear notices of alleged infringement under the Digital Millennium Copyright Act (17 U.S.C. § 512).${position}`)]);
S.push(['Before you send a notice', P(`Consider whether the use is permitted, for example as fair use, commentary, criticism, or under a licence. Notices must come from the copyright owner or someone authorised to act for them. Misrepresentations in a notice carry legal liability.`)]);
S.push(['Before you send a notice', P(`Consider whether the use is permitted, for example as fair use, commentary, criticism, or under a licence. Notices must come from the copyright owner or someone authorised to act for them. Misrepresentations in a notice carry legal liability.`)]);
S.push(['Send a takedown notice', P(`Email <a href="mailto:${CONTACT}">${CONTACT}</a> with all of the following:`) + `<ol><li>Your name, postal address, phone number and email address, and whom you represent.</li><li>The copyrighted work you say was infringed, or a representative list if there are several.</li><li>The exact URL on ${host} of each item you want removed. General descriptions, search results and channel links are not enough to find it.</li><li>A statement that you have a good-faith belief the use is not authorised by the copyright owner, its agent or the law.</li><li>A statement, under penalty of perjury, that the information is accurate and that you are the owner or authorised to act for the owner.</li><li>Your physical or electronic signature.</li></ol>`]);
S.push(['Send a takedown notice', P(`Email <a href="mailto:${CONTACT}">${CONTACT}</a> with all of the following:`) + `<ol><li>Your name, postal address, phone number and email address, and whom you represent.</li><li>The copyrighted work you say was infringed, or a representative list if there are several.</li><li>The exact URL on ${host} of each item you want removed. General descriptions, search results and channel links are not enough to find it.</li><li>A statement that you have a good-faith belief the use is not authorised by the copyright owner, its agent or the law.</li><li>A statement, under penalty of perjury, that the information is accurate and that you are the owner or authorised to act for the owner.</li><li>Your physical or electronic signature.</li></ol>`]);
S.push(['What happens next', P(`We remove or disable access to the material, tell the person who posted it, and give them a copy of your notice, including your contact details. We act on complete notices as quickly as we can, normally within a few business days. Incomplete notices get a reply saying what is missing.${userContent(site) ? ' Copies held in caches and in third-party archives are outside our control and expire or must be requested separately.' : ''}`)]);
S.push(['What happens next', P(`We remove or disable access to the material, tell the person who posted it, and give them a copy of your notice, including your contact details. We act on complete notices as quickly as we can, normally within a few business days. Incomplete notices get a reply saying what is missing.${userContent(site) ? ' Copies held in caches and in third-party archives are outside our control and expire or must be requested separately.' : ''}`)]);
- if (!has(site, 'info', 'tools')) S.push(['Counter-notice', P(`If your material was removed by mistake or misidentification, send a counter-notice to the same address with: your name, address, phone number and email; the material and where it appeared before removal; a statement under penalty of perjury that you believe in good faith it was removed by mistake or misidentification; your consent to the jurisdiction of the federal district court for your address (or for Washington State if you are outside the United States) and to accept service from the complainant; and your signature.`,
+ if (!has(site, 'info', 'tools', 'public')) S.push(['Counter-notice', P(`If your material was removed by mistake or misidentification, send a counter-notice to the same address with: your name, address, phone number and email; the material and where it appeared before removal; a statement under penalty of perjury that you believe in good faith it was removed by mistake or misidentification; your consent to the jurisdiction of the federal district court for your address (or for Washington State if you are outside the United States) and to accept service from the complainant; and your signature.`,
`We forward the counter-notice to the complainant and may restore the material after 10 to 14 business days unless they tell us they have filed a court action.`)]);
`We forward the counter-notice to the complainant and may restore the material after 10 to 14 business days unless they tell us they have filed a court action.`)]);
S.push(['Repeat infringers and false claims', P(`Accounts that receive repeated valid notices lose the ability to post and, in appropriate circumstances, are terminated across the network. Knowingly false notices and counter-notices carry liability under § 512(f); we refuse notices that are clearly abusive or automated without review.`)]);
S.push(['Repeat infringers and false claims', P(`Accounts that receive repeated valid notices lose the ability to post and, in appropriate circumstances, are terminated across the network. Knowingly false notices and counter-notices carry liability under § 512(f); we refuse notices that are clearly abusive or automated without review.`)]);
S.push(['Other complaints', P(`Trademark, privacy, impersonation, harassment and other non-copyright complaints go to the same address; say which kind of complaint it is and give the exact URL. Content that sexually exploits children should be reported to us immediately and to the <a href="https://report.cybertip.org/" rel="noopener">NCMEC CyberTipline</a> or your national hotline; we remove it and report it to the authorities.`)]);
S.push(['Other complaints', P(`Trademark, privacy, impersonation, harassment and other non-copyright complaints go to the same address; say which kind of complaint it is and give the exact URL. Content that sexually exploits children should be reported to us immediately and to the <a href="https://report.cybertip.org/" rel="noopener">NCMEC CyberTipline</a> or your national hotline; we remove it and report it to the authorities.`)]);
@@ -202,4 +229,6 @@ function handler(site) {
@@ -202,4 +229,6 @@ function handler(site) {
};
};
}
}
-module.exports = { PATHS, TITLES, UPDATED, CONTACT, build, article, page, handler, CSS };
+const PROFILES = ['streaming', 'tools', 'ugc', 'games', 'hosting', 'account', 'info', 'public', 'service'];
+
+module.exports = { PATHS, TITLES, UPDATED, CONTACT, PROFILES, build, article, page, handler, CSS };
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.106.3",
+ "version": "4.107.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.106.3",
+ "version": "4.107.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.106.3",
+ "version": "4.107.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
new file mode 100644
new file mode 100644
@@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
+'use strict';
+// legal.js — the public and service profiles (11-03): ten sites served no /terms, /privacy or /dmca because no profile
+// told the truth about them ("info" says the site is not open yet; "tools" promises uploads deleted within the hour).
+// node test/legal-profiles.test.js
+const assert = require('assert');
+const legal = require('../legal');
+
+const text = (kind, site) => legal.build(kind, site).sections.map(([h, html]) => `${h}\n${html}`).join('\n');
+
+// public: open, read-only, no account; never "not open yet", never an account to delete, no counter-notice.
+{
+ const site = { host: 'openvibe.report', name: 'OpenVibe.Report', profile: 'public', what: 'The network\'s numbers open to everyone.' };
+ const t = text('terms', site);
+ assert.match(t, /What OpenVibe\.Report does\n<p>The network's numbers open to everyone\.<\/p>/);
+ assert.match(t, /No account needed/);
+ assert.doesNotMatch(t, /not open yet|delete your account|Your account\n/);
+ const p = text('privacy', site);
+ assert.match(p, /What you look up/);
+ assert.doesNotMatch(p, /deleted automatically|Account data: until/);
+ const d = text('dmca', site);
+ assert.match(d, /does not host uploads from the public/);
+ assert.doesNotMatch(d, /Counter-notice/);
+}
+
+// service: what it keeps, fetches and AI only when the site says so; its own processors named.
+{
+ const site = { host: 'openvibe.watch', name: 'OpenVibe.Watch', profile: 'service', what: 'Watch a page and get told when it changes.', makes: ['the watches you set', 'what each check found'], fetches: true };
+ const t = text('terms', site);
+ assert.match(t, /What you make here\n<p>OpenVibe\.Watch keeps what you make with it: the watches you set and what each check found/);
+ assert.match(t, /Requests it makes for you/);
+ assert.doesNotMatch(t, /AI models\n/, 'no AI unless the site says so');
+ assert.match(t, /Your content and the licence you give us/);
+ assert.doesNotMatch(t, /deleted automatically|not open yet/);
+ const p = text('privacy', site);
+ assert.match(p, /The watches you set and what each check found, linked to your account/);
+ assert.match(p, /Addresses you give it/);
+ assert.doesNotMatch(p, /What you send to AI models/);
+ assert.match(text('dmca', site), /keeps what its users make for themselves/);
+}
+{
+ const site = { host: 'ai.openvibe.services', name: 'OpenVibe AI', profile: 'service', ai: true, makes: ['your runs and their costs'], processors: ['your <own> list'] };
+ assert.match(text('terms', site), /AI models\n<p>Some of what OpenVibe AI does sends what you give it to an AI model/);
+ const p = text('privacy', site);
+ assert.match(p, /What you send to AI models/);
+ assert.match(p, /the AI providers whose models answer your requests/);
+ assert.match(p, /your <own> list/, 'escaped');
+ assert.doesNotMatch(p, /Addresses you give it/);
+}
+
+// The older profiles read as before.
+{
+ assert.match(text('terms', { host: 'x.openvibe.tools', name: 'X', profile: 'info' }), /This site is not open yet/);
+ assert.match(text('privacy', { host: 'openvibe.tools', name: 'OpenVibe.Tools', profile: 'tools' }), /deleted automatically/);
+ assert.doesNotMatch(text('terms', { host: 'openvibe.live', name: 'OpenVibe.Live', profile: 'streaming' }), /What you make here|No account needed/);
+}
+
+assert.deepStrictEqual(legal.PROFILES, ['streaming', 'tools', 'ugc', 'games', 'hosting', 'account', 'info', 'public', 'service']);
+for (const profile of legal.PROFILES) for (const kind of Object.keys(legal.TITLES)) assert.ok(legal.page(kind, { host: 'a.openvibe.network', name: 'A', profile, id: 'network' }).includes('</html>'));
+console.log('legal profiles: all checks passed');