@pie-players/pie-players-shared 0.3.74 → 1.0.0-beta.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 +88 -75
- package/dist/components/vendor/nds/README.md +4 -5
- package/dist/config/profile.d.ts +1 -1
- package/dist/config/profile.js +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/language-tags.d.ts +9 -1
- package/dist/i18n/language-tags.js +11 -1
- package/dist/i18n/messages/en-US.d.ts +33 -28
- package/dist/i18n/messages/en-US.js +34 -29
- package/dist/i18n/messages/nl-NL.d.ts +33 -28
- package/dist/i18n/messages/nl-NL.js +34 -29
- package/dist/i18n/provider.d.ts +14 -7
- package/dist/i18n/provider.js +26 -29
- package/dist/i18n/types.d.ts +4 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js +0 -14
- package/dist/loader-config.d.ts +0 -2
- package/dist/loader-config.js +0 -2
- package/dist/loaders/element-loader.js +13 -6
- package/dist/loaders/esm-adapter.d.ts +11 -0
- package/dist/loaders/esm-adapter.js +74 -45
- package/dist/loaders/iife-adapter.d.ts +3 -3
- package/dist/loaders/iife-adapter.js +36 -35
- package/dist/loaders/index.d.ts +4 -2
- package/dist/loaders/index.js +2 -1
- package/dist/loaders/mathjax-events.d.ts +22 -0
- package/dist/loaders/mathjax-events.js +65 -0
- package/dist/loaders/preloaded-registration.d.ts +18 -2
- package/dist/loaders/preloaded-registration.js +14 -2
- package/dist/media/index.d.ts +4 -6
- package/dist/media/index.js +6 -8
- package/dist/object/index.d.ts +8 -3
- package/dist/object/index.js +8 -6
- package/dist/pie/asset-handler.d.ts +0 -17
- package/dist/pie/asset-handler.js +1 -1
- package/dist/pie/authoring.js +5 -7
- package/dist/pie/elements-rendered.d.ts +23 -0
- package/dist/pie/elements-rendered.js +96 -0
- package/dist/pie/index.d.ts +4 -6
- package/dist/pie/index.js +4 -7
- package/dist/pie/initialization.d.ts +13 -14
- package/dist/pie/initialization.js +61 -75
- package/dist/pie/instrumentation-event-bridge.js +23 -1
- package/dist/pie/instrumentation-event-map.js +11 -18
- package/dist/pie/item-controller.js +4 -0
- package/dist/pie/item-session-contract.js +7 -9
- package/dist/pie/logger.d.ts +6 -0
- package/dist/pie/logger.js +10 -0
- package/dist/pie/math-assets.d.ts +48 -0
- package/dist/pie/math-assets.js +81 -0
- package/dist/pie/resource-monitor.d.ts +34 -32
- package/dist/pie/resource-monitor.js +152 -231
- package/dist/pie/scoring.d.ts +1 -6
- package/dist/pie/scoring.js +3 -8
- package/dist/pie/session-commit.js +1 -1
- package/dist/pie/stages.d.ts +11 -47
- package/dist/pie/stages.js +11 -47
- package/dist/pie/updates.js +5 -4
- package/dist/pie/utils.d.ts +0 -2
- package/dist/pie/utils.js +0 -2
- package/dist/security/sanitize-item-markup.d.ts +8 -4
- package/dist/security/sanitize-item-markup.js +52 -6
- package/dist/security/sanitize-svg-icon.js +2 -2
- package/dist/security/wrap-overwide-images.d.ts +1 -1
- package/dist/security/wrap-overwide-images.js +20 -1
- package/dist/security/wrap-overwide.d.ts +8 -0
- package/dist/security/wrap-overwide.js +47 -3
- package/dist/timed-media/media-element-source.js +1 -2
- package/dist/types/index.d.ts +88 -153
- package/dist/types/index.js +2 -2
- package/dist/ui/attribute-coercion.d.ts +5 -0
- package/dist/ui/attribute-coercion.js +6 -4
- package/dist/ui/content-styles.d.ts +6 -10
- package/dist/ui/content-styles.js +8 -13
- package/dist/ui/first-focusable.d.ts +0 -1
- package/dist/ui/first-focusable.js +0 -1
- package/dist/ui/focus-trap.js +2 -3
- package/dist/ui/scope-css.d.ts +23 -26
- package/dist/ui/scope-css.js +23 -26
- package/package.json +6 -4
- package/dist/loaders/mathjax-version-conflict.d.ts +0 -17
- package/dist/loaders/mathjax-version-conflict.js +0 -51
- package/dist/pie/stage-tracker.d.ts +0 -50
- package/dist/pie/stage-tracker.js +0 -105
|
@@ -21,7 +21,8 @@ import { BundleType, isCustomElementConstructor, Status, } from "../pie/types.js
|
|
|
21
21
|
import { parsePackageName } from "../pie/utils.js";
|
|
22
22
|
import { AdapterFailure, } from "./element-loader-types.js";
|
|
23
23
|
import { isExactSemver } from "./element-package-policy.js";
|
|
24
|
-
import {
|
|
24
|
+
import { forwardMathjaxEvents } from "./mathjax-events.js";
|
|
25
|
+
import { isPlainRecord } from "../object/index.js";
|
|
25
26
|
export const BUILT_IN_VIEWS = {
|
|
26
27
|
delivery: { subpath: "", tagSuffix: "" },
|
|
27
28
|
author: { subpath: "/author", tagSuffix: "-config", fallback: "delivery" },
|
|
@@ -41,8 +42,8 @@ export function createEsmBackend(config) {
|
|
|
41
42
|
const editorRuntimeVariants = new Map();
|
|
42
43
|
/** Packages that do not publish the browser ESM exports this backend loads. */
|
|
43
44
|
const unpublishedPackages = new Map();
|
|
44
|
-
/** Windows whose MathJax
|
|
45
|
-
const
|
|
45
|
+
/** Windows whose MathJax events this backend forwards. */
|
|
46
|
+
const mathjaxEventViews = new WeakSet();
|
|
46
47
|
let sharedDependencyVersions = {};
|
|
47
48
|
let importer = defaultImporter;
|
|
48
49
|
let packageMetadataLoader = defaultPackageMetadataLoader;
|
|
@@ -82,7 +83,7 @@ export function createEsmBackend(config) {
|
|
|
82
83
|
if (moduleResolution === "import-map") {
|
|
83
84
|
assertImportMapSupported();
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
+
forwardMathjaxEventsOf(context.doc);
|
|
86
87
|
const newEntries = {};
|
|
87
88
|
for (const [tag, pkg] of Object.entries(elements)) {
|
|
88
89
|
const packageName = parsePackageName(pkg).name;
|
|
@@ -123,10 +124,15 @@ export function createEsmBackend(config) {
|
|
|
123
124
|
let injectedMap;
|
|
124
125
|
if (Object.keys(imports).length > 0) {
|
|
125
126
|
const json = JSON.stringify({ imports }, null, 2);
|
|
127
|
+
// URL resolution maps the shared dependencies and the editor
|
|
128
|
+
// runtime, so it needs import maps too.
|
|
126
129
|
assertImportMapSupported();
|
|
127
130
|
injectedMap = injectImportMap(json, context.doc, runtimePlan?.mappedRuntime);
|
|
128
131
|
importMapObserver?.(json, context.doc);
|
|
129
132
|
}
|
|
133
|
+
// Recorded before the map settles: a load that starts meanwhile skips
|
|
134
|
+
// these packages, builds on the shared dependency versions this map
|
|
135
|
+
// fixed, and its imports wait for the map in `importModule`.
|
|
130
136
|
for (const [pkg, variant] of runtimePlan?.served ?? []) {
|
|
131
137
|
editorRuntimeVariants.set(pkg, variant);
|
|
132
138
|
}
|
|
@@ -152,7 +158,7 @@ export function createEsmBackend(config) {
|
|
|
152
158
|
reasons.set(tag, {
|
|
153
159
|
kind: "define-failed",
|
|
154
160
|
tag,
|
|
155
|
-
cause:
|
|
161
|
+
cause: errorMessage(err),
|
|
156
162
|
});
|
|
157
163
|
return;
|
|
158
164
|
}
|
|
@@ -189,9 +195,7 @@ export function createEsmBackend(config) {
|
|
|
189
195
|
kind: "module-load-failed",
|
|
190
196
|
tag,
|
|
191
197
|
specifier: fallbackSpecifier,
|
|
192
|
-
cause: fallbackErr
|
|
193
|
-
? fallbackErr.message
|
|
194
|
-
: String(fallbackErr),
|
|
198
|
+
cause: errorMessage(fallbackErr),
|
|
195
199
|
});
|
|
196
200
|
return;
|
|
197
201
|
}
|
|
@@ -201,7 +205,7 @@ export function createEsmBackend(config) {
|
|
|
201
205
|
kind: "module-load-failed",
|
|
202
206
|
tag,
|
|
203
207
|
specifier,
|
|
204
|
-
cause:
|
|
208
|
+
cause: errorMessage(err),
|
|
205
209
|
});
|
|
206
210
|
return;
|
|
207
211
|
}
|
|
@@ -225,6 +229,8 @@ export function createEsmBackend(config) {
|
|
|
225
229
|
return;
|
|
226
230
|
}
|
|
227
231
|
try {
|
|
232
|
+
// `customElements` rejects a constructor already defined under
|
|
233
|
+
// another tag, so each versioned tag gets its own subclass.
|
|
228
234
|
defineCustomElementSafely(actualTag, class extends ElementClass {
|
|
229
235
|
}, `element tag for ${packageName}`);
|
|
230
236
|
}
|
|
@@ -232,35 +238,13 @@ export function createEsmBackend(config) {
|
|
|
232
238
|
reasons.set(tag, {
|
|
233
239
|
kind: "define-failed",
|
|
234
240
|
tag,
|
|
235
|
-
cause:
|
|
241
|
+
cause: errorMessage(err),
|
|
236
242
|
});
|
|
237
243
|
return;
|
|
238
244
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const variantController = servedVariant?.views.controller;
|
|
243
|
-
if (servedVariant && variantController) {
|
|
244
|
-
try {
|
|
245
|
-
const controllerModule = await importModule(cdnProvider.browserViewUrl(packageVersion, variantController), context.doc);
|
|
246
|
-
controller = controllerModule?.default ?? controllerModule;
|
|
247
|
-
}
|
|
248
|
-
catch (err) {
|
|
249
|
-
reportEditorRuntimeFallback(packageVersion, servedVariant.declaration, servedVariant.runtime, `its editor-runtime controller failed to load: ${errorMessage(err)}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (!controller) {
|
|
253
|
-
const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
|
|
254
|
-
try {
|
|
255
|
-
const controllerModule = await importModule(controllerSpecifier, context.doc);
|
|
256
|
-
controller = controllerModule?.default ?? controllerModule;
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
// Controllers are best-effort; element registration is what the
|
|
260
|
-
// primitive verifies.
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
245
|
+
const controller = loadControllers
|
|
246
|
+
? await importController(packageName, packageVersion, context.doc)
|
|
247
|
+
: null;
|
|
264
248
|
writeRegistryEntry({
|
|
265
249
|
package: packageVersion,
|
|
266
250
|
status: Status.loaded,
|
|
@@ -293,6 +277,35 @@ export function createEsmBackend(config) {
|
|
|
293
277
|
return undefined;
|
|
294
278
|
}
|
|
295
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* The package's controller: its editor-runtime variant's when one is served
|
|
282
|
+
* and loads, else the package's own. Controllers are best-effort; element
|
|
283
|
+
* registration is what the primitive verifies.
|
|
284
|
+
*/
|
|
285
|
+
async function importController(packageName, packageVersion, doc) {
|
|
286
|
+
let controller = null;
|
|
287
|
+
const servedVariant = editorRuntimeVariants.get(packageVersion);
|
|
288
|
+
const variantController = servedVariant?.views.controller;
|
|
289
|
+
if (servedVariant && variantController) {
|
|
290
|
+
try {
|
|
291
|
+
const controllerModule = await importModule(cdnProvider.browserViewUrl(packageVersion, variantController), doc);
|
|
292
|
+
controller = controllerModule?.default ?? controllerModule;
|
|
293
|
+
if (controller)
|
|
294
|
+
return controller;
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
reportEditorRuntimeFallback(packageVersion, servedVariant.declaration, servedVariant.runtime, `its editor-runtime controller failed to load: ${errorMessage(err)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const controllerSpecifier = resolveControllerSpecifier(packageName, packageVersion, moduleResolution, cdnProvider);
|
|
301
|
+
try {
|
|
302
|
+
const controllerModule = await importModule(controllerSpecifier, doc);
|
|
303
|
+
return controllerModule?.default ?? controllerModule;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return controller;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
296
309
|
/**
|
|
297
310
|
* Imports through the browser, or through es-module-shims once the document
|
|
298
311
|
* has rejected an import map, after every map a backend added has settled.
|
|
@@ -481,12 +494,12 @@ export function createEsmBackend(config) {
|
|
|
481
494
|
return undefined;
|
|
482
495
|
return provider;
|
|
483
496
|
}
|
|
484
|
-
function
|
|
497
|
+
function forwardMathjaxEventsOf(doc) {
|
|
485
498
|
const view = doc.defaultView;
|
|
486
|
-
if (!view ||
|
|
499
|
+
if (!view || mathjaxEventViews.has(view))
|
|
487
500
|
return;
|
|
488
|
-
|
|
489
|
-
|
|
501
|
+
mathjaxEventViews.add(view);
|
|
502
|
+
forwardMathjaxEvents(view, getInstrumentationProvider);
|
|
490
503
|
}
|
|
491
504
|
function reportSharedDependencyConflict(attributes, message = `[pie-esm] Shared dependency version conflict resolved for ${String(attributes.dependencyName)}`) {
|
|
492
505
|
if (typeof console !== "undefined" && console.warn) {
|
|
@@ -547,7 +560,8 @@ export function mapEsmViewElements(elements, view = "delivery", viewConfig) {
|
|
|
547
560
|
]));
|
|
548
561
|
}
|
|
549
562
|
function defaultImporter(specifier) {
|
|
550
|
-
//
|
|
563
|
+
// Host bundlers must leave this import alone: the specifier is a CDN URL or
|
|
564
|
+
// import-map specifier known only at runtime.
|
|
551
565
|
return import(/* webpackIgnore: true */ /* @vite-ignore */ specifier);
|
|
552
566
|
}
|
|
553
567
|
/**
|
|
@@ -608,6 +622,24 @@ export function resolveEsmRuntimeSupportUrl(packageVersion, options) {
|
|
|
608
622
|
? `${packageJsonUrl.slice(0, -"package.json".length)}${RUNTIME_SUPPORT_PATH}`
|
|
609
623
|
: undefined;
|
|
610
624
|
}
|
|
625
|
+
/** A package the probe below names, so a provider URL ends in its `<package>@<version>` path. */
|
|
626
|
+
const ROOT_PROBE_PACKAGE = "@pie-element/shared-math-rendering-mathjax@0.0.0";
|
|
627
|
+
/**
|
|
628
|
+
* The npm root the ESM CDN the players load elements from serves raw package
|
|
629
|
+
* files under, `<root>/<package>@<version>/<path>`: the base URL for jsDelivr,
|
|
630
|
+
* raw.esm.sh for esm.sh, and for a custom provider whatever its
|
|
631
|
+
* `packageJsonUrl` puts before the package. `undefined` when that URL names
|
|
632
|
+
* no such root.
|
|
633
|
+
*/
|
|
634
|
+
export function resolveEsmAssetRoot(options) {
|
|
635
|
+
const cdnBaseUrl = options.cdnBaseUrl.replace(/\/+$/, "");
|
|
636
|
+
const provider = resolveCdnProvider(options.cdnProvider, cdnBaseUrl);
|
|
637
|
+
const suffix = `/${ROOT_PROBE_PACKAGE}/package.json`;
|
|
638
|
+
const packageJsonUrl = provider.packageJsonUrl(ROOT_PROBE_PACKAGE);
|
|
639
|
+
return packageJsonUrl.endsWith(suffix)
|
|
640
|
+
? packageJsonUrl.slice(0, -suffix.length) || undefined
|
|
641
|
+
: undefined;
|
|
642
|
+
}
|
|
611
643
|
function resolveCdnProvider(provider, cdnBaseUrl) {
|
|
612
644
|
if (isEsmCdnProvider(provider))
|
|
613
645
|
return provider;
|
|
@@ -854,9 +886,6 @@ const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
|
854
886
|
/** Slash-separated segments, none starting with a dot. */
|
|
855
887
|
const BROWSER_VIEW_PATH = /^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
|
|
856
888
|
const BARE_SPECIFIER = /^(?:@[A-Za-z0-9_-][A-Za-z0-9._-]*\/)?[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
|
|
857
|
-
function isRecord(value) {
|
|
858
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
859
|
-
}
|
|
860
889
|
/**
|
|
861
890
|
* A package's `pie.browserEditorRuntime`: `undefined` when it declares none,
|
|
862
891
|
* `null` when the declaration is malformed.
|
|
@@ -865,7 +894,7 @@ function readEditorRuntimeDeclaration(metadata) {
|
|
|
865
894
|
const declared = metadata?.pie?.browserEditorRuntime;
|
|
866
895
|
if (declared === undefined)
|
|
867
896
|
return undefined;
|
|
868
|
-
if (!
|
|
897
|
+
if (!isPlainRecord(declared) || !isPlainRecord(declared.views))
|
|
869
898
|
return null;
|
|
870
899
|
const { name, version } = declared;
|
|
871
900
|
if (typeof name !== "string" || !PACKAGE_NAME.test(name))
|
|
@@ -889,7 +918,7 @@ function readEditorRuntimeDeclaration(metadata) {
|
|
|
889
918
|
*/
|
|
890
919
|
function readBrowserModules(metadata) {
|
|
891
920
|
const declared = metadata.pie?.browserModules;
|
|
892
|
-
if (!
|
|
921
|
+
if (!isPlainRecord(declared))
|
|
893
922
|
return null;
|
|
894
923
|
const modules = Object.entries(declared);
|
|
895
924
|
const valid = modules.length > 0 &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* IIFE backend adapter for the ElementLoader primitive.
|
|
3
3
|
*
|
|
4
|
-
* Loads PIE elements from IIFE bundles served by the
|
|
4
|
+
* Loads PIE elements from IIFE bundles served by the bundle host,
|
|
5
5
|
* then registers them with `customElements`. On any per-tag failure
|
|
6
6
|
* (missing package, non-constructor element class, define failure),
|
|
7
7
|
* throws `AdapterFailure` with a structured `reasons` map that the
|
|
@@ -38,7 +38,7 @@ export type IifeBundleRetryStatus = {
|
|
|
38
38
|
};
|
|
39
39
|
export type IifeBackendConfig = {
|
|
40
40
|
kind: "iife";
|
|
41
|
-
/** Base URL
|
|
41
|
+
/** Base URL of the bundle host. */
|
|
42
42
|
bundleHost: string;
|
|
43
43
|
/** Bundle variant to request (player / client-player / editor). */
|
|
44
44
|
bundleType?: BundleType;
|
|
@@ -54,7 +54,7 @@ export type IifeBackendConfig = {
|
|
|
54
54
|
/**
|
|
55
55
|
* Retry policy for transient bundle load failures.
|
|
56
56
|
*
|
|
57
|
-
* When the bundle
|
|
57
|
+
* When the bundle host is building a bundle on demand, the first
|
|
58
58
|
* few `<script src>` requests can fail. This policy polls until the
|
|
59
59
|
* bundle is available or `timeoutMs` elapses. Default matches
|
|
60
60
|
* `DEFAULT_IIFE_BUNDLE_RETRY_CONFIG`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* IIFE backend adapter for the ElementLoader primitive.
|
|
3
3
|
*
|
|
4
|
-
* Loads PIE elements from IIFE bundles served by the
|
|
4
|
+
* Loads PIE elements from IIFE bundles served by the bundle host,
|
|
5
5
|
* then registers them with `customElements`. On any per-tag failure
|
|
6
6
|
* (missing package, non-constructor element class, define failure),
|
|
7
7
|
* throws `AdapterFailure` with a structured `reasons` map that the
|
|
@@ -76,7 +76,7 @@ export function createIifeBackend(config) {
|
|
|
76
76
|
/**
|
|
77
77
|
* Wrap the single-shot script loader with poll-until-ready retry.
|
|
78
78
|
*
|
|
79
|
-
* The
|
|
79
|
+
* The bundle host may return a transient failure while it
|
|
80
80
|
* builds a bundle on demand. Retry with fixed-interval polling until
|
|
81
81
|
* the bundle is ready or the cumulative deadline elapses. Emits
|
|
82
82
|
* retry lifecycle events to the host via `onBundleRetryStatus` and to
|
|
@@ -111,7 +111,7 @@ export function createIifeBackend(config) {
|
|
|
111
111
|
catch (error) {
|
|
112
112
|
const elapsedMs = Date.now() - startedAt;
|
|
113
113
|
const remainingMs = timeoutMs - elapsedMs;
|
|
114
|
-
const reason =
|
|
114
|
+
const reason = errorMessage(error);
|
|
115
115
|
if (remainingMs <= 0) {
|
|
116
116
|
const timeoutError = new Error(`IIFE bundle load timed out after ${timeoutMs}ms: ${url}`);
|
|
117
117
|
config.onBundleRetryStatus?.({
|
|
@@ -201,45 +201,33 @@ export function createIifeBackend(config) {
|
|
|
201
201
|
return;
|
|
202
202
|
const conflict = findIifeElementMapConflict(elements);
|
|
203
203
|
if (conflict) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
cause: conflict,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
throw new AdapterFailure(reasons);
|
|
204
|
+
throw failureForEveryTag(elements, (tag) => ({
|
|
205
|
+
kind: "backend-rejected",
|
|
206
|
+
tag,
|
|
207
|
+
cause: conflict,
|
|
208
|
+
}));
|
|
213
209
|
}
|
|
214
210
|
const bundleUrl = buildBundleUrl(elements, bundleType, config);
|
|
215
211
|
try {
|
|
216
212
|
await ensureBundleLoaded(bundleUrl, context.doc);
|
|
217
213
|
}
|
|
218
214
|
catch (err) {
|
|
219
|
-
const cause =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
cause,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
throw new AdapterFailure(reasons);
|
|
215
|
+
const cause = errorMessage(err);
|
|
216
|
+
throw failureForEveryTag(elements, (tag) => ({
|
|
217
|
+
kind: "bundle-load-failed",
|
|
218
|
+
tag,
|
|
219
|
+
url: bundleUrl,
|
|
220
|
+
cause,
|
|
221
|
+
}));
|
|
230
222
|
}
|
|
231
223
|
const pieModule = readPieModule();
|
|
232
224
|
if (!pieModule) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
cause: "window.pie.default missing after bundle load",
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
throw new AdapterFailure(reasons);
|
|
225
|
+
throw failureForEveryTag(elements, (tag) => ({
|
|
226
|
+
kind: "bundle-load-failed",
|
|
227
|
+
tag,
|
|
228
|
+
url: bundleUrl,
|
|
229
|
+
cause: "window.pie.default missing after bundle load",
|
|
230
|
+
}));
|
|
243
231
|
}
|
|
244
232
|
const reasons = new Map();
|
|
245
233
|
const isEditorBundle = bundleType === BundleType.editor;
|
|
@@ -282,11 +270,13 @@ export function createIifeBackend(config) {
|
|
|
282
270
|
reasons.set(tag, {
|
|
283
271
|
kind: "define-failed",
|
|
284
272
|
tag,
|
|
285
|
-
cause:
|
|
273
|
+
cause: errorMessage(err),
|
|
286
274
|
});
|
|
287
275
|
continue;
|
|
288
276
|
}
|
|
289
277
|
try {
|
|
278
|
+
// `customElements` rejects a constructor already defined under
|
|
279
|
+
// another tag, so each versioned tag gets its own subclass.
|
|
290
280
|
defineCustomElementSafely(actualTag, class extends ElementClass {
|
|
291
281
|
}, `element tag for ${packageName}`);
|
|
292
282
|
}
|
|
@@ -294,7 +284,7 @@ export function createIifeBackend(config) {
|
|
|
294
284
|
reasons.set(tag, {
|
|
295
285
|
kind: "define-failed",
|
|
296
286
|
tag,
|
|
297
|
-
cause:
|
|
287
|
+
cause: errorMessage(err),
|
|
298
288
|
});
|
|
299
289
|
continue;
|
|
300
290
|
}
|
|
@@ -320,6 +310,17 @@ export function createIifeBackend(config) {
|
|
|
320
310
|
};
|
|
321
311
|
}
|
|
322
312
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
313
|
+
/** A failure of the whole request, reported once per tag. */
|
|
314
|
+
function failureForEveryTag(elements, reasonFor) {
|
|
315
|
+
const reasons = new Map();
|
|
316
|
+
for (const tag of Object.keys(elements)) {
|
|
317
|
+
reasons.set(tag, reasonFor(tag));
|
|
318
|
+
}
|
|
319
|
+
return new AdapterFailure(reasons);
|
|
320
|
+
}
|
|
321
|
+
function errorMessage(err) {
|
|
322
|
+
return err instanceof Error ? err.message : String(err);
|
|
323
|
+
}
|
|
323
324
|
function defaultLoadBundleScript(url, doc) {
|
|
324
325
|
return new Promise((resolve, reject) => {
|
|
325
326
|
const script = doc.createElement("script");
|
package/dist/loaders/index.d.ts
CHANGED
|
@@ -37,12 +37,14 @@ export { assertElementPackagesAllowed, ElementPackagePolicyError, } from "./elem
|
|
|
37
37
|
export type { BackendContext, BackendOption, ElementLoaderBackend, ElementTag, EnsureRegisteredOptions, EsmBackendConfig, IifeBackendConfig, RegistrationFailureReason, } from "./element-loader.js";
|
|
38
38
|
export { AdapterFailure, ElementAssertionError, ElementLoaderError, assertRegistered, describeRegistrationFailures, ensureRegistered, } from "./element-loader.js";
|
|
39
39
|
export type { BuiltInEsmCdnProviderName, EsmBackend, EsmCdnProvider, EsmCdnProviderName, EsmCdnProviderOption, ViewConfig, } from "./esm-adapter.js";
|
|
40
|
-
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
40
|
+
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmAssetRoot, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
41
41
|
export type { IifeBackend, IifeBundleRetryStatus, } from "./iife-adapter.js";
|
|
42
|
+
export { forwardMathjaxEvents, MATHJAX_NO_ASSET_ROOT_EVENT, MATHJAX_VERSION_CONFLICT_EVENT, } from "./mathjax-events.js";
|
|
42
43
|
export { DEFAULT_BUNDLE_HOST, DEFAULT_ESM_CDN_URL } from "./defaults.js";
|
|
43
44
|
export { createIifeBackend } from "./iife-adapter.js";
|
|
44
45
|
export { resolveLoadControllers } from "./controller-loading.js";
|
|
45
|
-
export type { PreloadedController, PreloadedElement, } from "./preloaded-registration.js";
|
|
46
|
+
export type { PreloadedController, PreloadedElement, PreloadedRegistrationOptions, } from "./preloaded-registration.js";
|
|
47
|
+
export type { MathAssetOptions } from "../pie/math-assets.js";
|
|
46
48
|
export { registerPreloadedElements } from "./preloaded-registration.js";
|
|
47
49
|
export type { PreloadedElementSpecs } from "./preloaded-alignment.js";
|
|
48
50
|
export { alignPreloadedElementVersions } from "./preloaded-alignment.js";
|
package/dist/loaders/index.js
CHANGED
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
export { aggregateElements } from "./ElementLoader.js";
|
|
34
34
|
export { assertElementPackagesAllowed, ElementPackagePolicyError, } from "./element-package-policy.js";
|
|
35
35
|
export { AdapterFailure, ElementAssertionError, ElementLoaderError, assertRegistered, describeRegistrationFailures, ensureRegistered, } from "./element-loader.js";
|
|
36
|
-
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
36
|
+
export { BUILT_IN_VIEWS, createEsmBackend, mapEsmViewElements, resolveEsmAssetRoot, resolveEsmRuntimeSupportUrl, } from "./esm-adapter.js";
|
|
37
|
+
export { forwardMathjaxEvents, MATHJAX_NO_ASSET_ROOT_EVENT, MATHJAX_VERSION_CONFLICT_EVENT, } from "./mathjax-events.js";
|
|
37
38
|
export { DEFAULT_BUNDLE_HOST, DEFAULT_ESM_CDN_URL } from "./defaults.js";
|
|
38
39
|
export { createIifeBackend } from "./iife-adapter.js";
|
|
39
40
|
export { resolveLoadControllers } from "./controller-loading.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwards the MathJax adapter's page events to instrumentation.
|
|
3
|
+
*
|
|
4
|
+
* Every copy of the MathJax 4 adapter that browser ESM elements render with
|
|
5
|
+
* dispatches these on `window`, once per condition per page:
|
|
6
|
+
* `pie-mathjax-version-conflict` when MathJax 3 runs on the same page — an
|
|
7
|
+
* unsupported page, see
|
|
8
|
+
* docs/item-player/loading-strategies.md#one-mathjax-version-per-page — and
|
|
9
|
+
* `pie-mathjax-no-asset-root` when a copy has nowhere to load MathJax's files
|
|
10
|
+
* from, see docs/item-player/loading-strategies.md#mathjax-assets. The event
|
|
11
|
+
* names are repeated here because players do not depend on the element
|
|
12
|
+
* packages.
|
|
13
|
+
*/
|
|
14
|
+
import type { InstrumentationProvider } from "../instrumentation/types.js";
|
|
15
|
+
export declare const MATHJAX_VERSION_CONFLICT_EVENT = "pie-mathjax-version-conflict";
|
|
16
|
+
export declare const MATHJAX_NO_ASSET_ROOT_EVENT = "pie-mathjax-no-asset-root";
|
|
17
|
+
/**
|
|
18
|
+
* Tracks each MathJax event on `target` with the provider `getProvider`
|
|
19
|
+
* returns when it fires, once per provider. Returns the function that stops
|
|
20
|
+
* forwarding.
|
|
21
|
+
*/
|
|
22
|
+
export declare function forwardMathjaxEvents(target: EventTarget, getProvider: () => InstrumentationProvider | undefined): () => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwards the MathJax adapter's page events to instrumentation.
|
|
3
|
+
*
|
|
4
|
+
* Every copy of the MathJax 4 adapter that browser ESM elements render with
|
|
5
|
+
* dispatches these on `window`, once per condition per page:
|
|
6
|
+
* `pie-mathjax-version-conflict` when MathJax 3 runs on the same page — an
|
|
7
|
+
* unsupported page, see
|
|
8
|
+
* docs/item-player/loading-strategies.md#one-mathjax-version-per-page — and
|
|
9
|
+
* `pie-mathjax-no-asset-root` when a copy has nowhere to load MathJax's files
|
|
10
|
+
* from, see docs/item-player/loading-strategies.md#mathjax-assets. The event
|
|
11
|
+
* names are repeated here because players do not depend on the element
|
|
12
|
+
* packages.
|
|
13
|
+
*/
|
|
14
|
+
export const MATHJAX_VERSION_CONFLICT_EVENT = "pie-mathjax-version-conflict";
|
|
15
|
+
export const MATHJAX_NO_ASSET_ROOT_EVENT = "pie-mathjax-no-asset-root";
|
|
16
|
+
/** The detail fields each event is tracked with. */
|
|
17
|
+
const TRACKED_FIELDS = {
|
|
18
|
+
[MATHJAX_VERSION_CONFLICT_EVENT]: ["condition", "docsUrl"],
|
|
19
|
+
[MATHJAX_NO_ASSET_ROOT_EVENT]: ["effect", "docsUrl"],
|
|
20
|
+
};
|
|
21
|
+
// Every player on a page listens, and each player bundle carries its own copy
|
|
22
|
+
// of this module, so the providers that tracked an event are kept on the page.
|
|
23
|
+
const TRACKED = Symbol.for("@pie-players/pie-players-shared/mathjax-version-conflict-tracked");
|
|
24
|
+
function providersThatTracked(event) {
|
|
25
|
+
const registry = globalThis;
|
|
26
|
+
registry[TRACKED] ??= new WeakMap();
|
|
27
|
+
let providers = registry[TRACKED].get(event);
|
|
28
|
+
if (!providers) {
|
|
29
|
+
providers = new WeakSet();
|
|
30
|
+
registry[TRACKED].set(event, providers);
|
|
31
|
+
}
|
|
32
|
+
return providers;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Tracks each MathJax event on `target` with the provider `getProvider`
|
|
36
|
+
* returns when it fires, once per provider. Returns the function that stops
|
|
37
|
+
* forwarding.
|
|
38
|
+
*/
|
|
39
|
+
export function forwardMathjaxEvents(target, getProvider) {
|
|
40
|
+
const forward = (event) => {
|
|
41
|
+
const provider = getProvider();
|
|
42
|
+
if (!provider)
|
|
43
|
+
return;
|
|
44
|
+
const tracked = providersThatTracked(event);
|
|
45
|
+
if (tracked.has(provider))
|
|
46
|
+
return;
|
|
47
|
+
tracked.add(provider);
|
|
48
|
+
const detail = event.detail;
|
|
49
|
+
const fields = (detail && typeof detail === "object" ? detail : {});
|
|
50
|
+
try {
|
|
51
|
+
provider.trackEvent(event.type, Object.fromEntries(TRACKED_FIELDS[event.type].map((field) => [field, fields[field]])));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Swallow: instrumentation must never break rendering.
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
for (const type of Object.keys(TRACKED_FIELDS)) {
|
|
58
|
+
target.addEventListener(type, forward);
|
|
59
|
+
}
|
|
60
|
+
return () => {
|
|
61
|
+
for (const type of Object.keys(TRACKED_FIELDS)) {
|
|
62
|
+
target.removeEventListener(type, forward);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
* player that is not hosted runs the controller's `model()` in the browser, so
|
|
16
16
|
* it needs the `controller` an entry registers; the item player warns about
|
|
17
17
|
* each tag it renders without one.
|
|
18
|
+
*
|
|
19
|
+
* Bundled by the host, the elements' copies of the MathJax adapter find no npm
|
|
20
|
+
* root in their module URLs, so the registration also takes where they load
|
|
21
|
+
* MathJax's fonts and speech from (`options.math`): a root, or each file's URL.
|
|
18
22
|
*/
|
|
23
|
+
import { type MathAssetOptions } from "../pie/math-assets.js";
|
|
19
24
|
/** A controller module: the `model()` a player that is not hosted runs. */
|
|
20
25
|
export type PreloadedController = {
|
|
21
26
|
readonly model: (...args: any[]) => unknown;
|
|
@@ -43,6 +48,16 @@ export interface PreloadedElement {
|
|
|
43
48
|
readonly default: PreloadedController;
|
|
44
49
|
};
|
|
45
50
|
}
|
|
51
|
+
export interface PreloadedRegistrationOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Where the elements' MathJax loads its fonts, its speech worker and, on the
|
|
54
|
+
* adapter's npm build, MathJax itself from, and whether math is in the tab
|
|
55
|
+
* order, written to `window['@pie-lib/math-rendering@2'].opts`. With neither
|
|
56
|
+
* an `assetRoot` nor the fonts in `assetUrls`, math renders without web fonts
|
|
57
|
+
* and speech.
|
|
58
|
+
*/
|
|
59
|
+
math?: MathAssetOptions;
|
|
60
|
+
}
|
|
46
61
|
/**
|
|
47
62
|
* Register host-bundled elements for the `preloaded` strategy.
|
|
48
63
|
*
|
|
@@ -51,6 +66,7 @@ export interface PreloadedElement {
|
|
|
51
66
|
* registry entry, which only gains a controller it lacked. Every entry is
|
|
52
67
|
* validated before any is registered. A package registers at one version per
|
|
53
68
|
* page, because the players align every authored version of a package to the
|
|
54
|
-
* registered one.
|
|
69
|
+
* registered one. `options.math` replaces the page's math asset options it
|
|
70
|
+
* sets, and keeps the others.
|
|
55
71
|
*/
|
|
56
|
-
export declare function registerPreloadedElements(elements: readonly PreloadedElement[]): void;
|
|
72
|
+
export declare function registerPreloadedElements(elements: readonly PreloadedElement[], options?: PreloadedRegistrationOptions): void;
|
|
@@ -15,8 +15,13 @@
|
|
|
15
15
|
* player that is not hosted runs the controller's `model()` in the browser, so
|
|
16
16
|
* it needs the `controller` an entry registers; the item player warns about
|
|
17
17
|
* each tag it renders without one.
|
|
18
|
+
*
|
|
19
|
+
* Bundled by the host, the elements' copies of the MathJax adapter find no npm
|
|
20
|
+
* root in their module URLs, so the registration also takes where they load
|
|
21
|
+
* MathJax's fonts and speech from (`options.math`): a root, or each file's URL.
|
|
18
22
|
*/
|
|
19
23
|
import { defineCustomElementSafely } from "../pie/custom-element-define.js";
|
|
24
|
+
import { mathAssetOptionsError, setMathAssetOptions, } from "../pie/math-assets.js";
|
|
20
25
|
import { writeRegistryEntry } from "../pie/registry.js";
|
|
21
26
|
import { BundleType, isCustomElementConstructor, Status } from "../pie/types.js";
|
|
22
27
|
import { parsePackageName } from "../pie/utils.js";
|
|
@@ -31,15 +36,22 @@ import { pickElementClass } from "./esm-adapter.js";
|
|
|
31
36
|
* registry entry, which only gains a controller it lacked. Every entry is
|
|
32
37
|
* validated before any is registered. A package registers at one version per
|
|
33
38
|
* page, because the players align every authored version of a package to the
|
|
34
|
-
* registered one.
|
|
39
|
+
* registered one. `options.math` replaces the page's math asset options it
|
|
40
|
+
* sets, and keeps the others.
|
|
35
41
|
*/
|
|
36
|
-
export function registerPreloadedElements(elements) {
|
|
42
|
+
export function registerPreloadedElements(elements, options = {}) {
|
|
37
43
|
if (typeof window === "undefined" || typeof customElements === "undefined") {
|
|
38
44
|
throw new Error("[registerPreloadedElements] requires a browser with customElements");
|
|
39
45
|
}
|
|
40
46
|
const preloaded = preloadedPackageSpecs();
|
|
41
47
|
const resolved = elements.map(resolveElement);
|
|
42
48
|
assertOneVersionPerPackage(resolved, preloaded);
|
|
49
|
+
const mathError = options.math === undefined ? undefined : mathAssetOptionsError(options.math);
|
|
50
|
+
if (mathError) {
|
|
51
|
+
throw new Error(`[registerPreloadedElements] options.math: ${mathError}`);
|
|
52
|
+
}
|
|
53
|
+
if (options.math)
|
|
54
|
+
setMathAssetOptions(options.math);
|
|
43
55
|
for (const { packageName, spec, tagName, elementClass, controller } of resolved) {
|
|
44
56
|
defineCustomElementSafely(tagName, class extends elementClass {
|
|
45
57
|
}, `preloaded element tag for ${packageName}`);
|
package/dist/media/index.d.ts
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
* Validation primitives shared by every catalog card whose payload references
|
|
3
3
|
* media.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Two copies of a URL allow-list is one copy that gets a fix and one that does
|
|
10
|
-
* not.
|
|
5
|
+
* `sign-language` and recorded `spoken` cards both hand authored, wire-facing
|
|
6
|
+
* URLs to a media element in the learner's browser, so both take the same
|
|
7
|
+
* scheme allow-list and the same "treat as absent, never as partially valid"
|
|
8
|
+
* posture from here.
|
|
11
9
|
*
|
|
12
10
|
* Lives here, behind its own subpath, so a PIE element can validate authored
|
|
13
11
|
* media without importing the assessment toolkit, whose root entry declares
|
package/dist/media/index.js
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
* Validation primitives shared by every catalog card whose payload references
|
|
3
3
|
* media.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Two copies of a URL allow-list is one copy that gets a fix and one that does
|
|
10
|
-
* not.
|
|
5
|
+
* `sign-language` and recorded `spoken` cards both hand authored, wire-facing
|
|
6
|
+
* URLs to a media element in the learner's browser, so both take the same
|
|
7
|
+
* scheme allow-list and the same "treat as absent, never as partially valid"
|
|
8
|
+
* posture from here.
|
|
11
9
|
*
|
|
12
10
|
* Lives here, behind its own subpath, so a PIE element can validate authored
|
|
13
11
|
* media without importing the assessment toolkit, whose root entry declares
|
|
@@ -21,7 +19,7 @@
|
|
|
21
19
|
* Relative and protocol-relative URLs are allowed — host content is commonly
|
|
22
20
|
* served from the same origin as the player.
|
|
23
21
|
*/
|
|
24
|
-
const
|
|
22
|
+
const URL_SCHEME_PREFIX = /^[a-z][a-z0-9+.-]*:/i;
|
|
25
23
|
const ALLOWED_SRC_SCHEMES = new Set(["http:", "https:", "data:", "blob:"]);
|
|
26
24
|
/**
|
|
27
25
|
* The only `MediaAssetRef.version` this build renders.
|
|
@@ -53,7 +51,7 @@ export function isSafeMediaSrc(raw) {
|
|
|
53
51
|
return false;
|
|
54
52
|
// Relative ("/video.mp4", "video.mp4") and protocol-relative ("//cdn/x.mp4")
|
|
55
53
|
// forms carry no scheme to check and inherit the document's.
|
|
56
|
-
if (src.startsWith("//") || !
|
|
54
|
+
if (src.startsWith("//") || !URL_SCHEME_PREFIX.test(src))
|
|
57
55
|
return true;
|
|
58
56
|
const scheme = src.slice(0, src.indexOf(":") + 1).toLowerCase();
|
|
59
57
|
return ALLOWED_SRC_SCHEMES.has(scheme);
|