@missing-elements/h5p-embed 0.6.2 → 0.8.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 +29 -13
- package/bin/h5p-embed.mjs +4 -2
- package/lib/build.mjs +93 -10
- package/package.json +7 -7
- package/site/embed.js +34 -17
- package/site/main.js +2 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ a subdomain shares your site's cookies.
|
|
|
30
30
|
| `--packages <origins>` | Play packages and fetch library bundles only from these origins (the site's own is always allowed). Add `https://cdn.jsdelivr.net` for `libraries=pack` on a site built with `--no-libraries`. Default: any https host |
|
|
31
31
|
| `--ancestors <origins>` | Only these sites may frame the page (`frame-ancestors`, header only). Default: any site |
|
|
32
32
|
| `--default-libraries <sources>` | The `libraries` value for addresses that name none: `pack` or URLs. Checked against `--packages`. Default: none, so exports without libraries are refused unless the address asks |
|
|
33
|
-
| `--no-libraries` | Leave out the 13.
|
|
33
|
+
| `--no-libraries` | Leave out the library pack, 13.8 MB in `libraries/`, one archive per library, of which a learner's browser fetches only those a package needs; `libraries=pack` then fetches them from jsDelivr, where allowed |
|
|
34
34
|
| `--force` | Write into a non-empty folder, replacing only this tool's files |
|
|
35
35
|
| `-h`, `--help` / `-v`, `--version` | |
|
|
36
36
|
|
|
@@ -47,21 +47,35 @@ npx @missing-elements/h5p-embed h5p-player \
|
|
|
47
47
|
|
|
48
48
|
## Hosting
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Any static host, over https, at a domain's root or under a path. The page carries its own
|
|
51
|
+
policy in a `<meta>` tag, so it is protected wherever it is served. The headers — the same
|
|
52
|
+
policy with `frame-ancestors`, and the caching — are written beside it in the form each kind of
|
|
53
|
+
host reads; use the one yours does and ignore the rest:
|
|
54
|
+
|
|
55
|
+
| Host | File |
|
|
56
|
+
|---|---|
|
|
57
|
+
| A server of your own — Hetzner, IONOS, OVH, Scaleway, a university's machine | `Caddyfile` (run it as it is; Caddy fetches the certificate), `nginx.conf` (include it in the server block), `.htaccess` (Apache with `mod_headers`) |
|
|
58
|
+
| Netlify, Cloudflare Pages | `_headers` |
|
|
59
|
+
| Vercel | `vercel.json` |
|
|
60
|
+
| GitHub Pages, S3 and other object storage | none can set headers: the page's `<meta>` policy applies, `--ancestors` does not, and `h5p-sw.js` is cached by the host's default |
|
|
61
|
+
|
|
62
|
+
The headers themselves, for a host not listed:
|
|
63
|
+
|
|
64
|
+
| Path | Headers |
|
|
51
65
|
|---|---|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
66
|
+
| every path | `Content-Security-Policy` (the policy the command prints, with `frame-ancestors` from `--ancestors`), `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin` |
|
|
67
|
+
| `/h5p-sw.js` | `Cache-Control: no-cache`, so an update reaches learners on their next visit |
|
|
68
|
+
| `/frame-assets/*`, `/resizer.js`, `/libraries/*` | `Cache-Control: public, max-age=3600, stale-while-revalidate=86400` |
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
`Cache-Control: no-cache`. To update, run the command again with `--force` and redeploy.
|
|
70
|
+
To update, run the command again with `--force` and redeploy.
|
|
58
71
|
|
|
59
72
|
## The address
|
|
60
73
|
|
|
61
74
|
| Parameter | Effect |
|
|
62
75
|
|---|---|
|
|
63
76
|
| `src=<url>` | The package, required. Encode `&`, `#`, `+`, `%` and spaces |
|
|
64
|
-
| `libraries=pack`, `<url>` or `none` |
|
|
77
|
+
| `libraries=pack`, `<url>` 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` |
|
|
78
|
+
| `refresh-libraries=off` | Play the libraries a package carries as exported; `pack` then only fills gaps |
|
|
65
79
|
| `frame`, `copyright`, `export`, `icon`, `reporting` | H5P's action bar and its buttons |
|
|
66
80
|
| `fullscreen=off` | No fullscreen button |
|
|
67
81
|
| `preload=auto` | Start fetching media at once |
|
|
@@ -99,7 +113,7 @@ Whatever `xapi=` says, the frame also posts to its parent:
|
|
|
99
113
|
{ context: 'h5p-offline-player', action: 'report',
|
|
100
114
|
source, // { type: 'range-http' | 'chunked' | 'file', size }
|
|
101
115
|
metadata, // { title, license, licenseVersion, authors, mainLibrary }, from h5p.json; show as text
|
|
102
|
-
libraryBundle, // { url, origin, fromCache }, or null
|
|
116
|
+
libraryBundle, // { url, origin, fromCache, refreshed? }, or null; refreshed: [{ library, from, to }]
|
|
103
117
|
elapsedMs }
|
|
104
118
|
{ context: 'h5p-offline-player', action: 'error', code, message } // code 'refused', 'no-src' or the load's
|
|
105
119
|
```
|
|
@@ -114,19 +128,21 @@ await buildSite({ out: 'public/player', packages: ['https://cdn.example.org'] })
|
|
|
114
128
|
`buildSite` takes `out`, `libraries` (default `true`), `packages`, `ancestors`, `defaultLibraries`
|
|
115
129
|
and `force`, as the flags. `@missing-elements/h5p-embed/embed.js` exports `startEmbed()`, the
|
|
116
130
|
page's script, which reads the page's `<h5p-player>`, `#loader` and `#notice` (see
|
|
117
|
-
`site/index.html` and `site/embed.css`). Its options: `
|
|
118
|
-
|
|
131
|
+
`site/index.html` and `site/embed.css`). Its options: `pack` (the `@missing-elements/h5p-libraries`
|
|
132
|
+
export, which `libraries=pack` then plays from) or `packBase` (the folder of a copy of its
|
|
133
|
+
`parts/`), `packages` (allowed origins, `null` for any), `defaultLibraries`, `runtime` (the
|
|
119
134
|
`@missing-elements/h5p-runtime` export, for a bundled element) and `askInOwnFrame` (default
|
|
120
135
|
`true`; `false` plays a package from another origin as it is when a same-origin page frames it).
|
|
136
|
+
`librariesPack`, the URL of a whole `libraries.h5p`, still works, as a bundle that only fills gaps.
|
|
121
137
|
|
|
122
138
|
```js
|
|
123
139
|
import '@missing-elements/h5p-offline-player'
|
|
124
140
|
import { runtime } from '@missing-elements/h5p-runtime'
|
|
125
|
-
import
|
|
141
|
+
import { pack } from '@missing-elements/h5p-libraries'
|
|
126
142
|
import '@missing-elements/h5p-embed/embed.css'
|
|
127
143
|
import { startEmbed } from '@missing-elements/h5p-embed/embed.js'
|
|
128
144
|
|
|
129
|
-
startEmbed({ runtime,
|
|
145
|
+
startEmbed({ runtime, pack, defaultLibraries: 'pack' })
|
|
130
146
|
```
|
|
131
147
|
|
|
132
148
|
## Privacy
|
package/bin/h5p-embed.mjs
CHANGED
|
@@ -84,8 +84,10 @@ subdomain of your site — then embed a package:
|
|
|
84
84
|
style="width: 100%; border: 0"></iframe>
|
|
85
85
|
<script src="https://<player domain>/resizer.js"></script>
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
The page carries its policy; the headers beside it are written for five kinds of host:
|
|
88
|
+
Caddyfile, nginx.conf and .htaccess for a server of your own, _headers for Netlify and
|
|
89
|
+
Cloudflare Pages, vercel.json for Vercel. Object storage and GitHub Pages serve the page's own
|
|
90
|
+
policy and no headers${site.ancestors ? ' — there --ancestors has no effect, since only a header\ncan carry it' : ''}.`)
|
|
89
91
|
} catch (error) {
|
|
90
92
|
if (error instanceof EmbedError) {
|
|
91
93
|
console.error(`h5p-embed: ${error.message}`)
|
package/lib/build.mjs
CHANGED
|
@@ -7,8 +7,9 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
* Writes the embed page as a static site: one folder, deployable to any static host, that a site
|
|
8
8
|
* frames from a player domain of its own. Everything comes from installed packages — the element
|
|
9
9
|
* and its workers from the player's `dist/`, the H5P runtime from its own package as
|
|
10
|
-
* `frame-assets/`, the library pack from `@missing-elements/h5p-libraries`
|
|
11
|
-
* the ones this package was published with, and nothing is
|
|
10
|
+
* `frame-assets/`, the library pack's parts from `@missing-elements/h5p-libraries` as
|
|
11
|
+
* `libraries/` — so the versions are the ones this package was published with, and nothing is
|
|
12
|
+
* fetched.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export class EmbedError extends Error {}
|
|
@@ -52,7 +53,7 @@ export function parseOrigin(value) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/** Where the element's `libraries="pack"` fetches from when a site carries no copy of its own. */
|
|
55
|
-
|
|
56
|
+
const PACK_CDN_ORIGIN = 'https://cdn.jsdelivr.net'
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* A `libraries` value for addresses that name none, checked against the site it goes into: the
|
|
@@ -149,7 +150,7 @@ function headerRules(csp) {
|
|
|
149
150
|
{ path: '/h5p-sw.js', headers: { 'Cache-Control': 'no-cache' } },
|
|
150
151
|
{ path: '/frame-assets/*', headers: { 'Cache-Control': revalidate } },
|
|
151
152
|
{ path: '/resizer.js', headers: { 'Cache-Control': revalidate } },
|
|
152
|
-
{ path: '/libraries
|
|
153
|
+
{ path: '/libraries/*', headers: { 'Cache-Control': revalidate } }
|
|
153
154
|
]
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -168,19 +169,96 @@ function vercelConfig(rules) {
|
|
|
168
169
|
) + '\n'
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
/*
|
|
173
|
+
* The three server formats below are for a host of one's own — the European hosts a data
|
|
174
|
+
* protection officer accepts have no `_headers` convention, and a university's Apache or an
|
|
175
|
+
* nginx on a Hetzner box is where the embed page usually ends up. Each is written from the same
|
|
176
|
+
* rules as the two files above, so they cannot disagree. Values are quoted with double quotes
|
|
177
|
+
* in every format, which is safe because the policy carries only single quotes.
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
/** The rule for every path, and the ones for a single path or prefix. */
|
|
181
|
+
function splitRules(rules) {
|
|
182
|
+
const all = rules.find((rule) => rule.path === '/*')
|
|
183
|
+
return { all, specific: rules.filter((rule) => rule !== all) }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Caddy's `Caddyfile`: a site block to run as it is, or to paste into one. */
|
|
187
|
+
function caddyfile(rules) {
|
|
188
|
+
const { all, specific } = splitRules(rules)
|
|
189
|
+
const lines = [
|
|
190
|
+
'# Written by h5p-embed. Replace the domain and the root, then `caddy run` beside this file,',
|
|
191
|
+
'# or paste the block into your Caddyfile. Caddy fetches the certificate itself.',
|
|
192
|
+
'h5p-player.example.net {',
|
|
193
|
+
'\troot * /srv/h5p-player',
|
|
194
|
+
'\tfile_server',
|
|
195
|
+
...Object.entries(all.headers).map(([key, value]) => `\theader ${key} "${value}"`),
|
|
196
|
+
...specific.flatMap(({ path, headers }) => Object.entries(headers).map(([key, value]) => `\theader ${path} ${key} "${value}"`)),
|
|
197
|
+
'}'
|
|
198
|
+
]
|
|
199
|
+
return lines.join('\n') + '\n'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* nginx: a file to `include` inside the `server` block that serves the folder. nginx's
|
|
204
|
+
* `add_header` in a `location` replaces every `add_header` inherited from the server level
|
|
205
|
+
* rather than adding to it, so each location repeats the headers that apply everywhere.
|
|
206
|
+
*/
|
|
207
|
+
function nginxConf(rules) {
|
|
208
|
+
const { all, specific } = splitRules(rules)
|
|
209
|
+
const header = ([key, value]) => `add_header ${key} "${value}" always;`
|
|
210
|
+
const matcher = (path) => (path.endsWith('/*') ? `^~ ${path.slice(0, -1)}` : `= ${path}`)
|
|
211
|
+
const lines = [
|
|
212
|
+
'# Written by h5p-embed. Include this file inside the server block that serves the folder:',
|
|
213
|
+
'# server { listen 443 ssl; server_name h5p-player.example.net; root /srv/h5p-player; include /srv/h5p-player/nginx.conf; }',
|
|
214
|
+
'# A location repeats the headers set for every path: in nginx an add_header inside a location',
|
|
215
|
+
'# replaces the inherited ones instead of adding to them.',
|
|
216
|
+
...Object.entries(all.headers).map(header),
|
|
217
|
+
...specific.map(
|
|
218
|
+
({ path, headers }) =>
|
|
219
|
+
`location ${matcher(path)} {\n${[...Object.entries(all.headers), ...Object.entries(headers)].map((entry) => `\t${header(entry)}`).join('\n')}\n}`
|
|
220
|
+
)
|
|
221
|
+
]
|
|
222
|
+
return lines.join('\n') + '\n'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Apache's `.htaccess`, for a folder under a vhost that allows it. */
|
|
226
|
+
function htaccess(rules) {
|
|
227
|
+
const { all, specific } = splitRules(rules)
|
|
228
|
+
const header = ([key, value]) => `Header always set ${key} "${value}"`
|
|
229
|
+
const block = ({ path, headers }) => {
|
|
230
|
+
const body = Object.entries(headers).map((entry) => `\t${header(entry)}`).join('\n')
|
|
231
|
+
// <Files> matches a name in any directory; a prefix needs the request URI, with no anchor at
|
|
232
|
+
// the start, so that the site works under a path as well as at a root.
|
|
233
|
+
return path.endsWith('/*')
|
|
234
|
+
? `<If "%{REQUEST_URI} =~ m#${path.slice(0, -1)}#">\n${body}\n</If>`
|
|
235
|
+
: `<Files "${path.slice(1)}">\n${body}\n</Files>`
|
|
236
|
+
}
|
|
237
|
+
const lines = [
|
|
238
|
+
'# Written by h5p-embed. Needs mod_headers, and AllowOverride FileInfo on the folder; or paste',
|
|
239
|
+
'# the directives into the virtual host.',
|
|
240
|
+
...Object.entries(all.headers).map(header),
|
|
241
|
+
...specific.map(block)
|
|
242
|
+
]
|
|
243
|
+
return lines.join('\n') + '\n'
|
|
244
|
+
}
|
|
245
|
+
|
|
171
246
|
function notice({ version, libraries }) {
|
|
172
247
|
return `This folder is the H5P embed page, written by @missing-elements/h5p-embed.
|
|
173
248
|
|
|
174
249
|
index.html, main.js, embed.js, embed.css, resizer.js, config.js
|
|
175
250
|
the embed page and the sizing script: MIT
|
|
251
|
+
Caddyfile, nginx.conf, .htaccess, _headers, vercel.json
|
|
252
|
+
the same headers for five kinds of host; use the one yours reads
|
|
176
253
|
h5p-player.js, h5p-sw.js, h5p-jobs.js
|
|
177
254
|
@missing-elements/h5p-offline-player ${version}: MIT. The two workers also
|
|
178
255
|
carry zip.js, BSD-3-Clause, its licence in each file's header.
|
|
179
256
|
frame-assets/ @missing-elements/h5p-runtime, the H5P core runtime: GPL-3.0-only. Its
|
|
180
257
|
LICENSE.txt and NOTICE.txt say what it is and where its source is; keep them
|
|
181
258
|
with it.
|
|
182
|
-
${libraries ? ` libraries
|
|
183
|
-
own licence, listed in libraries.txt.
|
|
259
|
+
${libraries ? ` libraries/ @missing-elements/h5p-libraries, the H5P hub's libraries, one archive per
|
|
260
|
+
library and index.json, each under its own licence, listed in libraries.txt.
|
|
261
|
+
The player downloads only the ones a package needs.
|
|
184
262
|
` : ''}
|
|
185
263
|
Serve it from a domain that holds nothing else, and frame it:
|
|
186
264
|
|
|
@@ -211,7 +289,7 @@ async function occupied(dir) {
|
|
|
211
289
|
*
|
|
212
290
|
* @param {object} options
|
|
213
291
|
* @param {string} options.out
|
|
214
|
-
* @param {boolean} [options.libraries] include the library pack, for `libraries=pack` (default true)
|
|
292
|
+
* @param {boolean} [options.libraries] include the library pack's parts, for `libraries=pack` (default true)
|
|
215
293
|
* @param {string[] | string | null} [options.packages] the only origins packages may come from
|
|
216
294
|
* @param {string[] | string | null} [options.ancestors] the only origins that may frame the page
|
|
217
295
|
* @param {string | null} [options.defaultLibraries] the `libraries` value for addresses that name none
|
|
@@ -232,7 +310,7 @@ export async function buildSite({ out, libraries = true, packages = null, ancest
|
|
|
232
310
|
|
|
233
311
|
const player = dirname(installed('@missing-elements/h5p-offline-player/dist/h5p-player.js', 'Run `pnpm build` in the workspace, or reinstall this package.'))
|
|
234
312
|
const runtime = dirname(installed('@missing-elements/h5p-runtime/dist/h5p.css', 'Run `pnpm build` in the workspace, or reinstall this package.'))
|
|
235
|
-
const pack = libraries ? installed('@missing-elements/h5p-libraries/
|
|
313
|
+
const pack = libraries ? dirname(installed('@missing-elements/h5p-libraries/parts/index.json', 'Reinstall this package, or pass --no-libraries.')) : null
|
|
236
314
|
const version = (await readFile(join(player, 'VERSION'), 'utf8').catch(() => 'unknown')).trim()
|
|
237
315
|
|
|
238
316
|
await mkdir(target, { recursive: true })
|
|
@@ -240,11 +318,13 @@ export async function buildSite({ out, libraries = true, packages = null, ancest
|
|
|
240
318
|
for (const name of PLAYER_FILES) await cp(join(player, name), join(target, name))
|
|
241
319
|
await rm(join(target, 'frame-assets'), { recursive: true, force: true })
|
|
242
320
|
await cp(runtime, join(target, 'frame-assets'), { recursive: true })
|
|
321
|
+
// A site written before the pack was split carried it whole; that file is gone either way.
|
|
322
|
+
await rm(join(target, 'libraries.h5p'), { force: true })
|
|
323
|
+
await rm(join(target, 'libraries'), { recursive: true, force: true })
|
|
243
324
|
if (pack) {
|
|
244
|
-
await cp(pack, join(target, 'libraries
|
|
325
|
+
await cp(pack, join(target, 'libraries'), { recursive: true })
|
|
245
326
|
await cp(join(dirname(pack), 'libraries.txt'), join(target, 'libraries.txt'))
|
|
246
327
|
} else {
|
|
247
|
-
await rm(join(target, 'libraries.h5p'), { force: true })
|
|
248
328
|
await rm(join(target, 'libraries.txt'), { force: true })
|
|
249
329
|
}
|
|
250
330
|
|
|
@@ -259,6 +339,9 @@ export async function buildSite({ out, libraries = true, packages = null, ancest
|
|
|
259
339
|
const rules = headerRules(csp)
|
|
260
340
|
await writeFile(join(target, '_headers'), netlifyHeaders(rules))
|
|
261
341
|
await writeFile(join(target, 'vercel.json'), vercelConfig(rules))
|
|
342
|
+
await writeFile(join(target, 'Caddyfile'), caddyfile(rules))
|
|
343
|
+
await writeFile(join(target, 'nginx.conf'), nginxConf(rules))
|
|
344
|
+
await writeFile(join(target, '.htaccess'), htaccess(rules))
|
|
262
345
|
await writeFile(join(target, 'NOTICE.txt'), notice({ version, libraries: Boolean(pack) }))
|
|
263
346
|
|
|
264
347
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@missing-elements/h5p-embed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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": {
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"cli"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@missing-elements/h5p-
|
|
47
|
-
"@missing-elements/h5p-
|
|
48
|
-
"@missing-elements/h5p-offline-player": "^0.
|
|
46
|
+
"@missing-elements/h5p-libraries": "^0.3.0",
|
|
47
|
+
"@missing-elements/h5p-runtime": "^0.2.4",
|
|
48
|
+
"@missing-elements/h5p-offline-player": "^0.10.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^26.6.
|
|
51
|
+
"@types/node": "^26.6.5",
|
|
52
52
|
"@zip.js/zip.js": "^2.23.0",
|
|
53
|
-
"playwright": "^1.
|
|
53
|
+
"playwright": "^1.64.0",
|
|
54
54
|
"typescript": "^7.0.2",
|
|
55
55
|
"vitest": "^5.0.3"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
|
-
"node": ">=
|
|
58
|
+
"node": ">=22"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"test": "pnpm --filter @missing-elements/h5p-offline-player build && vitest run",
|
package/site/embed.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* `h5p-embed` was told when it wrote the site, and the demo's `/embed` with the demo's copy of the
|
|
6
6
|
* library pack.
|
|
7
7
|
*
|
|
8
|
-
* ?src=<package url>[&libraries=pack|<url> …|none][&
|
|
9
|
-
* [&frame][©right][&export][&icon][&reporting][&fullscreen=off]
|
|
8
|
+
* ?src=<package url>[&libraries=pack|<url> …|none][&refresh-libraries=off][&preload=auto]
|
|
9
|
+
* [&xapi=<parent origin>][&frame][©right][&export][&icon][&reporting][&fullscreen=off]
|
|
10
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
|
|
@@ -23,8 +23,12 @@ const PACK_CDN_ORIGIN = 'https://cdn.jsdelivr.net'
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @param {object} [options]
|
|
26
|
-
* @param {string | null} [options.
|
|
27
|
-
*
|
|
26
|
+
* @param {string | null} [options.packBase] the folder of this site's copy of the library pack's
|
|
27
|
+
* parts (`parts/` of `@missing-elements/h5p-libraries`), which `libraries=pack` plays from
|
|
28
|
+
* @param {{ libraries: object[] } | null} [options.pack] the same, as the `pack` export a bundler
|
|
29
|
+
* resolves; it wins over `packBase`. Without either, `pack` is the element's copy on jsDelivr
|
|
30
|
+
* @param {string | null} [options.librariesPack] the URL of a whole `libraries.h5p` on this site,
|
|
31
|
+
* for a page written before the pack was split: played as any bundle, with no refresh
|
|
28
32
|
* @param {string[] | null} [options.packages] the origins packages and library bundles may come
|
|
29
33
|
* from, besides this page's own; `null` plays any, one from another origin `untrusted` when the
|
|
30
34
|
* storage is this origin's
|
|
@@ -38,11 +42,15 @@ const PACK_CDN_ORIGIN = 'https://cdn.jsdelivr.net'
|
|
|
38
42
|
* page of its own origin, whose storage it shares. A site whose own preview frames the page for
|
|
39
43
|
* a package the visitor just chose passes `false`
|
|
40
44
|
*/
|
|
41
|
-
export function startEmbed({ librariesPack = null, packages = null, defaultLibraries = null, runtime = null, askInOwnFrame = true } = {}) {
|
|
45
|
+
export function startEmbed({ packBase = null, pack = null, librariesPack = null, packages = null, defaultLibraries = null, runtime = null, askInOwnFrame = true } = {}) {
|
|
42
46
|
const params = new URLSearchParams(location.search)
|
|
43
47
|
const player = document.querySelector('h5p-player')
|
|
44
|
-
// Before `src`: the element resolves the runtime's files
|
|
48
|
+
// Before `src`: the element resolves the runtime's files, and reads where the pack is, when a
|
|
49
|
+
// package is set.
|
|
45
50
|
if (runtime) player.runtime = runtime
|
|
51
|
+
if (pack) player.pack = pack
|
|
52
|
+
else if (packBase) player.setAttribute('pack-base', packBase)
|
|
53
|
+
const ownPack = Boolean(pack || packBase)
|
|
46
54
|
const notice = document.querySelector('#notice')
|
|
47
55
|
const loader = document.querySelector('#loader')
|
|
48
56
|
const framed = window.parent !== window
|
|
@@ -75,7 +83,11 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
75
83
|
|
|
76
84
|
/* ---------------------------------------------------------------- sizing, upward */
|
|
77
85
|
|
|
78
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* A message to the page that frames this one: the height in the shape h5p-resizer.js expects,
|
|
88
|
+
* and `report`, `error` and `xapi` in this page's own. To any parent unless a target is named,
|
|
89
|
+
* since nothing in the height is secret; the xAPI relay names the origin that opted in.
|
|
90
|
+
*/
|
|
79
91
|
const post = (message, target = '*') => {
|
|
80
92
|
if (framed) window.parent.postMessage(message, target)
|
|
81
93
|
}
|
|
@@ -233,12 +245,13 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
233
245
|
if (value === 'none') return { value: '' }
|
|
234
246
|
for (const token of value.split(/\s+/).filter(Boolean)) {
|
|
235
247
|
if (token === 'pack' || token === 'hub') {
|
|
236
|
-
// This site's copy of the pack
|
|
237
|
-
// jsDelivr, where that may be reached, so a snippet
|
|
238
|
-
// a rebuild with --no-libraries. `hub` has meant
|
|
239
|
-
// host that answers a browser serves a
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
// This site's copy of the pack, which the element was pointed at above; without one, the
|
|
249
|
+
// element's own `pack`, the copy on jsDelivr, where that may be reached, so a snippet
|
|
250
|
+
// written for `pack` keeps playing after a rebuild with --no-libraries. `hub` has meant
|
|
251
|
+
// `pack` since player 0.6: the one H5P hub host that answers a browser serves a
|
|
252
|
+
// catalogue older than current exports need.
|
|
253
|
+
if (ownPack || (!librariesPack && permitted(PACK_CDN_ORIGIN))) sources.push('pack')
|
|
254
|
+
else if (librariesPack) sources.push(librariesPack)
|
|
242
255
|
else return { error: 'This player was set up without the library pack, so libraries=pack is not available here.' }
|
|
243
256
|
} else {
|
|
244
257
|
const origin = urlOrigin(token)
|
|
@@ -252,7 +265,8 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
252
265
|
/**
|
|
253
266
|
* The element's display options, by their attribute names, for the embedding page to ask for:
|
|
254
267
|
* `&frame©right&export` shows H5P's action bar with those buttons, `&fullscreen=off` takes
|
|
255
|
-
* that one away, `&
|
|
268
|
+
* that one away, `&refresh-libraries=off` plays a package's own libraries even where the pack
|
|
269
|
+
* has newer ones, `&activity-id=` names the statements' object and `&custom-css=` restyles the
|
|
256
270
|
* content to the embedding site's taste. `&lang=` is the embedding page's language, which this
|
|
257
271
|
* page cannot read across origins: H5P's own buttons and dialogs follow it. Not `custom-js`,
|
|
258
272
|
* `embed-code` or `user`: a script is a capability on this origin that a link should not hand
|
|
@@ -263,6 +277,9 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
263
277
|
if (params.has(name) && params.get(name) !== 'off') player.setAttribute(name, '')
|
|
264
278
|
}
|
|
265
279
|
if (params.get('fullscreen') === 'off') player.setAttribute('fullscreen', 'off')
|
|
280
|
+
// The pack's newer versions of the libraries a package carries, unless the embedding page
|
|
281
|
+
// needs the package exactly as exported.
|
|
282
|
+
if (params.get('refresh-libraries') === 'off') player.setAttribute('refresh-libraries', 'off')
|
|
266
283
|
for (const name of ['activity-id', 'custom-css']) {
|
|
267
284
|
const value = params.get(name)?.trim()
|
|
268
285
|
if (value) player.setAttribute(name, value)
|
|
@@ -354,11 +371,11 @@ export function startEmbed({ librariesPack = null, packages = null, defaultLibra
|
|
|
354
371
|
// as the host's own: an untrusted package plays with this site's sources alone, the page's
|
|
355
372
|
// default or its pack, and the address's only once the click has trusted it.
|
|
356
373
|
const own = defaultLibraries?.trim() ? librarySources(defaultLibraries.trim()).value : ''
|
|
357
|
-
const
|
|
358
|
-
if (!
|
|
374
|
+
const sitesSources = own || librarySources('pack').value
|
|
375
|
+
if (!sitesSources) {
|
|
359
376
|
askToTrust()
|
|
360
377
|
return
|
|
361
378
|
}
|
|
362
379
|
player.untrusted = true
|
|
363
|
-
start(src,
|
|
380
|
+
start(src, sitesSources)
|
|
364
381
|
}
|
package/site/main.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/*! @missing-elements/h5p-embed. MIT. */
|
|
2
2
|
// The page's entry: `config.js` is what `h5p-embed` was told when it wrote this site — whether
|
|
3
|
-
// the library pack
|
|
3
|
+
// the library pack's parts are here, and which hosts packages may come from.
|
|
4
4
|
import config from './config.js'
|
|
5
5
|
import { startEmbed } from './embed.js'
|
|
6
6
|
|
|
7
7
|
startEmbed({
|
|
8
|
-
|
|
8
|
+
packBase: config.libraries ? new URL('./libraries/', import.meta.url).href : null,
|
|
9
9
|
packages: config.packages,
|
|
10
10
|
defaultLibraries: config.defaultLibraries
|
|
11
11
|
})
|