@missing-elements/h5p-embed 0.3.1 → 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.
- package/README.md +7 -3
- package/package.json +3 -3
- package/site/embed.js +57 -25
package/README.md
CHANGED
|
@@ -66,10 +66,14 @@ It works at a domain's root or under a path, over https. Serve `h5p-sw.js` with
|
|
|
66
66
|
| `preload=auto` | Start fetching media at once |
|
|
67
67
|
| `activity-id=<IRI>` | The xAPI object id, instead of the package URL |
|
|
68
68
|
| `custom-css=<url>` | A stylesheet loaded into the content |
|
|
69
|
+
| `lang=<tag>` | The language of H5P's own buttons and dialogs, such as `de` or `pt-BR`. Default: English |
|
|
69
70
|
| `xapi=<origin>` | Relay statements to the embedding page |
|
|
70
71
|
|
|
71
|
-
A custom script and a learner's name cannot be set on the address. Opened unframed, the page
|
|
72
|
-
|
|
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.
|
|
73
77
|
|
|
74
78
|
## Messages
|
|
75
79
|
|
|
@@ -112,7 +116,7 @@ page's script, which reads the page's `<h5p-player>`, `#loader` and `#notice` (s
|
|
|
112
116
|
`site/index.html` and `site/embed.css`). Its options: `librariesPack` (the URL `libraries=pack`
|
|
113
117
|
names), `packages` (allowed origins, `null` for any), `defaultLibraries`, `runtime` (the
|
|
114
118
|
`@missing-elements/h5p-runtime` export, for a bundled element) and `askInOwnFrame` (default
|
|
115
|
-
`true`; `false`
|
|
119
|
+
`true`; `false` plays a package from another origin as it is when a same-origin page frames it).
|
|
116
120
|
|
|
117
121
|
```js
|
|
118
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
|
+
"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,8 +44,8 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@missing-elements/h5p-libraries": "^0.1.1",
|
|
47
|
-
"@missing-elements/h5p-offline-player": "^0.
|
|
48
|
-
"@missing-elements/h5p-runtime": "^0.1.
|
|
47
|
+
"@missing-elements/h5p-offline-player": "^0.7.0",
|
|
48
|
+
"@missing-elements/h5p-runtime": "^0.1.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^26.6.4",
|
package/site/embed.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* ?src=<package url>[&libraries=pack|<url> …|none][&preload=auto][&xapi=<parent origin>]
|
|
9
9
|
* [&frame][©right][&export][&icon][&reporting][&fullscreen=off]
|
|
10
|
-
* [&activity-id=<IRI>][&custom-css=<stylesheet url>]
|
|
10
|
+
* [&activity-id=<IRI>][&custom-css=<stylesheet url>][&lang=<language tag>]
|
|
11
11
|
*
|
|
12
12
|
* Upward it speaks H5P's own resizer protocol — the `hello` / `resize` exchange that h5p.org's
|
|
13
13
|
* embed code and its `h5p-resizer.js` use — so a page that already resizes h5p.org iframes
|
|
@@ -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,
|
|
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
|
|
36
|
-
*
|
|
37
|
-
* own
|
|
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.
|
|
@@ -243,9 +253,10 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
243
253
|
* The element's display options, by their attribute names, for the embedding page to ask for:
|
|
244
254
|
* `&frame©right&export` shows H5P's action bar with those buttons, `&fullscreen=off` takes
|
|
245
255
|
* that one away, `&activity-id=` names the statements' object and `&custom-css=` restyles the
|
|
246
|
-
* content to the embedding site's taste.
|
|
247
|
-
*
|
|
248
|
-
*
|
|
256
|
+
* content to the embedding site's taste. `&lang=` is the embedding page's language, which this
|
|
257
|
+
* page cannot read across origins: H5P's own buttons and dialogs follow it. Not `custom-js`,
|
|
258
|
+
* `embed-code` or `user`: a script is a capability on this origin that a link should not hand
|
|
259
|
+
* out, the embed is the embed, and a learner's name has no place in a URL.
|
|
249
260
|
*/
|
|
250
261
|
const applyOptions = () => {
|
|
251
262
|
for (const name of ['frame', 'copyright', 'export', 'icon', 'reporting']) {
|
|
@@ -256,6 +267,8 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
256
267
|
const value = params.get(name)?.trim()
|
|
257
268
|
if (value) player.setAttribute(name, value)
|
|
258
269
|
}
|
|
270
|
+
const lang = params.get('lang')?.trim().replaceAll('_', '-')
|
|
271
|
+
if (lang && /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/.test(lang)) player.setAttribute('lang', lang)
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
const start = (value, libraries) => {
|
|
@@ -308,25 +321,44 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
308
321
|
return
|
|
309
322
|
}
|
|
310
323
|
|
|
311
|
-
// A host on the list was vouched for when the site was set up
|
|
312
|
-
//
|
|
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.
|
|
313
326
|
if (origin === location.origin || allowed || !sharesOriginStorage()) {
|
|
314
327
|
start(src, sources.value)
|
|
315
328
|
return
|
|
316
329
|
}
|
|
317
|
-
|
|
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.
|
|
318
334
|
const host = new URL(src, location.href).host || origin
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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)
|
|
332
364
|
}
|