@pie-players/pie-players-shared 1.0.0-beta.6 → 1.0.0-beta.8
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 +3 -1
- package/dist/i18n/catalogs.d.ts +20 -13
- package/dist/i18n/catalogs.js +22 -23
- package/dist/i18n/content-language.d.ts +46 -4
- package/dist/i18n/content-language.js +83 -5
- package/dist/i18n/index.d.ts +3 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/language-tags.d.ts +73 -4
- package/dist/i18n/language-tags.js +176 -6
- package/dist/i18n/messages/en-US.d.ts +23 -0
- package/dist/i18n/messages/en-US.js +25 -0
- package/dist/i18n/provider.d.ts +3 -6
- package/dist/i18n/provider.js +5 -15
- package/dist/i18n/types.d.ts +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/loaders/ElementLoader.d.ts +1 -1
- package/dist/loaders/element-loader.d.ts +4 -1
- package/dist/loaders/element-loader.js +74 -21
- package/dist/loaders/iife-adapter.d.ts +7 -0
- package/dist/loaders/iife-adapter.js +1 -1
- package/dist/security/private-hosts.d.ts +27 -0
- package/dist/security/private-hosts.js +85 -0
- package/dist/security/validate-style-url.js +2 -2
- package/dist/tools/term-lookup.d.ts +15 -0
- package/dist/tools/term-lookup.js +16 -0
- package/dist/types/index.d.ts +47 -27
- package/dist/types/interchange.d.ts +116 -0
- package/dist/types/interchange.js +1 -0
- package/dist/ui/focus-trap.js +3 -1
- package/dist/ui/fontawesome.d.ts +51 -0
- package/dist/ui/fontawesome.js +122 -0
- package/dist/ui/ime.d.ts +6 -0
- package/dist/ui/ime.js +10 -0
- package/package.json +15 -6
- package/dist/i18n/messages/nl-NL.d.ts +0 -636
- package/dist/i18n/messages/nl-NL.js +0 -636
|
@@ -301,6 +301,12 @@ declare const enUS: {
|
|
|
301
301
|
lanthanide: string;
|
|
302
302
|
actinide: string;
|
|
303
303
|
};
|
|
304
|
+
/** Standard-state phase, keyed by the data file's lowercased `phase` value. */
|
|
305
|
+
phaseValue: {
|
|
306
|
+
solid: string;
|
|
307
|
+
liquid: string;
|
|
308
|
+
gas: string;
|
|
309
|
+
};
|
|
304
310
|
};
|
|
305
311
|
ruler: {
|
|
306
312
|
name: string;
|
|
@@ -431,6 +437,12 @@ declare const enUS: {
|
|
|
431
437
|
description: string;
|
|
432
438
|
buttonA11y: string;
|
|
433
439
|
tooltip: string;
|
|
440
|
+
/** A choice's toggle; `{choice}` is the choice's own accessible name. */
|
|
441
|
+
toggleA11y: string;
|
|
442
|
+
/** Appended to an eliminated choice's label for assistive technology. */
|
|
443
|
+
eliminatedA11y: string;
|
|
444
|
+
/** Stands in for `{choice}` when a choice has no text of its own. */
|
|
445
|
+
unlabeledChoice: string;
|
|
434
446
|
};
|
|
435
447
|
highlighter: {
|
|
436
448
|
name: string;
|
|
@@ -456,6 +468,17 @@ declare const enUS: {
|
|
|
456
468
|
highlightPinkA11y: string;
|
|
457
469
|
highlightBlueA11y: string;
|
|
458
470
|
highlightGreenA11y: string;
|
|
471
|
+
selectionAvailableA11y: string;
|
|
472
|
+
highlightedYellowA11y: string;
|
|
473
|
+
highlightedPinkA11y: string;
|
|
474
|
+
highlightedBlueA11y: string;
|
|
475
|
+
highlightedGreenA11y: string;
|
|
476
|
+
underlinedA11y: string;
|
|
477
|
+
removedA11y: string;
|
|
478
|
+
clearedA11y: {
|
|
479
|
+
one: string;
|
|
480
|
+
other: string;
|
|
481
|
+
};
|
|
459
482
|
};
|
|
460
483
|
theme: {
|
|
461
484
|
name: string;
|
|
@@ -306,6 +306,12 @@ const enUS = {
|
|
|
306
306
|
lanthanide: "Lanthanide",
|
|
307
307
|
actinide: "Actinide",
|
|
308
308
|
},
|
|
309
|
+
/** Standard-state phase, keyed by the data file's lowercased `phase` value. */
|
|
310
|
+
phaseValue: {
|
|
311
|
+
solid: "Solid",
|
|
312
|
+
liquid: "Liquid",
|
|
313
|
+
gas: "Gas",
|
|
314
|
+
},
|
|
309
315
|
},
|
|
310
316
|
ruler: {
|
|
311
317
|
name: "Ruler",
|
|
@@ -448,6 +454,12 @@ const enUS = {
|
|
|
448
454
|
description: "Strike through answer choices",
|
|
449
455
|
buttonA11y: "Strike Through, eliminate answer choices",
|
|
450
456
|
tooltip: "Strike Through",
|
|
457
|
+
/** A choice's toggle; `{choice}` is the choice's own accessible name. */
|
|
458
|
+
toggleA11y: "Toggle elimination for {choice}",
|
|
459
|
+
/** Appended to an eliminated choice's label for assistive technology. */
|
|
460
|
+
eliminatedA11y: "(eliminated)",
|
|
461
|
+
/** Stands in for `{choice}` when a choice has no text of its own. */
|
|
462
|
+
unlabeledChoice: "Unlabeled choice",
|
|
451
463
|
},
|
|
452
464
|
highlighter: {
|
|
453
465
|
name: "Highlighter",
|
|
@@ -476,6 +488,19 @@ const enUS = {
|
|
|
476
488
|
highlightPinkA11y: "Pink highlight",
|
|
477
489
|
highlightBlueA11y: "Blue highlight",
|
|
478
490
|
highlightGreenA11y: "Green highlight",
|
|
491
|
+
// Announced in the strip's live region. A colour is authored per phrase:
|
|
492
|
+
// a colour adjective agrees with its noun in some languages.
|
|
493
|
+
selectionAvailableA11y: "Annotation toolbar available for “{text}”. Press Shift+F10 for annotation tools.",
|
|
494
|
+
highlightedYellowA11y: "“{text}” highlighted in yellow",
|
|
495
|
+
highlightedPinkA11y: "“{text}” highlighted in pink",
|
|
496
|
+
highlightedBlueA11y: "“{text}” highlighted in blue",
|
|
497
|
+
highlightedGreenA11y: "“{text}” highlighted in green",
|
|
498
|
+
underlinedA11y: "“{text}” underlined",
|
|
499
|
+
removedA11y: "Removed annotation from “{text}”",
|
|
500
|
+
clearedA11y: {
|
|
501
|
+
one: "{count} annotation cleared",
|
|
502
|
+
other: "{count} annotations cleared",
|
|
503
|
+
},
|
|
479
504
|
},
|
|
480
505
|
theme: {
|
|
481
506
|
name: "Theme",
|
package/dist/i18n/provider.d.ts
CHANGED
|
@@ -122,12 +122,9 @@ export declare class SimpleI18n implements I18nServiceApi {
|
|
|
122
122
|
/** The resolved catalog for a locale, for a host that wants to inspect it. */
|
|
123
123
|
getBundle(locale?: LocaleCode): TranslationBundle | undefined;
|
|
124
124
|
/**
|
|
125
|
-
* Which shipped catalog serves a requested tag
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* primary-subtag widening, so a bare `nl` reaches the regional `nl-NL`
|
|
129
|
-
* catalog we actually ship. POSIX `nl_NL` normalizes on the way in, which is
|
|
130
|
-
* the form the Learnosity transform emits.
|
|
125
|
+
* Which shipped catalog serves a requested tag, under
|
|
126
|
+
* `findClosestLanguageMatch`. POSIX `nl_NL` normalizes on the way in, which
|
|
127
|
+
* is the form the Learnosity transform emits.
|
|
131
128
|
*/
|
|
132
129
|
private resolveLocaleTag;
|
|
133
130
|
private loadCatalog;
|
package/dist/i18n/provider.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module @pie-players/pie-players-shared/i18n/provider
|
|
11
11
|
*/
|
|
12
|
-
import { canonicalLanguageTag,
|
|
12
|
+
import { canonicalLanguageTag, findClosestLanguageMatch, normalizeLanguageTag, toLanguageTag, } from "./language-tags.js";
|
|
13
13
|
import enUS from "./messages/en-US.js";
|
|
14
14
|
/** The locale every fallback chain terminates at. */
|
|
15
15
|
export const DEFAULT_LOCALE = "en-US";
|
|
@@ -302,24 +302,14 @@ export class SimpleI18n {
|
|
|
302
302
|
return { locale: tag, translations, direction: localeDirection(tag) };
|
|
303
303
|
}
|
|
304
304
|
/**
|
|
305
|
-
* Which shipped catalog serves a requested tag
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* primary-subtag widening, so a bare `nl` reaches the regional `nl-NL`
|
|
309
|
-
* catalog we actually ship. POSIX `nl_NL` normalizes on the way in, which is
|
|
310
|
-
* the form the Learnosity transform emits.
|
|
305
|
+
* Which shipped catalog serves a requested tag, under
|
|
306
|
+
* `findClosestLanguageMatch`. POSIX `nl_NL` normalizes on the way in, which
|
|
307
|
+
* is the form the Learnosity transform emits.
|
|
311
308
|
*/
|
|
312
309
|
resolveLocaleTag(locale) {
|
|
313
310
|
if (!locale)
|
|
314
311
|
return undefined;
|
|
315
|
-
|
|
316
|
-
const exactOrPrefix = findBestLanguageMatch(locale, available);
|
|
317
|
-
if (exactOrPrefix)
|
|
318
|
-
return exactOrPrefix;
|
|
319
|
-
const requestedLanguage = normalizeLanguageTag(locale).split("-")[0];
|
|
320
|
-
if (!requestedLanguage)
|
|
321
|
-
return undefined;
|
|
322
|
-
return available.find((candidate) => normalizeLanguageTag(candidate).split("-")[0] === requestedLanguage);
|
|
312
|
+
return findClosestLanguageMatch(locale, this.getAvailableLocales());
|
|
323
313
|
}
|
|
324
314
|
async loadCatalog(locale) {
|
|
325
315
|
const load = this.config.loadCatalog;
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -16,8 +16,13 @@ import type enUS from "./messages/en-US.js";
|
|
|
16
16
|
* syntax — the Learnosity transform emits POSIX `nl_NL`, QTI catalog cards carry
|
|
17
17
|
* `xml:lang` — and the resolver normalizes both onto these, so this list is the
|
|
18
18
|
* set of catalogs, not the set of accepted inputs.
|
|
19
|
+
*
|
|
20
|
+
* `en-US` is the only bundled catalog: the statically resident source of truth
|
|
21
|
+
* every other locale falls back through. Every other locale this repository
|
|
22
|
+
* ships lives in the optional generated tier
|
|
23
|
+
* (`@pie-players/pie-i18n-generated`), which a host composes into its loader.
|
|
19
24
|
*/
|
|
20
|
-
export type BundledLocaleCode = "en-US"
|
|
25
|
+
export type BundledLocaleCode = "en-US";
|
|
21
26
|
/**
|
|
22
27
|
* Any locale tag. A host may supply a locale we ship no catalog for and provide
|
|
23
28
|
* its own messages through {@link I18nConfig.customMessages}, so this is
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./ui/pointer-rotate.js";
|
|
|
16
16
|
export * from "./ui/overlay-containment.js";
|
|
17
17
|
export * from "./ui/overlay-placement.js";
|
|
18
18
|
export * from "./ui/focus-trap.js";
|
|
19
|
+
export * from "./ui/ime.js";
|
|
19
20
|
export * from "./ui/first-focusable.js";
|
|
20
21
|
export * from "./ui/safe-storage.js";
|
|
21
22
|
export * from "./ui/scope-css.js";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./ui/pointer-rotate.js";
|
|
|
15
15
|
export * from "./ui/overlay-containment.js";
|
|
16
16
|
export * from "./ui/overlay-placement.js";
|
|
17
17
|
export * from "./ui/focus-trap.js";
|
|
18
|
+
export * from "./ui/ime.js";
|
|
18
19
|
export * from "./ui/first-focusable.js";
|
|
19
20
|
export * from "./ui/safe-storage.js";
|
|
20
21
|
export * from "./ui/scope-css.js";
|
|
@@ -88,7 +88,10 @@ export type EnsureRegisteredOptions = {
|
|
|
88
88
|
*
|
|
89
89
|
* The primitive is the sole authority on registration state:
|
|
90
90
|
* - Empty element map resolves immediately (no backend call).
|
|
91
|
-
* - Already-registered tags resolve immediately (no backend call).
|
|
91
|
+
* - Already-registered tags resolve immediately (no backend call). A request
|
|
92
|
+
* that loads other tags through an IIFE or ESM backend that loads controllers
|
|
93
|
+
* also loads its registered tags with no controller, so their controllers
|
|
94
|
+
* fill in.
|
|
92
95
|
* - Concurrent identical requests share one backend call.
|
|
93
96
|
* - After the adapter's `load` settles, a post-load verification pass
|
|
94
97
|
* (bounded `customElements.whenDefined`) checks every tag. Any missing
|
|
@@ -26,7 +26,7 @@ import { parsePackageName } from "../pie/utils.js";
|
|
|
26
26
|
import { parseVersionedTagName } from "../pie/versioned-tag.js";
|
|
27
27
|
import { AdapterFailure, } from "./element-loader-types.js";
|
|
28
28
|
import { clearPackageMetadataRequests, createEsmBackend, } from "./esm-adapter.js";
|
|
29
|
-
import { createIifeBackend } from "./iife-adapter.js";
|
|
29
|
+
import { createIifeBackend, findIifeElementMapConflict, } from "./iife-adapter.js";
|
|
30
30
|
import { assertElementPackagesAllowed, } from "./element-package-policy.js";
|
|
31
31
|
export { AdapterFailure } from "./element-loader-types.js";
|
|
32
32
|
/**
|
|
@@ -125,9 +125,10 @@ function registeredPackageName(tag) {
|
|
|
125
125
|
}
|
|
126
126
|
const DEFAULT_WHEN_DEFINED_TIMEOUT_MS = 5000;
|
|
127
127
|
const DEFAULT_LOAD_TIMEOUT_MS = DEFAULT_IIFE_BUNDLE_RETRY_CONFIG.timeoutMs;
|
|
128
|
-
//
|
|
129
|
-
//
|
|
128
|
+
// Requests share a backend call by both the original map and the unresolved
|
|
129
|
+
// subset, so incremental registration does not change an in-flight identity.
|
|
130
130
|
const inFlightRequests = new Map();
|
|
131
|
+
const inFlightKeysByPromise = new Map();
|
|
131
132
|
// An ESM backend keeps page state that later loads build on (the import maps it
|
|
132
133
|
// injected, the shared dependency versions they fixed), so every load with the
|
|
133
134
|
// same config reuses one backend. IIFE backends are created per call.
|
|
@@ -139,7 +140,10 @@ const resolvedBackendOverrides = new Map();
|
|
|
139
140
|
*
|
|
140
141
|
* The primitive is the sole authority on registration state:
|
|
141
142
|
* - Empty element map resolves immediately (no backend call).
|
|
142
|
-
* - Already-registered tags resolve immediately (no backend call).
|
|
143
|
+
* - Already-registered tags resolve immediately (no backend call). A request
|
|
144
|
+
* that loads other tags through an IIFE or ESM backend that loads controllers
|
|
145
|
+
* also loads its registered tags with no controller, so their controllers
|
|
146
|
+
* fill in.
|
|
143
147
|
* - Concurrent identical requests share one backend call.
|
|
144
148
|
* - After the adapter's `load` settles, a post-load verification pass
|
|
145
149
|
* (bounded `customElements.whenDefined`) checks every tag. Any missing
|
|
@@ -153,30 +157,54 @@ export async function ensureRegistered(elements, options) {
|
|
|
153
157
|
// Enforce policy before the already-registered fast path: a matching tag in
|
|
154
158
|
// the registry must not turn a disallowed authored package into valid input.
|
|
155
159
|
assertElementPackagesAllowed(elements, options.elementPackagePolicy);
|
|
156
|
-
|
|
160
|
+
// Preserve the original request identity while tags register mid-flight.
|
|
161
|
+
const originalDedupKey = buildDedupKey(options.backend, elements);
|
|
162
|
+
const originalRequest = inFlightRequests.get(originalDedupKey);
|
|
163
|
+
if (originalRequest)
|
|
164
|
+
return originalRequest;
|
|
165
|
+
if (Object.keys(elements).every(isRegistered))
|
|
166
|
+
return;
|
|
167
|
+
const loadsControllers = backendLoadsControllers(options.backend);
|
|
168
|
+
const unresolvedElements = {};
|
|
169
|
+
for (const [tag, packageSpec] of Object.entries(elements)) {
|
|
170
|
+
if (!isRegistered(tag) || (loadsControllers && awaitsController(tag))) {
|
|
171
|
+
unresolvedElements[tag] = packageSpec;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const tags = Object.keys(unresolvedElements);
|
|
157
175
|
const timeoutMs = options.whenDefinedTimeoutMs ?? DEFAULT_WHEN_DEFINED_TIMEOUT_MS;
|
|
158
176
|
const loadTimeoutMs = options.loadTimeoutMs ?? DEFAULT_LOAD_TIMEOUT_MS;
|
|
159
|
-
|
|
160
|
-
// bookkeeping entirely.
|
|
161
|
-
if (allAlreadyRegistered(tags))
|
|
162
|
-
return;
|
|
163
|
-
const dedupKey = buildDedupKey(options.backend, elements);
|
|
177
|
+
const dedupKey = buildDedupKey(options.backend, unresolvedElements);
|
|
164
178
|
const existing = inFlightRequests.get(dedupKey);
|
|
165
|
-
if (existing)
|
|
179
|
+
if (existing) {
|
|
180
|
+
aliasInFlightRequest(originalDedupKey, existing);
|
|
166
181
|
return existing;
|
|
182
|
+
}
|
|
167
183
|
const backend = resolveBackend(options.backend);
|
|
168
184
|
const doc = resolveDoc(options.doc);
|
|
169
|
-
const
|
|
170
|
-
|
|
185
|
+
const loadElements = isIifeConfig(options.backend)
|
|
186
|
+
? iifeLoadElements(elements, unresolvedElements)
|
|
187
|
+
: unresolvedElements;
|
|
188
|
+
const promise = runEnsureRegistered(loadElements, tags, backend, { doc, whenDefinedTimeoutMs: timeoutMs }, timeoutMs, loadTimeoutMs);
|
|
189
|
+
aliasInFlightRequest(originalDedupKey, promise);
|
|
190
|
+
aliasInFlightRequest(dedupKey, promise);
|
|
171
191
|
try {
|
|
172
192
|
await promise;
|
|
173
193
|
}
|
|
174
194
|
finally {
|
|
175
|
-
|
|
176
|
-
inFlightRequests.
|
|
195
|
+
for (const key of inFlightKeysByPromise.get(promise) ?? []) {
|
|
196
|
+
if (inFlightRequests.get(key) === promise)
|
|
197
|
+
inFlightRequests.delete(key);
|
|
177
198
|
}
|
|
199
|
+
inFlightKeysByPromise.delete(promise);
|
|
178
200
|
}
|
|
179
201
|
}
|
|
202
|
+
function aliasInFlightRequest(key, promise) {
|
|
203
|
+
inFlightRequests.set(key, promise);
|
|
204
|
+
const keys = inFlightKeysByPromise.get(promise) ?? new Set();
|
|
205
|
+
keys.add(key);
|
|
206
|
+
inFlightKeysByPromise.set(promise, keys);
|
|
207
|
+
}
|
|
180
208
|
async function runEnsureRegistered(elements, tags, backend, context, timeoutMs, loadTimeoutMs) {
|
|
181
209
|
let adapterError;
|
|
182
210
|
try {
|
|
@@ -273,13 +301,13 @@ function resolveBackend(backend) {
|
|
|
273
301
|
if (isDirectBackend(backend))
|
|
274
302
|
return backend;
|
|
275
303
|
if ("kind" in backend) {
|
|
304
|
+
const backendKey = backendKeyOf(backend);
|
|
305
|
+
const override = resolvedBackendOverrides.get(backendKey);
|
|
306
|
+
if (override)
|
|
307
|
+
return override;
|
|
276
308
|
if (backend.kind === "iife")
|
|
277
309
|
return createIifeBackend(backend);
|
|
278
310
|
if (backend.kind === "esm") {
|
|
279
|
-
const backendKey = backendKeyOf(backend);
|
|
280
|
-
const override = resolvedBackendOverrides.get(backendKey);
|
|
281
|
-
if (override)
|
|
282
|
-
return override;
|
|
283
311
|
const existing = resolvedEsmBackends.get(backendKey);
|
|
284
312
|
if (existing)
|
|
285
313
|
return existing;
|
|
@@ -415,8 +443,32 @@ function getOpaqueObjectRefId(value) {
|
|
|
415
443
|
}
|
|
416
444
|
return id;
|
|
417
445
|
}
|
|
418
|
-
function
|
|
419
|
-
return
|
|
446
|
+
function isIifeConfig(backend) {
|
|
447
|
+
return !isDirectBackend(backend) && "kind" in backend && backend.kind === "iife";
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* An IIFE bundle URL names its packages, so the full map keeps it the URL a
|
|
451
|
+
* host already fetched. Two specs of one package cannot share a bundle, so a
|
|
452
|
+
* conflict narrows the map: first to the unresolved tags, then to the undefined.
|
|
453
|
+
*/
|
|
454
|
+
function iifeLoadElements(elements, unresolvedElements) {
|
|
455
|
+
if (!findIifeElementMapConflict(elements))
|
|
456
|
+
return elements;
|
|
457
|
+
if (!findIifeElementMapConflict(unresolvedElements))
|
|
458
|
+
return unresolvedElements;
|
|
459
|
+
return Object.fromEntries(Object.entries(unresolvedElements).filter(([tag]) => !isRegistered(tag)));
|
|
460
|
+
}
|
|
461
|
+
function backendLoadsControllers(backend) {
|
|
462
|
+
if (isDirectBackend(backend) || !("kind" in backend))
|
|
463
|
+
return false;
|
|
464
|
+
if (backend.kind === "iife")
|
|
465
|
+
return backend.needsControllers ?? true;
|
|
466
|
+
if (backend.kind === "esm")
|
|
467
|
+
return backend.loadControllers ?? true;
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
function awaitsController(tag) {
|
|
471
|
+
return typeof window !== "undefined" && !pieRegistry()[tag]?.controller;
|
|
420
472
|
}
|
|
421
473
|
function isRegistered(tag) {
|
|
422
474
|
if (typeof customElements === "undefined")
|
|
@@ -471,6 +523,7 @@ function extractAdapterReason(err, tag) {
|
|
|
471
523
|
export const __testing = {
|
|
472
524
|
resetDedupState() {
|
|
473
525
|
inFlightRequests.clear();
|
|
526
|
+
inFlightKeysByPromise.clear();
|
|
474
527
|
resolvedEsmBackends.clear();
|
|
475
528
|
resolvedBackendOverrides.clear();
|
|
476
529
|
clearPackageMetadataRequests();
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { InstrumentationProvider } from "../instrumentation/types.js";
|
|
22
22
|
import { type IifeBundleRetryConfig } from "../loader-config.js";
|
|
23
23
|
import { BundleType } from "../pie/types.js";
|
|
24
|
+
import type { ElementMap } from "./ElementLoader.js";
|
|
24
25
|
import { type ElementLoaderBackend } from "./element-loader-types.js";
|
|
25
26
|
/**
|
|
26
27
|
* Bundle-build retry status emitted while a bundle is being built or after
|
|
@@ -77,4 +78,10 @@ export type IifeBackendConfig = {
|
|
|
77
78
|
instrumentationProvider?: InstrumentationProvider;
|
|
78
79
|
};
|
|
79
80
|
export type IifeBackend = ElementLoaderBackend & {};
|
|
81
|
+
/**
|
|
82
|
+
* The legacy IIFE bundle global is keyed by unversioned package name. Two
|
|
83
|
+
* distinct specs for one package therefore cannot coexist in a single bundle:
|
|
84
|
+
* one constructor would silently be reused for both requested versioned tags.
|
|
85
|
+
*/
|
|
86
|
+
export declare function findIifeElementMapConflict(elements: ElementMap): string | undefined;
|
|
80
87
|
export declare function createIifeBackend(config: IifeBackendConfig): IifeBackend;
|
|
@@ -31,7 +31,7 @@ import { AdapterFailure, } from "./element-loader-types.js";
|
|
|
31
31
|
* distinct specs for one package therefore cannot coexist in a single bundle:
|
|
32
32
|
* one constructor would silently be reused for both requested versioned tags.
|
|
33
33
|
*/
|
|
34
|
-
function findIifeElementMapConflict(elements) {
|
|
34
|
+
export function findIifeElementMapConflict(elements) {
|
|
35
35
|
const specsByPackageName = new Map();
|
|
36
36
|
for (const packageSpecValue of Object.values(elements)) {
|
|
37
37
|
const packageSpec = String(packageSpecValue);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostname classification for a URL a server fetches on someone else's behalf,
|
|
3
|
+
* such as an SSML `<audio src>` a TTS provider will request or an asset URL an
|
|
4
|
+
* upstream service returns. Every such check reads this module, so no two
|
|
5
|
+
* copies of it can drift apart.
|
|
6
|
+
*
|
|
7
|
+
* IP literals are classified by `ipaddr.js`, whose ranges follow the IANA IPv4
|
|
8
|
+
* and IPv6 Special-Purpose Address Registries (RFC 6890). A host is private
|
|
9
|
+
* unless it is a public unicast address or a name: loopback, private, link-local,
|
|
10
|
+
* unique-local, carrier-grade NAT, multicast, broadcast, documentation and
|
|
11
|
+
* reserved ranges, and the IPv6 forms that embed an IPv4 address (NAT64, 6to4,
|
|
12
|
+
* Teredo), all count. `ipaddr.js` also reads the numeric IPv4 forms (`2130706433`,
|
|
13
|
+
* `0x7f000001`, `0177.0.0.1`) and unmaps IPv4-mapped IPv6.
|
|
14
|
+
*
|
|
15
|
+
* Only the literal is checked. A public name that resolves to a private address
|
|
16
|
+
* passes; guarding that takes a check of the resolved address at connect time.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Whether `hostname` is a cloud instance-metadata endpoint. */
|
|
21
|
+
export declare function isCloudMetadataHostname(hostname: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether `hostname` is anything other than a public unicast address or a
|
|
24
|
+
* public name: `localhost` and its subdomains (RFC 6761), or an IP literal in
|
|
25
|
+
* any special-purpose range.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isPrivateHostname(hostname: string): boolean;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostname classification for a URL a server fetches on someone else's behalf,
|
|
3
|
+
* such as an SSML `<audio src>` a TTS provider will request or an asset URL an
|
|
4
|
+
* upstream service returns. Every such check reads this module, so no two
|
|
5
|
+
* copies of it can drift apart.
|
|
6
|
+
*
|
|
7
|
+
* IP literals are classified by `ipaddr.js`, whose ranges follow the IANA IPv4
|
|
8
|
+
* and IPv6 Special-Purpose Address Registries (RFC 6890). A host is private
|
|
9
|
+
* unless it is a public unicast address or a name: loopback, private, link-local,
|
|
10
|
+
* unique-local, carrier-grade NAT, multicast, broadcast, documentation and
|
|
11
|
+
* reserved ranges, and the IPv6 forms that embed an IPv4 address (NAT64, 6to4,
|
|
12
|
+
* Teredo), all count. `ipaddr.js` also reads the numeric IPv4 forms (`2130706433`,
|
|
13
|
+
* `0x7f000001`, `0177.0.0.1`) and unmaps IPv4-mapped IPv6.
|
|
14
|
+
*
|
|
15
|
+
* Only the literal is checked. A public name that resolves to a private address
|
|
16
|
+
* passes; guarding that takes a check of the resolved address at connect time.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import ipaddr from "ipaddr.js";
|
|
21
|
+
/**
|
|
22
|
+
* Cloud instance-metadata hostnames. They hand out credentials. No registry
|
|
23
|
+
* lists them; each is the provider's documented name.
|
|
24
|
+
*/
|
|
25
|
+
const CLOUD_METADATA_HOSTNAMES = new Set([
|
|
26
|
+
"metadata.google.internal",
|
|
27
|
+
"metadata.azure.internal",
|
|
28
|
+
"metadata.packet.net",
|
|
29
|
+
"metadata.oci.oraclecloud.com",
|
|
30
|
+
]);
|
|
31
|
+
/** Cloud instance-metadata addresses: the link-local one most clouds use, and AWS's IPv6 one. */
|
|
32
|
+
const CLOUD_METADATA_ADDRESSES = ["169.254.169.254", "fd00:ec2::254"].map((address) => ipaddr.parse(address));
|
|
33
|
+
/** Lower-cased, without brackets or a trailing dot. */
|
|
34
|
+
function normalizeHostname(hostname) {
|
|
35
|
+
return hostname
|
|
36
|
+
.replace(/^\[|\]$/g, "")
|
|
37
|
+
.replace(/\.$/, "")
|
|
38
|
+
.toLowerCase();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The address `host` is a literal for, IPv4-mapped (`::ffff:a.b.c.d`) and
|
|
42
|
+
* IPv4-compatible (`::a.b.c.d`) IPv6 reduced to IPv4, or `null` for a name.
|
|
43
|
+
*/
|
|
44
|
+
function parseAddress(host) {
|
|
45
|
+
if (!ipaddr.isValid(host))
|
|
46
|
+
return null;
|
|
47
|
+
const address = ipaddr.process(host);
|
|
48
|
+
if (address.kind() !== "ipv6")
|
|
49
|
+
return address;
|
|
50
|
+
// `ipaddr.js` classifies deprecated IPv4-compatible addresses as unicast;
|
|
51
|
+
// read the IPv4 address in their low 32 bits instead.
|
|
52
|
+
const { parts } = address;
|
|
53
|
+
const isIpv4Compatible = parts.slice(0, 6).every((part) => part === 0) &&
|
|
54
|
+
(parts[6] !== 0 || parts[7] > 1);
|
|
55
|
+
return isIpv4Compatible
|
|
56
|
+
? new ipaddr.IPv4([
|
|
57
|
+
parts[6] >> 8,
|
|
58
|
+
parts[6] & 0xff,
|
|
59
|
+
parts[7] >> 8,
|
|
60
|
+
parts[7] & 0xff,
|
|
61
|
+
])
|
|
62
|
+
: address;
|
|
63
|
+
}
|
|
64
|
+
/** Whether `hostname` is a cloud instance-metadata endpoint. */
|
|
65
|
+
export function isCloudMetadataHostname(hostname) {
|
|
66
|
+
const host = normalizeHostname(hostname);
|
|
67
|
+
if (CLOUD_METADATA_HOSTNAMES.has(host))
|
|
68
|
+
return true;
|
|
69
|
+
const address = parseAddress(host);
|
|
70
|
+
return (address !== null &&
|
|
71
|
+
CLOUD_METADATA_ADDRESSES.some((metadata) => metadata.kind() === address.kind() &&
|
|
72
|
+
metadata.toNormalizedString() === address.toNormalizedString()));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether `hostname` is anything other than a public unicast address or a
|
|
76
|
+
* public name: `localhost` and its subdomains (RFC 6761), or an IP literal in
|
|
77
|
+
* any special-purpose range.
|
|
78
|
+
*/
|
|
79
|
+
export function isPrivateHostname(hostname) {
|
|
80
|
+
const host = normalizeHostname(hostname);
|
|
81
|
+
if (host === "localhost" || host.endsWith(".localhost"))
|
|
82
|
+
return true;
|
|
83
|
+
const address = parseAddress(host);
|
|
84
|
+
return address !== null && address.range() !== "unicast";
|
|
85
|
+
}
|
|
@@ -30,11 +30,11 @@ export function validateExternalStyleUrl(url, options = {}) {
|
|
|
30
30
|
? new URL(url, options.baseUrl)
|
|
31
31
|
: new URL(url);
|
|
32
32
|
}
|
|
33
|
-
catch
|
|
33
|
+
catch {
|
|
34
34
|
return {
|
|
35
35
|
ok: false,
|
|
36
36
|
reason: "invalid-url",
|
|
37
|
-
message:
|
|
37
|
+
message: "External stylesheet URL could not be parsed.",
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
if (resolvedUrl.protocol !== "http:" && resolvedUrl.protocol !== "https:") {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* corpus behind one is licensed per programme, so a host supplies either an endpoint
|
|
14
14
|
* or a resolver of its own.
|
|
15
15
|
*/
|
|
16
|
+
import type { TextDirection } from "../i18n/types.js";
|
|
16
17
|
/** What a host's term service is asked for. */
|
|
17
18
|
export interface TermLookupRequest {
|
|
18
19
|
/** The term, already normalised by {@link normalizeTerm}. */
|
|
@@ -187,3 +188,17 @@ export declare function termPanelStatusMessage<TItem>(state: TermPanelState<TIte
|
|
|
187
188
|
countLabel: (count: number) => string;
|
|
188
189
|
emptyLabel: (term: string) => string;
|
|
189
190
|
}): string;
|
|
191
|
+
/**
|
|
192
|
+
* `lang` and `dir` for a lookup's results: the language the lookup asked in.
|
|
193
|
+
*
|
|
194
|
+
* Results are in the corpus's language, which is neither the panel's interface
|
|
195
|
+
* locale nor necessarily the content the term came from: a Spanish gloss for an
|
|
196
|
+
* English passage. Stamped on the results, an Arabic definition in an English panel
|
|
197
|
+
* reads and lays out as Arabic, and the panel's language never reaches the content.
|
|
198
|
+
* With no language named the corpus's is unknown, so the stamp is `lang=""` and
|
|
199
|
+
* `dir="auto"`, the one undeclared content gets.
|
|
200
|
+
*/
|
|
201
|
+
export declare function termResultLanguage(language: string | undefined): {
|
|
202
|
+
lang: string;
|
|
203
|
+
dir: TextDirection | "auto";
|
|
204
|
+
};
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* corpus behind one is licensed per programme, so a host supplies either an endpoint
|
|
14
14
|
* or a resolver of its own.
|
|
15
15
|
*/
|
|
16
|
+
import { toLanguageTag } from "../i18n/language-tags.js";
|
|
17
|
+
import { localeDirection } from "../i18n/provider.js";
|
|
16
18
|
/** Longest term worth sending. Past this it is prose, not a headword. */
|
|
17
19
|
export const MAX_LOOKUP_TERM_LENGTH = 80;
|
|
18
20
|
/** Words a term may span. Generous enough for `carbon dioxide` and `mother in law`. */
|
|
@@ -282,3 +284,17 @@ export function termPanelStatusMessage(state, labels) {
|
|
|
282
284
|
return labels.emptyLabel(state.term);
|
|
283
285
|
return "";
|
|
284
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* `lang` and `dir` for a lookup's results: the language the lookup asked in.
|
|
289
|
+
*
|
|
290
|
+
* Results are in the corpus's language, which is neither the panel's interface
|
|
291
|
+
* locale nor necessarily the content the term came from: a Spanish gloss for an
|
|
292
|
+
* English passage. Stamped on the results, an Arabic definition in an English panel
|
|
293
|
+
* reads and lays out as Arabic, and the panel's language never reaches the content.
|
|
294
|
+
* With no language named the corpus's is unknown, so the stamp is `lang=""` and
|
|
295
|
+
* `dir="auto"`, the one undeclared content gets.
|
|
296
|
+
*/
|
|
297
|
+
export function termResultLanguage(language) {
|
|
298
|
+
const tag = toLanguageTag(language);
|
|
299
|
+
return tag ? { lang: tag, dir: localeDirection(tag) } : { lang: "", dir: "auto" };
|
|
300
|
+
}
|