@missing-elements/h5p-embed 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +6 -3
  2. package/package.json +2 -2
  3. package/site/embed.js +50 -21
package/README.md CHANGED
@@ -69,8 +69,11 @@ It works at a domain's root or under a path, over https. Serve `h5p-sw.js` with
69
69
  | `lang=<tag>` | The language of H5P's own buttons and dialogs, such as `de` or `pt-BR`. Default: English |
70
70
  | `xapi=<origin>` | Relay statements to the embedding page |
71
71
 
72
- A custom script and a learner's name cannot be set on the address. Opened unframed, the page asks
73
- before playing a package from another origin unless `--packages` names it.
72
+ A custom script and a learner's name cannot be set on the address. Opened unframed, the page plays
73
+ a package from another origin `untrusted` unless `--packages` names it: none of the package's own
74
+ code runs, its libraries come from this domain's pack or `--default-libraries` (never from the
75
+ address's `libraries=`), and a button offers to play it with its own scripts only when it needs
76
+ them.
74
77
 
75
78
  ## Messages
76
79
 
@@ -113,7 +116,7 @@ page's script, which reads the page's `<h5p-player>`, `#loader` and `#notice` (s
113
116
  `site/index.html` and `site/embed.css`). Its options: `librariesPack` (the URL `libraries=pack`
114
117
  names), `packages` (allowed origins, `null` for any), `defaultLibraries`, `runtime` (the
115
118
  `@missing-elements/h5p-runtime` export, for a bundled element) and `askInOwnFrame` (default
116
- `true`; `false` skips the click when a same-origin page frames it).
119
+ `true`; `false` plays a package from another origin as it is when a same-origin page frames it).
117
120
 
118
121
  ```js
119
122
  import '@missing-elements/h5p-offline-player'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@missing-elements/h5p-embed",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Self-hosted H5P embeds: writes the H5P embed page as a static site for a player domain of your own, then any site frames it with an iframe and one script line. No H5P server, no third-party service; optional allowlists for package hosts and embedding sites.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "@missing-elements/h5p-libraries": "^0.1.1",
47
- "@missing-elements/h5p-offline-player": "^0.6.2",
47
+ "@missing-elements/h5p-offline-player": "^0.7.0",
48
48
  "@missing-elements/h5p-runtime": "^0.1.2"
49
49
  },
50
50
  "devDependencies": {
package/site/embed.js CHANGED
@@ -26,15 +26,17 @@ const PACK_CDN_ORIGIN = 'https://cdn.jsdelivr.net'
26
26
  * @param {string | null} [options.librariesPack] the URL of a copy of `@missing-elements/h5p-libraries`
27
27
  * on this site, which `libraries=pack` names; without one, `pack` is the element's copy on jsDelivr
28
28
  * @param {string[] | null} [options.packages] the origins packages and library bundles may come
29
- * from, besides this page's own; `null` plays any, asking first when the storage is this origin's
29
+ * from, besides this page's own; `null` plays any, one from another origin `untrusted` when the
30
+ * storage is this origin's
30
31
  * @param {string | null} [options.defaultLibraries] the `libraries` value for an address that has
31
32
  * none — `pack` or URLs, as the parameter — so a snippet without `&libraries=` still plays
32
33
  * an export that carries no libraries; `&libraries=none` turns it off for one address
33
34
  * @param {object | null} [options.runtime] the `runtime` export of `@missing-elements/h5p-runtime`,
34
35
  * for a page that bundles the element; without it the element looks for `frame-assets/` beside itself
35
- * @param {boolean} [options.askInOwnFrame] whether a package from another origin waits for a click
36
- * when this page is framed by a page of its own origin, whose storage it shares. A site whose
37
- * own preview frames the page for a package the visitor just chose passes `false`
36
+ * @param {boolean} [options.askInOwnFrame] whether a package from another origin plays `untrusted`,
37
+ * with a click to play it with its own code when it needs that, when this page is framed by a
38
+ * page of its own origin, whose storage it shares. A site whose own preview frames the page for
39
+ * a package the visitor just chose passes `false`
38
40
  */
39
41
  export function startEmbed({ librariesPack = null, packages = null, defaultLibraries = null, runtime = null, askInOwnFrame = true } = {}) {
40
42
  const params = new URLSearchParams(location.search)
@@ -166,8 +168,16 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
166
168
 
167
169
  /* ---------------------------------------------------------------- errors, and Safari */
168
170
 
171
+ /** Asks whether to play an untrusted package with its own code; set when one is played so. */
172
+ let askToTrust = null
173
+
169
174
  player.addEventListener('error', (event) => {
170
- const { code, message } = event.detail
175
+ const { code, message, missingLibraries } = event.detail
176
+ // Played untrusted, it cannot do without its own copy of a library: a click decides.
177
+ if (missingLibraries?.carried && player.untrusted && askToTrust) {
178
+ askToTrust()
179
+ return
180
+ }
171
181
  if (code === 'no-worker' && framed) {
172
182
  // Detected, not sniffed: a browser, an in-app one or a page that is not https may give a
173
183
  // frame no Service Worker, and the player cannot run without one. Safari does allow it.
@@ -311,25 +321,44 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
311
321
  return
312
322
  }
313
323
 
314
- // A host on the list was vouched for when the site was set up; anything else from another
315
- // origin waits for a click when it would share this origin's storage.
324
+ // A host on the list was vouched for when the site was set up, and in another site's frame the
325
+ // storage is that site's own: those play as they are.
316
326
  if (origin === location.origin || allowed || !sharesOriginStorage()) {
317
327
  start(src, sources.value)
318
328
  return
319
329
  }
320
- loader.hidden = true
330
+
331
+ // Anything else from another origin would share this origin's storage. It plays untrusted, as
332
+ // data with the library pack's code and none of its own, and waits for a click that names the
333
+ // host only when it cannot play without its own, or there is no pack to play it with.
321
334
  const host = new URL(src, location.href).host || origin
322
- const button = document.createElement('button')
323
- button.type = 'button'
324
- button.textContent = 'Open the package'
325
- button.addEventListener('click', () => {
326
- say('')
327
- loader.hidden = false
328
- start(src, sources.value)
329
- })
330
- say(
331
- `This link opens a package from ${host}. A package runs its own scripts on this site, and they ` +
332
- 'can read what other packages saved in this browser. Open it only if you trust that site.'
333
- )
334
- notice.append(' ', button)
335
+ askToTrust = () => {
336
+ loader.hidden = true
337
+ const button = document.createElement('button')
338
+ button.type = 'button'
339
+ button.textContent = 'Open with its own scripts'
340
+ button.addEventListener('click', () => {
341
+ say('')
342
+ loader.hidden = false
343
+ player.untrusted = false
344
+ player.removeAttribute('src')
345
+ start(src, sources.value)
346
+ })
347
+ say(
348
+ `This package from ${host} plays only with its own scripts. They would run on this site and ` +
349
+ 'could read what other packages saved in this browser. Open it only if you trust that site.'
350
+ )
351
+ notice.append(' ', button)
352
+ }
353
+ // The address's `libraries=` is the link-writer's as much as its `src`, and a bundle's code runs
354
+ // as the host's own: an untrusted package plays with this site's sources alone, the page's
355
+ // default or its pack, and the address's only once the click has trusted it.
356
+ const own = defaultLibraries?.trim() ? librarySources(defaultLibraries.trim()).value : ''
357
+ const pack = own || librarySources('pack').value
358
+ if (!pack) {
359
+ askToTrust()
360
+ return
361
+ }
362
+ player.untrusted = true
363
+ start(src, pack)
335
364
  }