From e1cf939aaa6b3e4f46a117ff838b0e5e88747a7c Mon Sep 17 00:00:00 2001 From: Aleksandr Shevchuk Date: Sun, 18 Oct 2026 19:24:33 +0300 Subject: [PATCH] feat!: drop libraries="hub" and settle auto-resize before 1.0 `hub` has been read as `pack` since player 0.6. Kept into 1.0 it would have had to stay through 1.x, so the element, the embed site and the verifier stop special-casing it: it is a word like any other, a URL relative to the page, and a page still writing it fails with an error naming that address. A bare `auto-resize` is no alias, since anything but `off` means on, so it leaves the deprecation list and our own pages stop writing it. The README's API table names the `saveInterval` property, and loses a runtime note the peer range already enforces. BREAKING CHANGE: libraries="hub" (element), &libraries=hub and --default-libraries hub (embed), --libraries hub (verify) no longer mean the pack. Write pack. Closes #44 Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 14 ++++++++------ README.md | 6 +++--- apps/demo/demo/setup.html | 4 ++-- apps/demo/embed.html | 2 +- apps/demo/scripts/build-demo-content.mjs | 2 +- docs/compatibility.md | 3 +-- docs/libraries.md | 6 +++--- docs/privacy.md | 2 +- packages/embed/README.md | 2 +- packages/embed/lib/build.mjs | 10 +++++----- packages/embed/site/embed.js | 6 ++---- packages/embed/site/index.html | 2 +- packages/embed/tests/build.test.ts | 17 ++++++++--------- packages/embed/tests/site.test.ts | 6 +++--- packages/player/src/h5p-offline-player.ts | 9 +-------- packages/player/src/shared/constants.ts | 5 ++--- packages/player/src/shared/library-sources.ts | 19 ++++++------------- .../player/tests/browser/libraries.test.ts | 4 ++-- .../player/tests/unit/library-sources.test.ts | 13 +++++++------ packages/verify/README.md | 2 +- packages/verify/bin/h5p-verify.mjs | 8 ++++---- packages/verify/lib/page.html | 2 +- packages/verify/lib/verify.mjs | 6 +++--- skills/h5p-player-setup/SKILL.md | 6 +++--- 24 files changed, 70 insertions(+), 86 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b639a39..360b967 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -968,8 +968,10 @@ element, and the element acts. That relay is why `frame-document.ts` has a `mess never promised. `pack` is `@missing-elements/h5p-libraries` on jsDelivr at `LIBRARY_PACK_VERSION` (its parts under `LIBRARY_PACK_BASE` since 2026-10-18, next invariant but two), which `sync-h5p-assets` stamps from `packages/libraries/package.json`; - `hub` is read as `pack` with one console note (`parseLibrarySources`), and `pack hub` is not - tried twice. Pinned rather than `@0`, so a player release always plays against the same + `hub` was read as `pack`, with a console note, from 0.6 to 0.10, and went before 1.0 + (2026-10-18, #44): an alias kept would have had to stay through 1.x, and a page still writing it + now fails on the address `hub`, named in the error, rather than playing something it did not + name. Pinned rather than `@0`, so a player release always plays against the same libraries, and `check-library-pack.mjs` in the player's `prepublishOnly` refuses a version not on npm, or one whose `parts/index.json` on jsDelivr is not this checkout's: the weekly refresh (`.github/workflows/libraries.yml`) bumps the pack on a branch and stamps the player — version @@ -2028,8 +2030,8 @@ SPA-fallback trap described above cannot happen there. turns the default `connect-src 'self' https:` into exactly the hosts given, and `config.js` hands the same list to the page, which refuses an `src` or a library URL off it by name before fetching anything. (Until 2026-10-10 it also refused `hub` unless `https://api.h5p.org` - was listed; `hub` now means `pack`, and a site without its own pack needs - `https://cdn.jsdelivr.net` listed for it.) The page checks only the address it is + was listed; a site without its own pack needs `https://cdn.jsdelivr.net` listed for `pack`, and + `hub`, which meant `pack` from then to embed 0.8, is no longer a token.) The page checks only the address it is given; `connect-src` also applies to every redirect, so a listed host that redirects off the list fails as a plain network error — the README says to list every host on the way. With a list, a listed host plays as it is, never untrusted: the host vouched for it when it wrote the site. Without one, a package @@ -2514,8 +2516,8 @@ deliberate additions to it, not drift: it; the GPL on the H5P core made that `(MIT AND GPL-3.0-only)`, and from 0.5 the runtime and the frame's boot script are `@missing-elements/h5p-runtime` so the player can be MIT alone. - A published library bundle, and `libraries` as an ordered list. The design has `hub` or one - URL; the hub's move put a bundle the project controls first, and since 0.6 `hub` means that - bundle and the hub is not asked at all. Since 0.10 the pack is split, a package downloads only + URL; the hub's move put a bundle the project controls first, and since 0.6 the hub is not asked + at all (`hub` meant the pack until it went before 1.0). Since 0.10 the pack is split, a package downloads only the parts it needs, and the pack's newer versions play in place of the older ones a package carries: the design plays what a package carries. - Content written for an older minor than the one served is upgraded in the frame with the diff --git a/README.md b/README.md index 2c4de02..dd0719a 100644 --- a/README.md +++ b/README.md @@ -179,11 +179,11 @@ allows one Service Worker, or must work offline, mounts the player in its own wo | `jobs` | attribute | The background worker's URL, for a CSP without `blob:` in `worker-src`. Default: `blob:`, then `h5p-jobs.js` beside the element | | `runtime` | property | The `runtime` export of `@missing-elements/h5p-runtime`. Set before `src` | | `assets-base` | attribute | Where the runtime's `dist/` is served. Default: `frame-assets/` beside the element | -| `libraries` | attribute | Sources for a package's libraries, tried in order: `pack`, the library pack, of which only the libraries a package needs are downloaded, and whose newer versions play in place of those a package carries; a `.h5p` URL, which only fills gaps; or both. `hub` means `pack`. Default: a package plays as it is, and one without its libraries is refused | +| `libraries` | attribute | Sources for a package's libraries, tried in order: `pack`, the library pack, of which only the libraries a package needs are downloaded, and whose newer versions play in place of those a package carries; a `.h5p` URL, which only fills gaps; or both. Default: a package plays as it is, and one without its libraries is refused | | `pack-base` | attribute | Where `pack` is: the folder of a copy of `@missing-elements/h5p-libraries`' `parts/` on your site. Default: jsDelivr, at the version this release pins | | `pack` | property | The `pack` export of `@missing-elements/h5p-libraries`, as a bundler emits it; wins over `pack-base`. Set before `src` | | `refresh-libraries` | attribute | `off` plays the libraries a package carries as exported, and `pack` only fills gaps. Default: the pack's newer versions play | -| `untrusted` | attribute, property | Plays a package you did not make as data: its own library folders are ignored and the libraries come from `libraries` (default `pack`), `content/` serves only H5P's own file types, and the frame's script policy names exact paths. Its content is checked against each library's `semantics.json`, as the H5P CMS plugins check it. A package that needs its own copy of a library is refused, with `missingLibraries.carried` set. Needs the runtime at 0.2 or later | +| `untrusted` | attribute, property | Plays a package you did not make as data: its own library folders are ignored and the libraries come from `libraries` (default `pack`), `content/` serves only H5P's own file types, and the frame's script policy names exact paths. Its content is checked against each library's `semantics.json`, as the H5P CMS plugins check it. A package that needs its own copy of a library is refused, with `missingLibraries.carried` set | | `allow-origins` | attribute | Extra origins for the frame's CSP, space separated | | `frame`, `copyright`, `export`, `icon`, `embed` | attribute | Show the H5P action bar and its buttons. `export` needs `download-url` or a package URL, `embed` needs `embed-code` | | `fullscreen` | attribute | `off` removes the fullscreen button | @@ -196,7 +196,7 @@ allows one Service Worker, or must work offline, mounts the player in its own wo | `auto-resize` | attribute | `off` to size the element yourself. Default: follows the content's height | | `preload` | attribute, property | `auto` pulls large deflated media before it is asked for. Default: `none` | | `resume` | attribute, property | `device` keeps the content's saved state on this device; `host` hands it to the page. Default: `off` | -| `save-interval` | attribute, property | Seconds between saves while the content runs, with `resume`; also saved after each completion and as the page closes. Default: `10` | +| `save-interval` | attribute, property (`saveInterval`) | Seconds between saves while the content runs, with `resume`; also saved after each completion and as the page closes. Default: `10` | | `userData` | property | Under `resume="host"`, the state to restore, as `userdata` events carried it. Set before `src`. Not checked against the build: compare the `revision` you stored | | `state` | read-only | `idle`, `probing`, `downloading`, `indexing`, `ready` or `error` | | `pkgId`, `revision` | read-only | The loaded package's id and build fingerprint | diff --git a/apps/demo/demo/setup.html b/apps/demo/demo/setup.html index fe91391..5b46bff 100644 --- a/apps/demo/demo/setup.html +++ b/apps/demo/demo/setup.html @@ -287,11 +287,11 @@

Attributes

jobsWhere the background worker script is, for a page whose Content Security Policy refuses blob: workers: copy dist/h5p-jobs.js beside h5p-sw.js and point this at it. Default: a blob: URL, then h5p-jobs.js next to the element. Must be same-origin. runtimeA property: the runtime export of @missing-elements/h5p-runtime, for a bundler to emit the files. Set it before src; it wins over assets-base. assets-baseThe folder the runtime's dist/ is served from. Default: frame-assets/ next to the element. May be a CDN. - librariesWhere a package's libraries come from: pack for @missing-elements/h5p-libraries, every H5P hub content type's libraries, one archive per library, of which only those a package needs are downloaded, from jsDelivr, and whose newer versions play in place of those a package carries; or the URL of a .h5p that carries library folders, which only fills gaps; or several separated by spaces, tried in order. hub means pack. Downloaded once and cached. Default: a package plays as it is, and one without its libraries is refused, with the missing libraries named. + librariesWhere a package's libraries come from: pack for @missing-elements/h5p-libraries, every H5P hub content type's libraries, one archive per library, of which only those a package needs are downloaded, from jsDelivr, and whose newer versions play in place of those a package carries; or the URL of a .h5p that carries library folders, which only fills gaps; or several separated by spaces, tried in order. Downloaded once and cached. Default: a package plays as it is, and one without its libraries is refused, with the missing libraries named. pack-baseThe folder of your own copy of the pack's parts/, with its libraries.txt of licences beside it, for pack to come from instead of jsDelivr. With a bundler, set the pack property to the package's pack export instead. refresh-librariesoff plays the libraries a package carries as exported; pack then only fills gaps. preloadauto pulls large compressed media once the content is up, instead of when the learner presses play. - untrustedPlays a package you did not make as data: none of its own code runs. Its library folders are ignored and the libraries come from libraries, pack when that is not set; content/ serves only H5P's own file types; the frame's script policy names exact paths instead of your origin; the content is checked against each library's semantics.json, as the H5P CMS plugins check it. A package that needs its own copy of a library is refused, and the error's missingLibraries.carried says so. Needs the runtime at 0.2 or later. + untrustedPlays a package you did not make as data: none of its own code runs. Its library folders are ignored and the libraries come from libraries, pack when that is not set; content/ serves only H5P's own file types; the frame's script policy names exact paths instead of your origin; the content is checked against each library's semantics.json, as the H5P CMS plugins check it. A package that needs its own copy of a library is refused, and the error's missingLibraries.carried says so. allow-originsExtra origins the frame's Content Security Policy should permit, space separated: a tenant's video host, an in-house CDN. resumeKeep the content's own saved state — the slide reached, the answers so far, the position in a video — in the browser's storage on that device, and pick up there on the next load of the package. host keeps nothing on the device: the page gets a userdata event on every save and hands the state back through userData before the next load. Default: off, nothing is kept. Nothing is sent in either case. save-intervalSeconds between saves while the content runs, with resume. The content is also saved shortly after each completion and as the page closes. Default: 10; the H5P plugins use 30, which suits a host that sends each save to a server. diff --git a/apps/demo/embed.html b/apps/demo/embed.html index dfba66c..e0e5850 100644 --- a/apps/demo/embed.html +++ b/apps/demo/embed.html @@ -16,7 +16,7 @@ Loading… - + diff --git a/apps/demo/scripts/build-demo-content.mjs b/apps/demo/scripts/build-demo-content.mjs index 61d3ea0..bd3f03b 100644 --- a/apps/demo/scripts/build-demo-content.mjs +++ b/apps/demo/scripts/build-demo-content.mjs @@ -10,7 +10,7 @@ import { download, hubBundle as cachedBundle } from './lib/hub.mjs' * Builds the packages the hosted demo plays, from `demo/content/src//` into * `demo/content/.h5p`. Each source is our own `content.json` plus a `manifest.json` * naming the content type; the libraries come from the H5P hub's bundle for that content type, - * exactly as `libraries="hub"` fetches them at runtime, and the result is a self-contained + * as `libraries="hub"` fetched them at runtime up to player 0.5, and the result is a self-contained * package run through the normalizer, so it is also an example of what the normalizer produces. * A manifest with `"stripped": true` is written the way h5p.com and h5p.org export: `h5p.json` * naming the main library alone, `content/`, and no library folders — the package the player diff --git a/docs/compatibility.md b/docs/compatibility.md index dda2e6d..ebda956 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -130,8 +130,7 @@ warning can come in any release. ## Deprecation A renamed attribute, value or option keeps working through the rest of its major and goes only -in a major. Two are in that state now: `libraries="hub"`, read as `pack` with a console note, and -a bare `auto-resize`, read as the default. +in a major. None is in that state now. ## Browsers diff --git a/docs/libraries.md b/docs/libraries.md index 698e5e2..3b4c85f 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -81,9 +81,9 @@ the ones before it lack. The pack refreshes wherever it is in the list, and supp missing once the sources before it have been tried. A `.h5p` URL only supplies what is missing. Nothing is on by default: a package is played as it is unless `libraries` names a source. -`hub` means `pack`. Up to player 0.5 it asked the H5P hub itself, but the only hub host that -answers a browser serves an outdated catalogue, so current exports could not get their libraries -there anyway. +The H5P hub itself is not asked: the only hub host that answers a browser serves an outdated +catalogue, so current exports could not get their libraries there anyway. Up to player 0.10, +`hub` was read as `pack`; it is now a word like any other, a URL relative to the page. ## When a source cannot be reached diff --git a/docs/privacy.md b/docs/privacy.md index c2c34c3..a37c7ab 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -40,7 +40,7 @@ the [README](https://github.com/missing-elements/h5p-offline-player#which-setup) |---|---|---| | The package | The host in `src` | Every load from a URL. A picked file is not sent anywhere | | The player and runtime files | Your site, or a CDN if `assets-base` names one (jsDelivr in the README's example) | Every load | -| Library pack | `cdn.jsdelivr.net` for `pack` (and `hub`, which means the same), unless `pack-base` or the `pack` property names your own copy | With `libraries="pack"`: the libraries a package lacks, and those the pack has newer versions of than the package carries, once each per device. The requests show which libraries, so which content types, are played. Serve the pack from your own domain to keep this on your hosting | +| Library pack | `cdn.jsdelivr.net` for `pack`, unless `pack-base` or the `pack` property names your own copy | With `libraries="pack"`: the libraries a package lacks, and those the pack has newer versions of than the package carries, once each per device. The requests show which libraries, so which content types, are played. Serve the pack from your own domain to keep this on your hosting | | Library bundle | The `.h5p` URL in `libraries` | A package without its own libraries, once per bundle | | MathJax | `cdn.jsdelivr.net` or `cdnjs.cloudflare.com` | Only a package carrying an old `H5P.MathDisplay` that loads MathJax from a CDN. The pack's MathDisplay renders from its own folder, and sends nothing | | Web fonts | `ajax.googleapis.com`, `fonts.googleapis.com`, `fonts.gstatic.com` (Google) | Only content types that load Google Fonts | diff --git a/packages/embed/README.md b/packages/embed/README.md index a880d4b..d31c94d 100644 --- a/packages/embed/README.md +++ b/packages/embed/README.md @@ -74,7 +74,7 @@ To update, run the command again with `--force` and redeploy. | Parameter | Effect | |---|---| | `src=` | The package, required. Encode `&`, `#`, `+`, `%` and spaces | -| `libraries=pack`, `` or `none` | Where libraries come from; several are tried in order. `pack` is this domain's copy, or jsDelivr's without one: a package gets the libraries it lacks from it, and those it carries at the pack's newer versions. `hub` means `pack`; `none` turns off `--default-libraries` | +| `libraries=pack`, `` or `none` | Where libraries come from; several are tried in order. `pack` is this domain's copy, or jsDelivr's without one: a package gets the libraries it lacks from it, and those it carries at the pack's newer versions. `none` turns off `--default-libraries` | | `refresh-libraries=off` | Play the libraries a package carries as exported; `pack` then only fills gaps | | `frame`, `copyright`, `export`, `icon`, `reporting` | H5P's action bar and its buttons | | `fullscreen=off` | No fullscreen button | diff --git a/packages/embed/lib/build.mjs b/packages/embed/lib/build.mjs index b5290d5..261711d 100644 --- a/packages/embed/lib/build.mjs +++ b/packages/embed/lib/build.mjs @@ -57,7 +57,7 @@ const PACK_CDN_ORIGIN = 'https://cdn.jsdelivr.net' /** * A `libraries` value for addresses that name none, checked against the site it goes into: the - * tokens the page understands (`pack`, `hub`, `none`, https URLs), and with a host list, nothing + * tokens the page understands (`pack`, `none`, https URLs), and with a host list, nothing * the page would then refuse on every load. Returns the value normalised to single spaces. * * @param {string | null | undefined} value @@ -74,19 +74,19 @@ export function parseDefaultLibraries(value, { packages = null, libraries = true return null } // An exact match on a parsed origin, not a substring of a URL. Without its own copy, `pack` is - // the copy on jsDelivr that the element names; `hub` has meant `pack` since player 0.6. + // the copy on jsDelivr that the element names. const cdnAllowed = !packages || packages.some((origin) => origin === PACK_CDN_ORIGIN) for (const token of tokens) { - if (token === 'pack' || token === 'hub') { + if (token === 'pack') { if (!libraries && !cdnAllowed) { - throw new EmbedError(`--default-libraries names ${token}, but the site has no pack (--no-libraries) and --packages does not list ${PACK_CDN_ORIGIN}.`) + throw new EmbedError(`--default-libraries names pack, but the site has no pack (--no-libraries) and --packages does not list ${PACK_CDN_ORIGIN}.`) } } else { let url try { url = new URL(token) } catch { - throw new EmbedError(`--default-libraries: not pack, hub, none or a URL: ${token}`) + throw new EmbedError(`--default-libraries: not pack, none or a URL: ${token}`) } if (url.protocol !== 'https:' && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) { throw new EmbedError(`--default-libraries: not an https URL: ${token}`) diff --git a/packages/embed/site/embed.js b/packages/embed/site/embed.js index 68ca8ae..486541e 100644 --- a/packages/embed/site/embed.js +++ b/packages/embed/site/embed.js @@ -244,12 +244,10 @@ export function startEmbed({ packBase = null, pack = null, librariesPack = null, const sources = [] if (value === 'none') return { value: '' } for (const token of value.split(/\s+/).filter(Boolean)) { - if (token === 'pack' || token === 'hub') { + if (token === 'pack') { // This site's copy of the pack, which the element was pointed at above; without one, the // element's own `pack`, the copy on jsDelivr, where that may be reached, so a snippet - // written for `pack` keeps playing after a rebuild with --no-libraries. `hub` has meant - // `pack` since player 0.6: the one H5P hub host that answers a browser serves a - // catalogue older than current exports need. + // written for `pack` keeps playing after a rebuild with --no-libraries. if (ownPack || (!librariesPack && permitted(PACK_CDN_ORIGIN))) sources.push('pack') else if (librariesPack) sources.push(librariesPack) else return { error: 'This player was set up without the library pack, so libraries=pack is not available here.' } diff --git a/packages/embed/site/index.html b/packages/embed/site/index.html index 2e1fd3c..64f78e7 100644 --- a/packages/embed/site/index.html +++ b/packages/embed/site/index.html @@ -16,7 +16,7 @@ Loading… - + diff --git a/packages/embed/tests/build.test.ts b/packages/embed/tests/build.test.ts index 9dc31c3..d34e5a5 100644 --- a/packages/embed/tests/build.test.ts +++ b/packages/embed/tests/build.test.ts @@ -48,22 +48,21 @@ describe('the default library source', () => { it('takes what the parameter takes, and none as no default', () => { expect(parseDefaultLibraries(null)).toBeNull() expect(parseDefaultLibraries('none')).toBeNull() - expect(parseDefaultLibraries(' pack hub ')).toBe('pack hub') - expect(parseDefaultLibraries('https://cdn.example.org/libraries.h5p hub')).toBe('https://cdn.example.org/libraries.h5p hub') + expect(parseDefaultLibraries(' https://cdn.example.org/libraries.h5p pack ')).toBe('https://cdn.example.org/libraries.h5p pack') }) it('refuses at build time what the page would refuse on every load', () => { const packages = ['https://cdn.example.org'] - // `hub` means `pack` now: the site's own copy, or jsDelivr's when the site has none. - expect(parseDefaultLibraries('hub', { packages })).toBe('hub') - expect(() => parseDefaultLibraries('hub', { packages, libraries: false })).toThrow(/does not list https:\/\/cdn\.jsdelivr\.net/) + // `pack` is the site's own copy, or jsDelivr's when the site has none, which the list must name. + expect(parseDefaultLibraries('pack', { packages })).toBe('pack') + expect(() => parseDefaultLibraries('pack', { packages, libraries: false })).toThrow(/no pack .* does not list https:\/\/cdn\.jsdelivr\.net/) expect(parseDefaultLibraries('pack', { packages: [...packages, 'https://cdn.jsdelivr.net'], libraries: false })).toBe('pack') expect(() => parseDefaultLibraries('https://elsewhere.example/l.h5p', { packages })).toThrow(/which --packages does not list/) - expect(() => parseDefaultLibraries('pack', { packages, libraries: false })).toThrow(/no pack/) - expect(parseDefaultLibraries('pack', { packages })).toBe('pack') - expect(() => parseDefaultLibraries('none hub')).toThrow(/stands alone/) + expect(() => parseDefaultLibraries('none pack')).toThrow(/stands alone/) expect(() => parseDefaultLibraries('http://cdn.example.org/l.h5p')).toThrow(/not an https URL/) - expect(() => parseDefaultLibraries('libraries')).toThrow(/not pack, hub, none or a URL/) + expect(() => parseDefaultLibraries('libraries')).toThrow(/not pack, none or a URL/) + // `hub` meant `pack` up to embed 0.8; now it is a word like any other. + expect(() => parseDefaultLibraries('hub')).toThrow(/not pack, none or a URL: hub/) }) }) diff --git a/packages/embed/tests/site.test.ts b/packages/embed/tests/site.test.ts index f7c308f..9e7c87b 100644 --- a/packages/embed/tests/site.test.ts +++ b/packages/embed/tests/site.test.ts @@ -170,7 +170,7 @@ beforeAll(async () => { pages.set('/open-de.html', snippet(open.origin, `src=${open.origin}/quiz.h5p&lang=de`)) pages.set('/locked.html', snippet(locked.origin, `src=${embedding.origin}/quiz.h5p`)) pages.set('/locked-elsewhere.html', snippet(locked.origin, `src=${open.origin}/quiz.h5p`)) - pages.set('/locked-hub.html', snippet(locked.origin, `src=${embedding.origin}/quiz.h5p&libraries=hub`)) + pages.set('/locked-pack.html', snippet(locked.origin, `src=${embedding.origin}/quiz.h5p&libraries=pack`)) pages.set('/fallback.html', snippet(fallback.origin, `src=${embedding.origin}/stripped.h5p`)) pages.set('/fallback-none.html', snippet(fallback.origin, `src=${embedding.origin}/stripped.h5p&libraries=none`)) pages.set('/lean-pack.html', snippet(lean.origin, `src=${embedding.origin}/quiz.h5p&libraries=pack`)) @@ -303,9 +303,9 @@ describe('a written site, framed from another site', () => { await page.close() }) - it('takes libraries=hub as the pack, this site\'s own copy', async () => { + it('takes libraries=pack as this site\'s own copy', async () => { const page = await browser.newPage() - await page.goto(`${embedding.origin}/locked-hub.html`) + await page.goto(`${embedding.origin}/locked-pack.html`) const frame = await playerFrame(page, locked.origin) await frame.waitForFunction(() => document.querySelector('h5p-player')!.getAttribute('libraries') === 'pack') expect(await frame.evaluate(() => document.querySelector('h5p-player')!.getAttribute('pack-base'))).toBe(`${locked.origin}/libraries/`) diff --git a/packages/player/src/h5p-offline-player.ts b/packages/player/src/h5p-offline-player.ts index 207e4b3..42a0075 100644 --- a/packages/player/src/h5p-offline-player.ts +++ b/packages/player/src/h5p-offline-player.ts @@ -1129,11 +1129,7 @@ export class H5PPlayerElement extends HTMLElement { * not count, so without the attribute it takes the pack: otherwise nothing could play it. */ private librarySources(): LibrarySource[] { - return parseLibrarySources(this.getAttribute('libraries')?.trim() || (this.untrusted ? 'pack' : null), () => { - if (hubNoticeShown) return - hubNoticeShown = true - console.info(': libraries="hub" now means libraries="pack", the library pack on jsDelivr.') - }) + return parseLibrarySources(this.getAttribute('libraries')?.trim() || (this.untrusted ? 'pack' : null)) } private async resolveSource( @@ -2014,9 +2010,6 @@ async function readPackIndex(base: string): Promise): HeldStatement { return data.type === 'xapi' diff --git a/packages/player/src/shared/constants.ts b/packages/player/src/shared/constants.ts index 1031b7b..0f6665f 100644 --- a/packages/player/src/shared/constants.ts +++ b/packages/player/src/shared/constants.ts @@ -13,9 +13,8 @@ export const LIBRARY_PACK_VERSION = '0.3.0' /** * `libraries="pack"` with no copy of the host's own: every H5P hub content type's runtime * libraries, one archive per library folder under `parts/`, on a CDN that answers with CORS. The - * worker carries this version's index (`PACK_INDEX`) and names the parts a package needs. `hub` - * means the same: the H5P hub itself is not asked, since the only host of it that sends CORS - * headers serves an outdated catalogue. + * worker carries this version's index (`PACK_INDEX`) and names the parts a package needs. The H5P + * hub itself is not asked: the only host of it that sends CORS headers serves an outdated catalogue. */ export const LIBRARY_PACK_BASE = `https://cdn.jsdelivr.net/npm/@missing-elements/h5p-libraries@${LIBRARY_PACK_VERSION}/parts/` diff --git a/packages/player/src/shared/library-sources.ts b/packages/player/src/shared/library-sources.ts index d8028c5..e766e1b 100644 --- a/packages/player/src/shared/library-sources.ts +++ b/packages/player/src/shared/library-sources.ts @@ -3,20 +3,13 @@ import type { LibrarySource } from './protocol' /** * The `libraries` attribute: `pack` or a URL, or several separated by whitespace, in order. * - * `hub` is read as `pack`. Up to 0.5 it asked the H5P hub for the content type's bundle; the one - * hub host that sends CORS headers serves a catalogue older than current exports need, so a page - * written for `hub` now gets the pack. `onHub` hears that it happened, for a console note. + * Any other word is a URL, relative to the page. Up to 0.10 `hub` was read as `pack`, the H5P hub + * having been replaced by the pack in 0.6; it went before 1.0, and a page that still writes it + * now fails on the address, named in the error, rather than playing something it did not name. */ -export function parseLibrarySources(value: string | null | undefined, onHub?: () => void): LibrarySource[] { +export function parseLibrarySources(value: string | null | undefined): LibrarySource[] { const words = value?.trim() ? value.trim().split(/\s+/) : [] - const sources = words.map((word): LibrarySource => { - if (word === 'pack') return 'pack' - if (word === 'hub') { - onHub?.() - return 'pack' - } - return { url: word } - }) - // `pack hub`, written when the pack came before the hub, would try the same bundle twice. + const sources = words.map((word): LibrarySource => (word === 'pack' ? 'pack' : { url: word })) + // `pack` written twice would try the same parts twice. return sources.filter((source, index) => source !== 'pack' || sources.indexOf('pack') === index) } diff --git a/packages/player/tests/browser/libraries.test.ts b/packages/player/tests/browser/libraries.test.ts index 441c9c7..bb06820 100644 --- a/packages/player/tests/browser/libraries.test.ts +++ b/packages/player/tests/browser/libraries.test.ts @@ -27,8 +27,8 @@ describe('supplying missing libraries', () => { it('takes several sources in order, asking the next only for what the one before left absent', async () => { // The first "bundle" is a content-only export, which supplies nothing; the second has the - // libraries. This is the shape the docs recommend — a bundle the site controls, then `hub` — - // with a fixture standing in for a bundle that lacks what the package needs. + // libraries: a fixture standing in for a bundle that lacks what the package needs, then one + // that has it. const player = await playWithLibraries(FIXTURES.needsLibraries, `${FIXTURES.contentOnly} ${FIXTURES.libraries}`) expect(player.state).toBe('ready') diff --git a/packages/player/tests/unit/library-sources.test.ts b/packages/player/tests/unit/library-sources.test.ts index c9fbb25..b94ccb4 100644 --- a/packages/player/tests/unit/library-sources.test.ts +++ b/packages/player/tests/unit/library-sources.test.ts @@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' import { LIBRARY_PACK_BASE, LIBRARY_PACK_VERSION } from '../../src/shared/constants' import { PACK_INDEX } from '../../src/shared/pack-index' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it } from 'vitest' import { parseLibrarySources } from '../../src/shared/library-sources' describe('the libraries attribute', () => { @@ -13,11 +13,12 @@ describe('the libraries attribute', () => { expect(parseLibrarySources(' /libraries.h5p pack ')).toEqual([{ url: '/libraries.h5p' }, 'pack']) }) - it('reads hub as pack, says so, and does not try the pack twice', () => { - const onHub = vi.fn() - expect(parseLibrarySources('hub', onHub)).toEqual(['pack']) - expect(onHub).toHaveBeenCalledTimes(1) - expect(parseLibrarySources('https://cdn.example.org/l.h5p pack hub')).toEqual([{ url: 'https://cdn.example.org/l.h5p' }, 'pack']) + it('does not try the pack twice', () => { + expect(parseLibrarySources('pack https://cdn.example.org/l.h5p pack')).toEqual(['pack', { url: 'https://cdn.example.org/l.h5p' }]) + }) + + it('reads hub as any other word, a URL, since it no longer means pack', () => { + expect(parseLibrarySources('hub')).toEqual([{ url: 'hub' }]) }) }) diff --git a/packages/verify/README.md b/packages/verify/README.md index 425b6f5..eea0492 100644 --- a/packages/verify/README.md +++ b/packages/verify/README.md @@ -56,7 +56,7 @@ come from the pack on jsDelivr unless `--libraries` names another; name a copy o | | | |---|---| | `--untrusted` | play it as a site plays a package it did not make, and report what the content check removed | -| `--libraries ` | where libraries come from, tried in order: `pack` (the library pack; `hub` means the same), a bundle's URL, a bundle on disk, or a copy of the pack's `parts/` folder, which plays as `pack`; the verifier serves what is on disk itself. With the pack, a library the package carries plays at the pack's newer version, as on a site with the pack, and the summary lists each under `refreshed from the pack` | +| `--libraries ` | where libraries come from, tried in order: `pack` (the library pack), a bundle's URL, a bundle on disk, or a copy of the pack's `parts/` folder, which plays as `pack`; the verifier serves what is on disk itself. With the pack, a library the package carries plays at the pack's newer version, as on a site with the pack, and the summary lists each under `refreshed from the pack` | | `--no-refresh-libraries` | play the libraries the package carries even where the pack has newer ones | | `--out ` | where the report and screenshot go | | `--browser ` | a Chromium-based browser; otherwise Chrome, then Edge, then Playwright's Chromium | diff --git a/packages/verify/bin/h5p-verify.mjs b/packages/verify/bin/h5p-verify.mjs index 3e64b8f..96e2a24 100755 --- a/packages/verify/bin/h5p-verify.mjs +++ b/packages/verify/bin/h5p-verify.mjs @@ -14,10 +14,10 @@ Options: default), and its content is checked against their semantics. What the check removed is reported as warnings --libraries where libraries come from, tried in order: pack (the published - library pack; hub means the same), a bundle's URL, a bundle on - disk, or a copy of the pack's parts/ folder, which keeps the check - offline. With the pack, a library the package carries plays at the - pack's newer version, as on a site with the pack + library pack), a bundle's URL, a bundle on disk, or a copy of the + pack's parts/ folder, which keeps the check offline. With the + pack, a library the package carries plays at the pack's newer + version, as on a site with the pack --no-refresh-libraries play the libraries the package carries even where the pack has newer --out where report.json and screenshot.png go (default: .verify/ beside the package) diff --git a/packages/verify/lib/page.html b/packages/verify/lib/page.html index 83200ec..46622cb 100644 --- a/packages/verify/lib/page.html +++ b/packages/verify/lib/page.html @@ -11,7 +11,7 @@ - +