@missing-elements/h5p-embed 0.1.0 → 0.2.1
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 +43 -4
- package/bin/h5p-embed.mjs +7 -0
- package/lib/build.mjs +55 -3
- package/package.json +10 -11
- package/site/embed.js +63 -7
- package/site/main.js +2 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npx @missing-elements/h5p-embed h5p-player
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
```
|
|
13
|
-
Wrote h5p-player/ (11.1 MB): the embed page, the player
|
|
13
|
+
Wrote h5p-player/ (11.1 MB): the embed page, the player, the H5P runtime, the library pack.
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Deploy the folder to any static host, then embed:
|
|
@@ -38,6 +38,7 @@ cookies and nothing else on it.
|
|
|
38
38
|
|---|---|
|
|
39
39
|
| `--packages <origins>` | Play packages, and fetch library bundles, only from these origins (this domain's own is always allowed). The page refuses anything else by name, and the policy's `connect-src` blocks it in the browser. Add `https://api.h5p.org` to allow `libraries=hub` |
|
|
40
40
|
| `--ancestors <origins>` | Only these sites may frame the page: `frame-ancestors`, which only a header can carry |
|
|
41
|
+
| `--default-libraries <sources>` | The `libraries` value for addresses that name none, so a snippet without `&libraries=` still plays an export that carries no libraries (h5p.com and h5p.org exports usually do not): `pack`, `hub`, URLs, as the parameter. Checked against `--packages` when the site is written. Default: none, and such exports are refused unless the address asks |
|
|
41
42
|
| `--no-libraries` | Leave out the 9.5 MB library pack; `libraries=pack` then means the hub, where allowed |
|
|
42
43
|
| `--force` | Write into a folder that is not empty, replacing only this tool's files |
|
|
43
44
|
|
|
@@ -77,7 +78,7 @@ redeploy.
|
|
|
77
78
|
| Parameter | Effect |
|
|
78
79
|
|---|---|
|
|
79
80
|
| `src=<url>` | The package, required. Encode `&`, `#`, `+`, `%` and spaces in it |
|
|
80
|
-
| `libraries=pack`, `hub
|
|
81
|
+
| `libraries=pack`, `hub`, `<url>` or `none` | Libraries for an export that has none (h5p.com and h5p.org exports usually do not); `none` turns off the site's `--default-libraries` for this address. `pack` is the copy on this domain, with the hub behind it where allowed, or the hub alone on a site written with `--no-libraries`; several sources may be given, tried in order |
|
|
81
82
|
| `frame`, `copyright`, `export`, `icon`, `reporting` | H5P's action bar under the content and its buttons |
|
|
82
83
|
| `fullscreen=off` | No fullscreen button |
|
|
83
84
|
| `preload=auto` | Start fetching media at once |
|
|
@@ -110,6 +111,25 @@ The checks prove where a message came from, not what it says: a package can post
|
|
|
110
111
|
so treat relayed results as the learner's report, not as proof for a grade. Each statement
|
|
111
112
|
carries `context.revision`, a fingerprint of the package build.
|
|
112
113
|
|
|
114
|
+
## Messages to the embedding page
|
|
115
|
+
|
|
116
|
+
Besides the heights and the relayed statements, the frame posts two messages to its parent,
|
|
117
|
+
whatever `xapi=` says. Neither carries anything the embedding page did not hand over itself.
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
// Once, when the content is up: what the player learnt about the package.
|
|
121
|
+
{ context: 'h5p-offline-player', action: 'report',
|
|
122
|
+
source, // { type: 'range-http' | 'chunked' | 'file', size }: streamed, or downloaded whole
|
|
123
|
+
metadata, // { title, license, licenseVersion, authors: [names], mainLibrary }, from h5p.json
|
|
124
|
+
libraryBundle, // { url, origin, fromCache } when libraries came from a bundle, else null
|
|
125
|
+
elapsedMs } // from setting the package to the content being up
|
|
126
|
+
|
|
127
|
+
// Instead, when the load fails, or the page refuses the address (code: 'refused', or 'no-src').
|
|
128
|
+
{ context: 'h5p-offline-player', action: 'error', code, message }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The strings in `metadata` are the package's own; show them as text.
|
|
132
|
+
|
|
113
133
|
## As a library
|
|
114
134
|
|
|
115
135
|
```js
|
|
@@ -118,8 +138,27 @@ import { buildSite } from '@missing-elements/h5p-embed'
|
|
|
118
138
|
await buildSite({ out: 'public/player', packages: ['https://cdn.example.org'] })
|
|
119
139
|
```
|
|
120
140
|
|
|
121
|
-
`@missing-elements/h5p-embed/embed.js` exports `startEmbed(
|
|
122
|
-
|
|
141
|
+
`@missing-elements/h5p-embed/embed.js` exports `startEmbed()`, the page's script, for a site
|
|
142
|
+
that builds the page into its own pipeline. It reads the page's `<h5p-player>`, `#loader` and
|
|
143
|
+
`#notice` (see `site/index.html`, and `site/embed.css` for their styles) and takes:
|
|
144
|
+
|
|
145
|
+
| Option | |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `librariesPack` | The URL of a copy of `@missing-elements/h5p-libraries`, which `libraries=pack` names |
|
|
148
|
+
| `packages` | The origins packages may come from, besides the page's own; `null` for any |
|
|
149
|
+
| `defaultLibraries` | The `libraries` value for addresses that name none |
|
|
150
|
+
| `runtime` | The `runtime` export of `@missing-elements/h5p-runtime`, for a bundled element |
|
|
151
|
+
| `askInOwnFrame` | `false` to skip the click a package from elsewhere otherwise waits for when a page of the same origin frames this one — for a site whose own preview frames it for a package the visitor just chose. Default `true` |
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
import '@missing-elements/h5p-offline-player'
|
|
155
|
+
import { runtime } from '@missing-elements/h5p-runtime'
|
|
156
|
+
import librariesPack from '@missing-elements/h5p-libraries/libraries.h5p?url'
|
|
157
|
+
import '@missing-elements/h5p-embed/embed.css'
|
|
158
|
+
import { startEmbed } from '@missing-elements/h5p-embed/embed.js'
|
|
159
|
+
|
|
160
|
+
startEmbed({ runtime, librariesPack, defaultLibraries: 'pack' })
|
|
161
|
+
```
|
|
123
162
|
|
|
124
163
|
## Licences
|
|
125
164
|
|
package/bin/h5p-embed.mjs
CHANGED
|
@@ -16,6 +16,10 @@ Options:
|
|
|
16
16
|
URL redirects to. Default: any https host.
|
|
17
17
|
--ancestors <origins> only these sites may frame the page (frame-ancestors, sent as a header).
|
|
18
18
|
Default: any site.
|
|
19
|
+
--default-libraries <sources>
|
|
20
|
+
the libraries= value for addresses that name none, so a snippet without
|
|
21
|
+
it still plays an export with no libraries: pack, hub, URLs, as the
|
|
22
|
+
parameter. Default: none, such exports are refused unless the address asks
|
|
19
23
|
--no-libraries leave out the 9.5 MB library pack that libraries=pack names
|
|
20
24
|
--force write into a folder that is not empty, replacing only this tool's files
|
|
21
25
|
-h, --help show this
|
|
@@ -28,6 +32,7 @@ try {
|
|
|
28
32
|
options: {
|
|
29
33
|
packages: { type: 'string', multiple: true },
|
|
30
34
|
ancestors: { type: 'string', multiple: true },
|
|
35
|
+
'default-libraries': { type: 'string' },
|
|
31
36
|
'no-libraries': { type: 'boolean' },
|
|
32
37
|
force: { type: 'boolean' },
|
|
33
38
|
help: { type: 'boolean', short: 'h' },
|
|
@@ -61,6 +66,7 @@ try {
|
|
|
61
66
|
libraries: !values['no-libraries'],
|
|
62
67
|
packages: values.packages ?? null,
|
|
63
68
|
ancestors: values.ancestors ?? null,
|
|
69
|
+
defaultLibraries: values['default-libraries'] ?? null,
|
|
64
70
|
force: values.force ?? false
|
|
65
71
|
})
|
|
66
72
|
const below = relative(process.cwd(), site.out)
|
|
@@ -69,6 +75,7 @@ try {
|
|
|
69
75
|
console.log(`Wrote ${folder}/ (${megabytes(site.size)}): the embed page, ${parts.join(', ')}.`)
|
|
70
76
|
console.log(`Packages from: ${site.packages ? `this site, ${site.packages.join(', ')}` : 'any https host'}.`)
|
|
71
77
|
console.log(`Framed by: ${site.ancestors ? site.ancestors.join(', ') : 'any site'}.`)
|
|
78
|
+
console.log(`Libraries for exports without them: ${site.defaultLibraries ?? 'only when the address asks'}.`)
|
|
72
79
|
console.log(`
|
|
73
80
|
Deploy the folder to a domain that holds nothing else — a separate registrable domain, not a
|
|
74
81
|
subdomain of your site — then embed a package:
|
package/lib/build.mjs
CHANGED
|
@@ -51,6 +51,47 @@ export function parseOrigin(value) {
|
|
|
51
51
|
return url.origin
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* A `libraries` value for addresses that name none, checked against the site it goes into: the
|
|
56
|
+
* tokens the page understands (`pack`, `hub`, `none`, https URLs), and with a host list, nothing
|
|
57
|
+
* the page would then refuse on every load. Returns the value normalised to single spaces.
|
|
58
|
+
*
|
|
59
|
+
* @param {string | null | undefined} value
|
|
60
|
+
* @param {object} [site]
|
|
61
|
+
* @param {string[] | null} [site.packages] the site's host list, as `parseOrigins` returns it
|
|
62
|
+
* @param {boolean} [site.libraries] whether the site carries the pack
|
|
63
|
+
*/
|
|
64
|
+
export function parseDefaultLibraries(value, { packages = null, libraries = true } = {}) {
|
|
65
|
+
if (value == null) return null
|
|
66
|
+
const tokens = String(value).split(/\s+/).filter(Boolean)
|
|
67
|
+
if (tokens.length === 0) return null
|
|
68
|
+
if (tokens.includes('none')) {
|
|
69
|
+
if (tokens.length > 1) throw new EmbedError('--default-libraries none stands alone.')
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
// An exact match on a parsed origin, not a substring of a URL.
|
|
73
|
+
const hubAllowed = !packages || packages.some((origin) => origin === 'https://api.h5p.org')
|
|
74
|
+
for (const token of tokens) {
|
|
75
|
+
if (token === 'hub') {
|
|
76
|
+
if (!hubAllowed) throw new EmbedError('--default-libraries names hub, but --packages does not list https://api.h5p.org.')
|
|
77
|
+
} else if (token === 'pack') {
|
|
78
|
+
if (!libraries && !hubAllowed) throw new EmbedError('--default-libraries names pack, but the site has no pack (--no-libraries) and no hub to fall back to.')
|
|
79
|
+
} else {
|
|
80
|
+
let url
|
|
81
|
+
try {
|
|
82
|
+
url = new URL(token)
|
|
83
|
+
} catch {
|
|
84
|
+
throw new EmbedError(`--default-libraries: not pack, hub, none or a URL: ${token}`)
|
|
85
|
+
}
|
|
86
|
+
if (url.protocol !== 'https:' && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
|
|
87
|
+
throw new EmbedError(`--default-libraries: not an https URL: ${token}`)
|
|
88
|
+
}
|
|
89
|
+
if (packages && !packages.includes(url.origin)) throw new EmbedError(`--default-libraries names ${url.origin}, which --packages does not list.`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return tokens.join(' ')
|
|
93
|
+
}
|
|
94
|
+
|
|
54
95
|
/** Origins given as a list, or as one string separated by commas or whitespace. */
|
|
55
96
|
export function parseOrigins(values) {
|
|
56
97
|
if (values == null) return null
|
|
@@ -169,15 +210,17 @@ async function occupied(dir) {
|
|
|
169
210
|
* @param {boolean} [options.libraries] include the library pack, for `libraries=pack` (default true)
|
|
170
211
|
* @param {string[] | string | null} [options.packages] the only origins packages may come from
|
|
171
212
|
* @param {string[] | string | null} [options.ancestors] the only origins that may frame the page
|
|
213
|
+
* @param {string | null} [options.defaultLibraries] the `libraries` value for addresses that name none
|
|
172
214
|
* @param {boolean} [options.force]
|
|
173
215
|
*/
|
|
174
|
-
export async function buildSite({ out, libraries = true, packages = null, ancestors = null, force = false }) {
|
|
216
|
+
export async function buildSite({ out, libraries = true, packages = null, ancestors = null, defaultLibraries = null, force = false }) {
|
|
175
217
|
if (!out) throw new EmbedError('No output folder given.')
|
|
176
218
|
const target = resolve(out)
|
|
177
219
|
const allowedPackages = parseOrigins(packages)
|
|
178
220
|
const allowedAncestors = parseOrigins(ancestors)
|
|
179
221
|
if (allowedPackages?.length === 0) throw new EmbedError('--packages names no origin.')
|
|
180
222
|
if (allowedAncestors?.length === 0) throw new EmbedError('--ancestors names no origin.')
|
|
223
|
+
const fallbackLibraries = parseDefaultLibraries(defaultLibraries, { packages: allowedPackages, libraries })
|
|
181
224
|
// Checked with --force too: a path that is a file is refused either way.
|
|
182
225
|
if ((await occupied(target)) && !force) {
|
|
183
226
|
throw new EmbedError(`${out} is not empty. Choose an empty folder, or pass --force to replace the files this writes.`)
|
|
@@ -205,7 +248,7 @@ export async function buildSite({ out, libraries = true, packages = null, ancest
|
|
|
205
248
|
await writeFile(join(target, 'index.html'), html.replace('%CSP%', contentSecurityPolicy({ packages: allowedPackages, meta: true })))
|
|
206
249
|
await writeFile(
|
|
207
250
|
join(target, 'config.js'),
|
|
208
|
-
`// Written by h5p-embed: what main.js hands the page.\nexport default ${JSON.stringify({ libraries: Boolean(pack), packages: allowedPackages })}\n`
|
|
251
|
+
`// Written by h5p-embed: what main.js hands the page.\nexport default ${JSON.stringify({ libraries: Boolean(pack), packages: allowedPackages, defaultLibraries: fallbackLibraries })}\n`
|
|
209
252
|
)
|
|
210
253
|
|
|
211
254
|
const csp = contentSecurityPolicy({ packages: allowedPackages, ancestors: allowedAncestors })
|
|
@@ -214,7 +257,16 @@ export async function buildSite({ out, libraries = true, packages = null, ancest
|
|
|
214
257
|
await writeFile(join(target, 'vercel.json'), vercelConfig(rules))
|
|
215
258
|
await writeFile(join(target, 'NOTICE.txt'), notice({ version, libraries: Boolean(pack) }))
|
|
216
259
|
|
|
217
|
-
return {
|
|
260
|
+
return {
|
|
261
|
+
out: target,
|
|
262
|
+
version,
|
|
263
|
+
csp,
|
|
264
|
+
libraries: Boolean(pack),
|
|
265
|
+
packages: allowedPackages,
|
|
266
|
+
ancestors: allowedAncestors,
|
|
267
|
+
defaultLibraries: fallbackLibraries,
|
|
268
|
+
size: await sizeOf(target)
|
|
269
|
+
}
|
|
218
270
|
}
|
|
219
271
|
|
|
220
272
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@missing-elements/h5p-embed",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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": {
|
|
@@ -42,24 +42,23 @@
|
|
|
42
42
|
"elearning",
|
|
43
43
|
"cli"
|
|
44
44
|
],
|
|
45
|
-
"scripts": {
|
|
46
|
-
"test": "pnpm --filter @missing-elements/h5p-offline-player build && vitest run",
|
|
47
|
-
"typecheck": "tsc -p tsconfig.json",
|
|
48
|
-
"prepack": "node -e \"require('node:fs').copyFileSync('../../LICENSE', 'LICENSE')\"",
|
|
49
|
-
"prepublishOnly": "pnpm typecheck && pnpm test"
|
|
50
|
-
},
|
|
51
45
|
"dependencies": {
|
|
52
|
-
"@missing-elements/h5p-libraries": "
|
|
53
|
-
"@missing-elements/h5p-
|
|
54
|
-
"@missing-elements/h5p-
|
|
46
|
+
"@missing-elements/h5p-libraries": "^0.1.0",
|
|
47
|
+
"@missing-elements/h5p-runtime": "^0.1.0",
|
|
48
|
+
"@missing-elements/h5p-offline-player": "^0.5.2"
|
|
55
49
|
},
|
|
56
50
|
"devDependencies": {
|
|
57
51
|
"@types/node": "^26.6.4",
|
|
52
|
+
"@zip.js/zip.js": "^2.23.0",
|
|
58
53
|
"playwright": "^1.50.0",
|
|
59
54
|
"typescript": "^7.0.2",
|
|
60
55
|
"vitest": "^5.0.3"
|
|
61
56
|
},
|
|
62
57
|
"engines": {
|
|
63
58
|
"node": ">=20"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"test": "pnpm --filter @missing-elements/h5p-offline-player build && vitest run",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
64
63
|
}
|
|
65
|
-
}
|
|
64
|
+
}
|
package/site/embed.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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|hub|<url>
|
|
8
|
+
* ?src=<package url>[&libraries=pack|hub|<url> …|none][&preload=auto][&xapi=<parent origin>]
|
|
9
9
|
* [&frame][©right][&export][&icon][&reporting][&fullscreen=off]
|
|
10
10
|
* [&activity-id=<IRI>][&custom-css=<stylesheet url>]
|
|
11
11
|
*
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* embed code and its `h5p-resizer.js` use — so a page that already resizes h5p.org iframes
|
|
14
14
|
* resizes this one without a change, and any other page gets `resizer.js` from this origin. xAPI
|
|
15
15
|
* statements are relayed to the parent only when `xapi=` names the parent's origin, and they are
|
|
16
|
-
* posted to that origin only.
|
|
16
|
+
* posted to that origin only. Once the content is up the page posts one `report` upward, and a
|
|
17
|
+
* load that fails posts its `error` (see `report` below): what an embedding page that checks a
|
|
18
|
+
* package — Embed My's preview — shows.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
/** Where `libraries=hub` fetches from: the one hub host that sends CORS headers (see the player). */
|
|
@@ -22,13 +24,23 @@ const HUB_ORIGIN = 'https://api.h5p.org'
|
|
|
22
24
|
/**
|
|
23
25
|
* @param {object} [options]
|
|
24
26
|
* @param {string | null} [options.librariesPack] the URL of a copy of `@missing-elements/h5p-libraries`
|
|
25
|
-
* on this site, which `libraries=pack` names; without one, `pack`
|
|
27
|
+
* on this site, which `libraries=pack` names; without one, `pack` means the hub where allowed
|
|
26
28
|
* @param {string[] | null} [options.packages] the origins packages and library bundles may come
|
|
27
29
|
* from, besides this page's own; `null` plays any, asking first when the storage is this origin's
|
|
30
|
+
* @param {string | null} [options.defaultLibraries] the `libraries` value for an address that has
|
|
31
|
+
* none — `pack`, `hub`, URLs, as the parameter — so a snippet without `&libraries=` still plays
|
|
32
|
+
* an export that carries no libraries; `&libraries=none` turns it off for one address
|
|
33
|
+
* @param {object | null} [options.runtime] the `runtime` export of `@missing-elements/h5p-runtime`,
|
|
34
|
+
* 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`
|
|
28
38
|
*/
|
|
29
|
-
export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
39
|
+
export function startEmbed({ librariesPack = null, packages = null, defaultLibraries = null, runtime = null, askInOwnFrame = true } = {}) {
|
|
30
40
|
const params = new URLSearchParams(location.search)
|
|
31
41
|
const player = document.querySelector('h5p-player')
|
|
42
|
+
// Before `src`: the element resolves the runtime's files when a package is set.
|
|
43
|
+
if (runtime) player.runtime = runtime
|
|
32
44
|
const notice = document.querySelector('#notice')
|
|
33
45
|
const loader = document.querySelector('#loader')
|
|
34
46
|
const framed = window.parent !== window
|
|
@@ -53,9 +65,10 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
53
65
|
requestAnimationFrame(announce)
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
const refuse = (text) => {
|
|
68
|
+
const refuse = (text, code = 'refused') => {
|
|
57
69
|
loader.hidden = true
|
|
58
70
|
say(text, 'error')
|
|
71
|
+
post({ context: 'h5p-offline-player', action: 'error', code, message: text })
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
/* ---------------------------------------------------------------- sizing, upward */
|
|
@@ -93,6 +106,42 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
93
106
|
player.addEventListener('resize', () => requestAnimationFrame(announce))
|
|
94
107
|
player.addEventListener('ready', () => requestAnimationFrame(announce))
|
|
95
108
|
|
|
109
|
+
/* ---------------------------------------------------------------- the report, upward */
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* What the player learnt about the package, for the embedding page to show: whether the host
|
|
113
|
+
* streamed it or made the browser download it whole (`source.type`), how big it is, what it
|
|
114
|
+
* says it is (`metadata`), where libraries it did not carry came from (`libraryBundle`, `null`
|
|
115
|
+
* when it carried its own), and how long it took here. Posted once, when the content is up:
|
|
116
|
+
*
|
|
117
|
+
* { context: 'h5p-offline-player', action: 'report', source, metadata, libraryBundle, elapsedMs }
|
|
118
|
+
*
|
|
119
|
+
* and to any parent, like the heights: the parent named the package, and the manifest's strings
|
|
120
|
+
* are the package's own to tell. A parent treats them as text. A load that fails before the
|
|
121
|
+
* content is up posts `{ context: 'h5p-offline-player', action: 'error', code, message }`
|
|
122
|
+
* instead, a refusal by this page included (`code: 'refused'`). The shapes are Embed My's,
|
|
123
|
+
* whose preview is built from them.
|
|
124
|
+
*/
|
|
125
|
+
let startedAt = 0
|
|
126
|
+
|
|
127
|
+
player.addEventListener('ready', (event) => {
|
|
128
|
+
const { source, metadata, libraryBundle } = event.detail
|
|
129
|
+
post({
|
|
130
|
+
context: 'h5p-offline-player',
|
|
131
|
+
action: 'report',
|
|
132
|
+
source: source && { type: source.type, size: source.size },
|
|
133
|
+
metadata: metadata && {
|
|
134
|
+
title: metadata.title,
|
|
135
|
+
license: metadata.license,
|
|
136
|
+
licenseVersion: metadata.licenseVersion,
|
|
137
|
+
authors: metadata.authors?.map(({ name }) => name),
|
|
138
|
+
mainLibrary: metadata.mainLibrary
|
|
139
|
+
},
|
|
140
|
+
libraryBundle,
|
|
141
|
+
elapsedMs: Math.round(performance.now() - startedAt)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
96
145
|
/* ---------------------------------------------------------------- xAPI, relayed on request */
|
|
97
146
|
|
|
98
147
|
/** An origin, or nothing: the parameter has to be exactly what `event.origin` will read. */
|
|
@@ -126,6 +175,7 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
126
175
|
href: location.href,
|
|
127
176
|
text: 'Open it on its own'
|
|
128
177
|
})
|
|
178
|
+
post({ context: 'h5p-offline-player', action: 'error', code, message })
|
|
129
179
|
return
|
|
130
180
|
}
|
|
131
181
|
// Once the content is up, a runtime error inside it is the content's business: it keeps
|
|
@@ -135,6 +185,7 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
135
185
|
return
|
|
136
186
|
}
|
|
137
187
|
say(message || code, 'error')
|
|
188
|
+
post({ context: 'h5p-offline-player', action: 'error', code, message })
|
|
138
189
|
})
|
|
139
190
|
|
|
140
191
|
player.addEventListener('statechange', (event) => {
|
|
@@ -169,6 +220,7 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
169
220
|
*/
|
|
170
221
|
const librarySources = (value) => {
|
|
171
222
|
const sources = []
|
|
223
|
+
if (value === 'none') return { value: '' }
|
|
172
224
|
for (const token of value.split(/\s+/).filter(Boolean)) {
|
|
173
225
|
if (token === 'pack') {
|
|
174
226
|
// The pack, with the hub behind it for what it lacks, where the hub may be reached: an
|
|
@@ -213,6 +265,7 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
213
265
|
if (libraries) player.setAttribute('libraries', libraries)
|
|
214
266
|
if (params.get('preload') === 'auto') player.setAttribute('preload', 'auto')
|
|
215
267
|
applyOptions()
|
|
268
|
+
startedAt = performance.now()
|
|
216
269
|
player.setAttribute('src', value)
|
|
217
270
|
}
|
|
218
271
|
|
|
@@ -225,6 +278,8 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
225
278
|
*/
|
|
226
279
|
const sharesOriginStorage = () => {
|
|
227
280
|
if (!framed) return true
|
|
281
|
+
// Treated as another site's frame: the page around it chose this package, so no click.
|
|
282
|
+
if (!askInOwnFrame) return false
|
|
228
283
|
try {
|
|
229
284
|
return window.parent.location.origin === location.origin
|
|
230
285
|
} catch {
|
|
@@ -236,7 +291,7 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
236
291
|
|
|
237
292
|
const src = params.get('src')?.trim()
|
|
238
293
|
if (!src) {
|
|
239
|
-
refuse('No package given. Add ?src=<url of a .h5p file> to the address.')
|
|
294
|
+
refuse('No package given. Add ?src=<url of a .h5p file> to the address.', 'no-src')
|
|
240
295
|
return
|
|
241
296
|
}
|
|
242
297
|
const origin = urlOrigin(src)
|
|
@@ -248,7 +303,8 @@ export function startEmbed({ librariesPack = null, packages = null } = {}) {
|
|
|
248
303
|
refuse(`This player does not play packages from ${origin}.`)
|
|
249
304
|
return
|
|
250
305
|
}
|
|
251
|
-
|
|
306
|
+
// The address's own value wins, `none` included; an address without one gets the page's default.
|
|
307
|
+
const libraries = params.get('libraries')?.trim() || defaultLibraries?.trim()
|
|
252
308
|
const sources = libraries ? librarySources(libraries) : { value: '' }
|
|
253
309
|
if (sources.error) {
|
|
254
310
|
refuse(sources.error)
|
package/site/main.js
CHANGED