@pie-players/pie-players-shared 0.3.73 → 0.3.75
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 +30 -2
- package/dist/components/vendor/nds/README.md +7 -5
- package/dist/config/profile.d.ts +0 -2
- package/dist/config/profile.js +2 -8
- package/dist/i18n/index.d.ts +2 -3
- package/dist/i18n/index.js +2 -3
- package/dist/i18n/messages/en-US.d.ts +11 -24
- package/dist/i18n/messages/en-US.js +13 -26
- package/dist/i18n/messages/nl-NL.d.ts +7 -24
- package/dist/i18n/messages/nl-NL.js +9 -26
- package/dist/i18n/provider.d.ts +4 -5
- package/dist/i18n/provider.js +4 -5
- package/dist/i18n/types.d.ts +4 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/instrumentation/index.d.ts +5 -4
- package/dist/instrumentation/index.js +5 -4
- package/dist/instrumentation/new-relic-agent.d.ts +20 -0
- package/dist/instrumentation/new-relic-agent.js +24 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +5 -4
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +7 -7
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +21 -32
- package/dist/instrumentation/types.d.ts +7 -3
- package/dist/loader-config.d.ts +16 -6
- package/dist/loaders/controller-loading.d.ts +14 -0
- package/dist/loaders/controller-loading.js +12 -0
- package/dist/loaders/defaults.d.ts +10 -0
- package/dist/loaders/defaults.js +10 -0
- package/dist/loaders/element-loader.d.ts +20 -8
- package/dist/loaders/element-loader.js +102 -36
- package/dist/loaders/element-package-policy.d.ts +2 -0
- package/dist/loaders/element-package-policy.js +2 -1
- package/dist/loaders/esm-adapter.d.ts +29 -0
- package/dist/loaders/esm-adapter.js +635 -102
- package/dist/loaders/iife-adapter.d.ts +0 -5
- package/dist/loaders/iife-adapter.js +3 -9
- package/dist/loaders/index.d.ts +12 -3
- package/dist/loaders/index.js +9 -3
- package/dist/loaders/mathjax-events.d.ts +22 -0
- package/dist/loaders/mathjax-events.js +65 -0
- package/dist/loaders/module-shim.d.ts +12 -0
- package/dist/loaders/module-shim.js +12 -0
- package/dist/loaders/preloaded-alignment.d.ts +24 -0
- package/dist/loaders/preloaded-alignment.js +57 -0
- package/dist/loaders/preloaded-authored-tags.d.ts +26 -0
- package/dist/loaders/preloaded-authored-tags.js +69 -0
- package/dist/loaders/preloaded-registration.d.ts +72 -0
- package/dist/loaders/preloaded-registration.js +141 -0
- package/dist/media/index.d.ts +69 -0
- package/dist/media/index.js +194 -0
- package/dist/pie/authoring.d.ts +1 -1
- package/dist/pie/authoring.js +25 -18
- package/dist/pie/element-announcements.d.ts +39 -0
- package/dist/pie/element-announcements.js +67 -0
- package/dist/pie/element-observer.d.ts +3 -1
- package/dist/pie/element-observer.js +1 -0
- package/dist/pie/elements-rendered.d.ts +25 -0
- package/dist/pie/elements-rendered.js +96 -0
- package/dist/pie/index.d.ts +8 -10
- package/dist/pie/index.js +9 -10
- package/dist/pie/initialization.js +13 -22
- package/dist/pie/initialize-element.d.ts +3 -0
- package/dist/pie/initialize-element.js +3 -4
- package/dist/pie/instrumentation-event-bridge.js +23 -1
- package/dist/pie/instrumentation-event-map.js +11 -25
- package/dist/pie/instrumentation-provider-resolution.d.ts +2 -1
- package/dist/pie/instrumentation-provider-resolution.js +0 -1
- package/dist/pie/item-controller.js +4 -0
- package/dist/pie/item-session-contract.d.ts +8 -0
- package/dist/pie/item-session-contract.js +30 -9
- package/dist/pie/logger.d.ts +6 -0
- package/dist/pie/logger.js +10 -0
- package/dist/pie/markup-math.d.ts +20 -0
- package/dist/pie/markup-math.js +118 -0
- package/dist/pie/math-assets.d.ts +48 -0
- package/dist/pie/math-assets.js +81 -0
- package/dist/pie/math-rendering.d.ts +10 -21
- package/dist/pie/math-rendering.js +50 -35
- package/dist/pie/registry.d.ts +13 -1
- package/dist/pie/registry.js +39 -1
- package/dist/pie/resource-monitor.d.ts +6 -33
- package/dist/pie/resource-monitor.js +14 -213
- package/dist/pie/scoring.d.ts +12 -8
- package/dist/pie/scoring.js +21 -24
- package/dist/pie/session-commit.d.ts +16 -0
- package/dist/pie/session-commit.js +34 -6
- package/dist/pie/session-snapshot.d.ts +80 -0
- package/dist/pie/session-snapshot.js +166 -0
- package/dist/pie/stages.d.ts +10 -47
- package/dist/pie/stages.js +10 -47
- package/dist/pie/tag-names.d.ts +1 -0
- package/dist/pie/tag-names.js +1 -0
- package/dist/pie/types.d.ts +4 -5
- package/dist/pie/types.js +1 -0
- package/dist/pie/updates.d.ts +2 -2
- package/dist/pie/updates.js +33 -15
- package/dist/pie/versioned-tag.d.ts +4 -0
- package/dist/pie/versioned-tag.js +4 -0
- package/dist/security/index.d.ts +2 -3
- package/dist/security/index.js +2 -3
- package/dist/security/sanitize-item-markup.d.ts +4 -2
- package/dist/security/sanitize-item-markup.js +48 -6
- package/dist/security/sanitize-svg-icon.d.ts +0 -2
- package/dist/security/sanitize-svg-icon.js +0 -5
- package/dist/security/wrap-overwide-images.js +19 -0
- package/dist/security/wrap-overwide.d.ts +13 -0
- package/dist/security/wrap-overwide.js +63 -3
- package/dist/timed-media/policy.js +5 -16
- package/dist/types/index.d.ts +67 -423
- package/dist/types/index.js +0 -68
- package/dist/ui/attribute-coercion.d.ts +12 -0
- package/dist/ui/attribute-coercion.js +33 -0
- package/dist/ui/content-styles.d.ts +18 -16
- package/dist/ui/content-styles.js +67 -36
- package/dist/ui/overlay-containment.d.ts +31 -0
- package/dist/ui/overlay-containment.js +63 -0
- package/dist/ui/overlay-placement.d.ts +110 -0
- package/dist/ui/overlay-placement.js +222 -0
- package/dist/ui/pointer-gesture.d.ts +32 -0
- package/dist/ui/pointer-gesture.js +56 -0
- package/dist/ui/pointer-rotate.d.ts +29 -0
- package/dist/ui/pointer-rotate.js +39 -0
- package/package.json +23 -18
- package/dist/pie/component-context.d.ts +0 -21
- package/dist/pie/component-context.js +0 -29
- package/dist/pie/player-initializer.d.ts +0 -54
- package/dist/pie/player-initializer.js +0 -122
- package/dist/pie/stage-tracker.d.ts +0 -50
- package/dist/pie/stage-tracker.js +0 -105
- package/dist/security/wrap-model-rich-content.d.ts +0 -1
- package/dist/security/wrap-model-rich-content.js +0 -40
- package/dist/server/npm-auth-env.d.ts +0 -13
- package/dist/server/npm-auth-env.js +0 -64
- package/dist/server/npm-registry.d.ts +0 -7
- package/dist/server/npm-registry.js +0 -59
- package/dist/ui/debug-panel-persistence.d.ts +0 -17
- package/dist/ui/debug-panel-persistence.js +0 -37
|
@@ -7,17 +7,15 @@
|
|
|
7
7
|
* Features:
|
|
8
8
|
* - Tracks resource load timing with PerformanceObserver
|
|
9
9
|
* - Detects and retries failed resource loads
|
|
10
|
-
* -
|
|
10
|
+
* - With `trackPageActions`, reports loads, retries and failures to the
|
|
11
|
+
* instrumentation provider it is given
|
|
11
12
|
* - Works with all resource types (audio, video, img, link)
|
|
12
13
|
*/
|
|
13
|
-
import { NewRelicInstrumentationProvider } from "../instrumentation/providers/NewRelicInstrumentationProvider.js";
|
|
14
14
|
import { isInstrumentationProvider } from "../instrumentation/provider-guards.js";
|
|
15
|
-
import { getCurrentComponentContext } from "./component-context.js";
|
|
16
15
|
import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
|
|
17
16
|
const DEFAULT_CONFIG = {
|
|
18
17
|
trackPageActions: false,
|
|
19
|
-
|
|
20
|
-
manageProviderLifecycle: undefined,
|
|
18
|
+
manageProviderLifecycle: false,
|
|
21
19
|
maxRetries: 3,
|
|
22
20
|
initialRetryDelay: 500,
|
|
23
21
|
maxRetryDelay: 5000,
|
|
@@ -43,49 +41,39 @@ export class ResourceMonitor {
|
|
|
43
41
|
isBrowser;
|
|
44
42
|
containerResources = new Set(); // Track resources within our container
|
|
45
43
|
provider;
|
|
46
|
-
ownsProvider;
|
|
47
|
-
manageProviderLifecycle;
|
|
48
44
|
started = false;
|
|
49
45
|
lifecycleVersion = 0;
|
|
50
46
|
pendingRetryTimers = new Map();
|
|
51
47
|
retryInFlight = new Set();
|
|
52
48
|
pendingMediaChecks = new Map();
|
|
53
49
|
constructor(config = {}) {
|
|
54
|
-
const validInjectedProvider = isInstrumentationProvider(config.instrumentationProvider)
|
|
55
|
-
? config.instrumentationProvider
|
|
56
|
-
: undefined;
|
|
57
|
-
const hasInjectedProvider = !!validInjectedProvider;
|
|
58
|
-
const manageProviderLifecycle = config.manageProviderLifecycle ?? !hasInjectedProvider;
|
|
59
50
|
this.config = {
|
|
60
51
|
trackPageActions: config.trackPageActions ?? DEFAULT_CONFIG.trackPageActions,
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
manageProviderLifecycle: config.manageProviderLifecycle ??
|
|
53
|
+
DEFAULT_CONFIG.manageProviderLifecycle,
|
|
63
54
|
maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,
|
|
64
55
|
initialRetryDelay: config.initialRetryDelay ?? DEFAULT_CONFIG.initialRetryDelay,
|
|
65
56
|
maxRetryDelay: config.maxRetryDelay ?? DEFAULT_CONFIG.maxRetryDelay,
|
|
66
57
|
debug: config.debug ?? DEFAULT_CONFIG.debug,
|
|
67
58
|
};
|
|
68
59
|
this.logger = createPieLogger("resource-monitor", () => this.isDebugEnabled());
|
|
69
|
-
|
|
60
|
+
this.provider = isInstrumentationProvider(config.instrumentationProvider)
|
|
61
|
+
? config.instrumentationProvider
|
|
62
|
+
: undefined;
|
|
63
|
+
if (config.instrumentationProvider && !this.provider) {
|
|
70
64
|
if (this.isDebugEnabled()) {
|
|
71
65
|
this.logger.warn("Ignoring invalid instrumentation provider; expected InstrumentationProvider shape");
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
|
-
// Always use a provider - default to NewRelic if not specified
|
|
75
|
-
this.ownsProvider = !this.config.instrumentationProvider;
|
|
76
|
-
this.provider =
|
|
77
|
-
this.config.instrumentationProvider ??
|
|
78
|
-
new NewRelicInstrumentationProvider();
|
|
79
|
-
this.manageProviderLifecycle = this.config.manageProviderLifecycle;
|
|
80
68
|
// Initialize the provider (async, but don't block constructor)
|
|
81
|
-
if (this.manageProviderLifecycle) {
|
|
69
|
+
if (this.provider && this.config.manageProviderLifecycle) {
|
|
82
70
|
this.provider.initialize().catch((err) => {
|
|
83
71
|
if (this.isDebugEnabled()) {
|
|
84
72
|
this.logger.warn("Failed to initialize instrumentation provider:", err);
|
|
85
73
|
}
|
|
86
74
|
});
|
|
87
75
|
}
|
|
88
|
-
else if (this.isDebugEnabled()) {
|
|
76
|
+
else if (this.provider && this.isDebugEnabled()) {
|
|
89
77
|
this.logger.debug("Skipping provider lifecycle management for injected provider");
|
|
90
78
|
}
|
|
91
79
|
this.isBrowser =
|
|
@@ -186,7 +174,7 @@ export class ResourceMonitor {
|
|
|
186
174
|
* Track event with instrumentation provider if enabled
|
|
187
175
|
*/
|
|
188
176
|
trackInstrumentationEvent(eventName, attributes) {
|
|
189
|
-
if (!this.config.trackPageActions || !this.provider
|
|
177
|
+
if (!this.config.trackPageActions || !this.provider?.isReady()) {
|
|
190
178
|
return;
|
|
191
179
|
}
|
|
192
180
|
this.provider.trackEvent(eventName, {
|
|
@@ -199,7 +187,7 @@ export class ResourceMonitor {
|
|
|
199
187
|
* Track error with instrumentation provider if enabled
|
|
200
188
|
*/
|
|
201
189
|
trackInstrumentationError(error, attributes) {
|
|
202
|
-
if (!this.config.trackPageActions || !this.provider
|
|
190
|
+
if (!this.config.trackPageActions || !this.provider?.isReady()) {
|
|
203
191
|
return;
|
|
204
192
|
}
|
|
205
193
|
this.provider.trackError(error, {
|
|
@@ -264,7 +252,7 @@ export class ResourceMonitor {
|
|
|
264
252
|
this.containerResources.clear();
|
|
265
253
|
this.container = null;
|
|
266
254
|
this.started = false;
|
|
267
|
-
if (this.manageProviderLifecycle) {
|
|
255
|
+
if (this.provider && this.config.manageProviderLifecycle) {
|
|
268
256
|
try {
|
|
269
257
|
this.provider.destroy();
|
|
270
258
|
}
|
|
@@ -1162,190 +1150,3 @@ export class ResourceMonitor {
|
|
|
1162
1150
|
};
|
|
1163
1151
|
}
|
|
1164
1152
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Create and start a resource monitor for a container
|
|
1167
|
-
*/
|
|
1168
|
-
export function createResourceMonitor(container, config = {}) {
|
|
1169
|
-
const monitor = new ResourceMonitor(config);
|
|
1170
|
-
monitor.start(container);
|
|
1171
|
-
return monitor;
|
|
1172
|
-
}
|
|
1173
|
-
// =============================================================================
|
|
1174
|
-
// Global resource request tracking (consolidated from the old font-request-tracker)
|
|
1175
|
-
// =============================================================================
|
|
1176
|
-
// Track failed resource requests (by URL)
|
|
1177
|
-
const failedRequests = new Map();
|
|
1178
|
-
function isResourceFile(url) {
|
|
1179
|
-
const resourceExtensions = [
|
|
1180
|
-
// Fonts
|
|
1181
|
-
".woff",
|
|
1182
|
-
".woff2",
|
|
1183
|
-
".ttf",
|
|
1184
|
-
".otf",
|
|
1185
|
-
".eot",
|
|
1186
|
-
// Images
|
|
1187
|
-
".gif",
|
|
1188
|
-
".jpg",
|
|
1189
|
-
".jpeg",
|
|
1190
|
-
".png",
|
|
1191
|
-
".svg",
|
|
1192
|
-
".webp",
|
|
1193
|
-
".ico",
|
|
1194
|
-
// Audio/Video
|
|
1195
|
-
".mp3",
|
|
1196
|
-
".mp4",
|
|
1197
|
-
".wav",
|
|
1198
|
-
".ogg",
|
|
1199
|
-
".webm",
|
|
1200
|
-
// Other assets
|
|
1201
|
-
".pdf",
|
|
1202
|
-
".css",
|
|
1203
|
-
".js",
|
|
1204
|
-
];
|
|
1205
|
-
return resourceExtensions.some((ext) => url.toLowerCase().includes(ext));
|
|
1206
|
-
}
|
|
1207
|
-
function getResourceType(url) {
|
|
1208
|
-
if (url.match(/\.(woff|woff2|ttf|otf|eot)/i))
|
|
1209
|
-
return "Font";
|
|
1210
|
-
if (url.match(/\.(gif|jpg|jpeg|png|svg|webp|ico)/i))
|
|
1211
|
-
return "Image";
|
|
1212
|
-
if (url.match(/\.(mp3|mp4|wav|ogg|webm)/i))
|
|
1213
|
-
return "Media";
|
|
1214
|
-
if (url.match(/\.(css)/i))
|
|
1215
|
-
return "Stylesheet";
|
|
1216
|
-
if (url.match(/\.(js)/i))
|
|
1217
|
-
return "Script";
|
|
1218
|
-
return "Resource";
|
|
1219
|
-
}
|
|
1220
|
-
function logFailedRequest(url, status, context) {
|
|
1221
|
-
// Only log 404s and other failures (status 0 for network errors)
|
|
1222
|
-
if (status !== 404 && status !== 0) {
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
const key = url;
|
|
1226
|
-
if (!failedRequests.has(key)) {
|
|
1227
|
-
failedRequests.set(key, []);
|
|
1228
|
-
}
|
|
1229
|
-
const contexts = failedRequests.get(key);
|
|
1230
|
-
contexts.push({
|
|
1231
|
-
...(context || { componentName: "Unknown", timestamp: Date.now() }),
|
|
1232
|
-
timestamp: Date.now(),
|
|
1233
|
-
});
|
|
1234
|
-
const resourceType = getResourceType(url);
|
|
1235
|
-
const filename = url.split("/").pop() || url;
|
|
1236
|
-
const itemId = context?.itemId || "Unknown";
|
|
1237
|
-
const componentName = context?.componentName || "Unknown component";
|
|
1238
|
-
const elementType = context?.elementType || "";
|
|
1239
|
-
console.warn(`Failed ${resourceType} request`, `\n Resource: ${filename}`, `\n URL: ${url}`, `\n Item ID: ${itemId}`, `\n Mini-Player: ${componentName}`, elementType ? `\n Element Type: ${elementType}` : "", `\n Status: ${status}`, context
|
|
1240
|
-
? ""
|
|
1241
|
-
: "\n Note: Component not tracked - may be from item markup or PIE element");
|
|
1242
|
-
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Initialize global resource request tracking (404s for fonts/images/etc.)
|
|
1245
|
-
* Call this once when the app loads (client-side only).
|
|
1246
|
-
*/
|
|
1247
|
-
export function initializeResourceRequestTracking() {
|
|
1248
|
-
if (typeof window === "undefined")
|
|
1249
|
-
return;
|
|
1250
|
-
// Track fetch requests
|
|
1251
|
-
const originalFetch = window.fetch;
|
|
1252
|
-
window.fetch = async function (...args) {
|
|
1253
|
-
// Extract URL from first argument (can be string, URL, or Request)
|
|
1254
|
-
let url = "";
|
|
1255
|
-
if (typeof args[0] === "string") {
|
|
1256
|
-
url = args[0];
|
|
1257
|
-
}
|
|
1258
|
-
else if (args[0] instanceof URL) {
|
|
1259
|
-
url = args[0].toString();
|
|
1260
|
-
}
|
|
1261
|
-
else if (args[0] instanceof Request) {
|
|
1262
|
-
url = args[0].url;
|
|
1263
|
-
}
|
|
1264
|
-
if (isResourceFile(url) || url.startsWith("/")) {
|
|
1265
|
-
const context = getCurrentComponentContext();
|
|
1266
|
-
try {
|
|
1267
|
-
const response = await originalFetch(...args);
|
|
1268
|
-
if (response.status === 404) {
|
|
1269
|
-
logFailedRequest(url, response.status, context);
|
|
1270
|
-
}
|
|
1271
|
-
return response;
|
|
1272
|
-
}
|
|
1273
|
-
catch (error) {
|
|
1274
|
-
logFailedRequest(url, 0, context);
|
|
1275
|
-
throw error;
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
return originalFetch(...args);
|
|
1279
|
-
};
|
|
1280
|
-
// Track XMLHttpRequest
|
|
1281
|
-
const originalXHROpen = XMLHttpRequest.prototype.open;
|
|
1282
|
-
XMLHttpRequest.prototype.open = function (method, url, async, username, password) {
|
|
1283
|
-
const urlString = typeof url === "string" ? url : url.toString();
|
|
1284
|
-
if (isResourceFile(urlString) || urlString.startsWith("/")) {
|
|
1285
|
-
const context = getCurrentComponentContext();
|
|
1286
|
-
this.addEventListener("load", function () {
|
|
1287
|
-
if (this.status === 404) {
|
|
1288
|
-
logFailedRequest(urlString, this.status, context);
|
|
1289
|
-
}
|
|
1290
|
-
});
|
|
1291
|
-
this.addEventListener("error", function () {
|
|
1292
|
-
logFailedRequest(urlString, 0, context);
|
|
1293
|
-
});
|
|
1294
|
-
}
|
|
1295
|
-
return originalXHROpen.call(this, method, url, async ?? true, username ?? null, password ?? null);
|
|
1296
|
-
};
|
|
1297
|
-
// Intercept img tag error events (most reliable way to catch failed image loads)
|
|
1298
|
-
const errorHandler = (event) => {
|
|
1299
|
-
const target = event.target;
|
|
1300
|
-
if (target instanceof HTMLImageElement && target.src) {
|
|
1301
|
-
const url = target.src;
|
|
1302
|
-
if (isResourceFile(url) ||
|
|
1303
|
-
url.startsWith("/") ||
|
|
1304
|
-
url.startsWith(window.location.origin + "/")) {
|
|
1305
|
-
const context = getCurrentComponentContext();
|
|
1306
|
-
const relativeUrl = url.startsWith(window.location.origin)
|
|
1307
|
-
? url.replace(window.location.origin, "")
|
|
1308
|
-
: url;
|
|
1309
|
-
logFailedRequest(relativeUrl, 404, context);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
document.addEventListener("error", errorHandler, true);
|
|
1314
|
-
// Track resource requests via PerformanceObserver (fonts, CSS resources, etc.)
|
|
1315
|
-
const observer = new PerformanceObserver((list) => {
|
|
1316
|
-
for (const entry of list.getEntries()) {
|
|
1317
|
-
if (entry.entryType !== "resource")
|
|
1318
|
-
continue;
|
|
1319
|
-
const resourceEntry = entry;
|
|
1320
|
-
const url = resourceEntry.name;
|
|
1321
|
-
if (isResourceFile(url) ||
|
|
1322
|
-
url.startsWith("/") ||
|
|
1323
|
-
url.startsWith(window.location.origin + "/")) {
|
|
1324
|
-
const perf = resourceEntry;
|
|
1325
|
-
const failed = perf.responseEnd === 0 && perf.duration > 0;
|
|
1326
|
-
const mightBe404 = perf.transferSize === 0 &&
|
|
1327
|
-
perf.responseEnd > 0 &&
|
|
1328
|
-
perf.responseStart === 0;
|
|
1329
|
-
if (failed || mightBe404) {
|
|
1330
|
-
const context = getCurrentComponentContext();
|
|
1331
|
-
const relativeUrl = url.startsWith(window.location.origin)
|
|
1332
|
-
? url.replace(window.location.origin, "")
|
|
1333
|
-
: url;
|
|
1334
|
-
logFailedRequest(relativeUrl, 404, context);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
});
|
|
1339
|
-
try {
|
|
1340
|
-
observer.observe({ type: "resource", buffered: true });
|
|
1341
|
-
}
|
|
1342
|
-
catch (e) {
|
|
1343
|
-
console.warn("Failed to set up PerformanceObserver for resource tracking:", e);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
export function getTrackedResourceRequests() {
|
|
1347
|
-
return new Map(failedRequests);
|
|
1348
|
-
}
|
|
1349
|
-
export function clearTrackedResourceRequests() {
|
|
1350
|
-
failedRequests.clear();
|
|
1351
|
-
}
|
package/dist/pie/scoring.d.ts
CHANGED
|
@@ -4,25 +4,29 @@
|
|
|
4
4
|
* Scoring and outcome calculation for PIE elements.
|
|
5
5
|
*/
|
|
6
6
|
import type { ConfigEntity, Env, OutcomeResponse, PieController, PieModel } from "../types/index.js";
|
|
7
|
+
import { BundleType } from "./types.js";
|
|
7
8
|
/**
|
|
8
|
-
* Find the controller for a PIE element
|
|
9
|
+
* Find the controller for a PIE element.
|
|
10
|
+
*
|
|
11
|
+
* `deliveryBundleType` is the bundle type of the player asking. A `player.js`
|
|
12
|
+
* delivery, which is what a hosted player is, resolves no controller: its
|
|
13
|
+
* models and scores come from the server, and the registry is shared with
|
|
14
|
+
* every other loader on the page, so a registered controller is no evidence
|
|
15
|
+
* that this player may run one.
|
|
9
16
|
*/
|
|
10
|
-
export declare const findPieController: (elementName: string) => PieController | undefined;
|
|
17
|
+
export declare const findPieController: (elementName: string, deliveryBundleType?: BundleType) => PieController | undefined;
|
|
11
18
|
export type ScorePieItemOptions = {
|
|
12
19
|
/** Scope element lookup to a specific player/root. Defaults to document for existing callers. */
|
|
13
20
|
container?: Pick<ParentNode, "querySelector">;
|
|
14
21
|
env?: Partial<Env>;
|
|
15
22
|
models?: PieModel[];
|
|
16
|
-
/**
|
|
17
|
-
* Existing callers keep the historical scorePieItem controller call shape.
|
|
18
|
-
* provideScore() opts into the item-player score result shape explicitly.
|
|
19
|
-
*/
|
|
20
|
-
outcomeArguments?: "session-env" | "model-session-env";
|
|
21
23
|
/**
|
|
22
24
|
* Preserve one result slot per model, including undefined for missing element/controller.
|
|
23
|
-
*
|
|
25
|
+
* Missing slots are filtered out by default.
|
|
24
26
|
*/
|
|
25
27
|
includeMissingResults?: boolean;
|
|
28
|
+
/** The scoring player's bundle type; `player.js` scores nothing locally. */
|
|
29
|
+
bundleType?: BundleType;
|
|
26
30
|
};
|
|
27
31
|
export declare function scorePieItem(config: ConfigEntity, sessionData: any[], options: ScorePieItemOptions & {
|
|
28
32
|
includeMissingResults: true;
|
package/dist/pie/scoring.js
CHANGED
|
@@ -10,9 +10,19 @@ import { findOrAddSession } from "./utils.js";
|
|
|
10
10
|
// Create module-level logger (respects global debug flag - pass function for dynamic checking)
|
|
11
11
|
const logger = createPieLogger("pie-scoring", () => isGlobalDebugEnabled());
|
|
12
12
|
/**
|
|
13
|
-
* Find the controller for a PIE element
|
|
13
|
+
* Find the controller for a PIE element.
|
|
14
|
+
*
|
|
15
|
+
* `deliveryBundleType` is the bundle type of the player asking. A `player.js`
|
|
16
|
+
* delivery, which is what a hosted player is, resolves no controller: its
|
|
17
|
+
* models and scores come from the server, and the registry is shared with
|
|
18
|
+
* every other loader on the page, so a registered controller is no evidence
|
|
19
|
+
* that this player may run one.
|
|
14
20
|
*/
|
|
15
|
-
export const findPieController = (elementName) => {
|
|
21
|
+
export const findPieController = (elementName, deliveryBundleType) => {
|
|
22
|
+
if (deliveryBundleType === BundleType.player) {
|
|
23
|
+
logger.debug(`[findPieController] ℹ️ ${elementName} delivered as player.js; using server-processed models`);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
16
26
|
const registry = pieRegistry();
|
|
17
27
|
logger.debug(`[findPieController] Looking for controller for: ${elementName}`);
|
|
18
28
|
logger.debug(`[findPieController] Registry has entries for:`, Object.keys(registry));
|
|
@@ -30,20 +40,12 @@ export const findPieController = (elementName) => {
|
|
|
30
40
|
controllerKeys: entry.controller ? Object.keys(entry.controller) : [],
|
|
31
41
|
bundleType: entry.bundleType,
|
|
32
42
|
});
|
|
33
|
-
|
|
43
|
+
// Registration writes `null` for a package without a controller.
|
|
44
|
+
const controller = entry.controller ?? undefined;
|
|
34
45
|
if (!controller) {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
logger.error(`[findPieController] ❌ CRITICAL: Registry entry exists for ${elementName} but controller is missing!`);
|
|
39
|
-
logger.error(`[findPieController] Bundle type: ${entry.bundleType} (controllers required)`);
|
|
40
|
-
logger.error(`[findPieController] Entry:`, entry);
|
|
41
|
-
throw new Error(`No controller found for ${elementName}. client-player.js bundles MUST include controllers. Check bundle loading and registration.`);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// player.js doesn't have controllers - this is expected
|
|
45
|
-
logger.debug(`[findPieController] ℹ️ No controller for ${elementName} - using server-processed models (player.js bundle)`);
|
|
46
|
-
}
|
|
46
|
+
// A package without one, such as a legacy `@pie-element/protractor`,
|
|
47
|
+
// renders the model it is given, as `<pie-player>` passed it through.
|
|
48
|
+
logger.debug(`[findPieController] ℹ️ No controller for ${elementName}; its model is used as given`);
|
|
47
49
|
}
|
|
48
50
|
else {
|
|
49
51
|
logger.debug(`[findPieController] ✅ Controller found for ${elementName} with functions:`, Object.keys(controller));
|
|
@@ -60,13 +62,9 @@ const findRenderedPieElement = (root, modelId) => {
|
|
|
60
62
|
return (root.querySelector(`[id="${escapedId}"]`) ||
|
|
61
63
|
root.querySelector(`[pie-id="${escapedId}"]`));
|
|
62
64
|
};
|
|
63
|
-
const invokeOutcome = async (controller, model, session, env
|
|
64
|
-
if (argumentShape === "model-session-env") {
|
|
65
|
-
const outcome = controller.outcome;
|
|
66
|
-
return outcome.call(controller, model, session, env);
|
|
67
|
-
}
|
|
65
|
+
const invokeOutcome = async (controller, model, session, env) => {
|
|
68
66
|
const outcome = controller.outcome;
|
|
69
|
-
return outcome.call(controller, session, env);
|
|
67
|
+
return outcome.call(controller, model, session, env);
|
|
70
68
|
};
|
|
71
69
|
/**
|
|
72
70
|
* Score a PIE item by calling outcome on all PIE elements
|
|
@@ -74,7 +72,6 @@ const invokeOutcome = async (controller, model, session, env, argumentShape) =>
|
|
|
74
72
|
export async function scorePieItem(config, sessionData, options = {}) {
|
|
75
73
|
const root = options.container ?? document;
|
|
76
74
|
const models = options.models ?? config.models ?? [];
|
|
77
|
-
const outcomeArguments = options.outcomeArguments ?? "session-env";
|
|
78
75
|
const scoringEnv = {
|
|
79
76
|
...options.env,
|
|
80
77
|
mode: "evaluate",
|
|
@@ -85,11 +82,11 @@ export async function scorePieItem(config, sessionData, options = {}) {
|
|
|
85
82
|
logger.debug("found pieEl %O for model id %s", pieEl, model.id);
|
|
86
83
|
const session = findOrAddSession(sessionData, model.id, model.element);
|
|
87
84
|
if (pieEl) {
|
|
88
|
-
const controller = findPieController(pieEl.localName);
|
|
85
|
+
const controller = findPieController(pieEl.localName, options.bundleType);
|
|
89
86
|
if (controller?.outcome) {
|
|
90
87
|
return {
|
|
91
88
|
...session,
|
|
92
|
-
...(await invokeOutcome(controller, model, session, scoringEnv
|
|
89
|
+
...(await invokeOutcome(controller, model, session, scoringEnv)),
|
|
93
90
|
};
|
|
94
91
|
}
|
|
95
92
|
else {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* element, and it does not reach `document`. An SPA host that unmounts the
|
|
18
18
|
* player is served by `commitPendingSessions()` on the player element, called
|
|
19
19
|
* before the unmount.
|
|
20
|
+
* - Ahead of all three, `flushPendingSessionNotifications` runs when focus
|
|
21
|
+
* leaves a player, so the event lands before the host acts on the click or
|
|
22
|
+
* key that moved it, and carries no reason.
|
|
20
23
|
*
|
|
21
24
|
* Two paths, per element:
|
|
22
25
|
*
|
|
@@ -72,6 +75,19 @@ export declare function noteSessionObserved(target: EventTarget | null | undefin
|
|
|
72
75
|
*/
|
|
73
76
|
export declare function noteSessionBaseline(root: ParentNode | null | undefined): void;
|
|
74
77
|
export declare function commitPendingSessions(root: ParentNode | null | undefined, options?: CommitPendingSessionsOptions): CommitPendingSessionsResult;
|
|
78
|
+
/**
|
|
79
|
+
* Deliver every pending `session-changed` now, as an ordinary announcement.
|
|
80
|
+
*
|
|
81
|
+
* The flush a player runs when focus leaves it, ahead of whatever the learner
|
|
82
|
+
* is moving to, so the host hears the response while the item it belongs to is
|
|
83
|
+
* still current. Same two paths and the same discriminant as the commit, with
|
|
84
|
+
* no `sessionCommitReason`: an adopted element's event is exactly what its
|
|
85
|
+
* debounce would have delivered, only earlier, and the next seam finds nothing
|
|
86
|
+
* pending.
|
|
87
|
+
*/
|
|
88
|
+
export declare function flushPendingSessionNotifications(root: ParentNode | null | undefined, options?: {
|
|
89
|
+
logger?: PieLogger;
|
|
90
|
+
}): CommitPendingSessionsResult;
|
|
75
91
|
export interface BindPageLifecycleCommitOptions {
|
|
76
92
|
/**
|
|
77
93
|
* The subtree to commit, re-evaluated on every transition: a player's
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* element, and it does not reach `document`. An SPA host that unmounts the
|
|
18
18
|
* player is served by `commitPendingSessions()` on the player element, called
|
|
19
19
|
* before the unmount.
|
|
20
|
+
* - Ahead of all three, `flushPendingSessionNotifications` runs when focus
|
|
21
|
+
* leaves a player, so the event lands before the host acts on the click or
|
|
22
|
+
* key that moved it, and carries no reason.
|
|
20
23
|
*
|
|
21
24
|
* Two paths, per element:
|
|
22
25
|
*
|
|
@@ -90,7 +93,7 @@ function assignedElementsOf(element) {
|
|
|
90
93
|
* elements assigned to each `<slot>`.
|
|
91
94
|
*
|
|
92
95
|
* `querySelectorAll("*")` sees none of the last two, and both are load-bearing
|
|
93
|
-
* here. The section player mounts each item inside `<pie-item-
|
|
96
|
+
* here. The section player mounts each item inside `<pie-item-scope>`'s shadow
|
|
94
97
|
* root, and projects its item pane through a slot on the assessment toolkit -
|
|
95
98
|
* a walk that stops at `<slot>` finds every delivery element in a section
|
|
96
99
|
* unreachable.
|
|
@@ -254,9 +257,14 @@ export function commitPendingSessions(root, options = {}) {
|
|
|
254
257
|
};
|
|
255
258
|
if (!root)
|
|
256
259
|
return result;
|
|
257
|
-
|
|
260
|
+
const reason = options.reason ?? "teardown";
|
|
261
|
+
return markCommitsDuring(root, reason, () => sweepPendingSessions(root, reason, options.logger, result));
|
|
258
262
|
}
|
|
259
|
-
|
|
263
|
+
/**
|
|
264
|
+
* `reason` is null for a flush, whose events are ordinary announcements.
|
|
265
|
+
*/
|
|
266
|
+
function sweepPendingSessions(root, reason, logger, result) {
|
|
267
|
+
const caller = reason ? "commitPendingSessions" : "flushPendingSessionNotifications";
|
|
260
268
|
for (const element of candidatesIn(root)) {
|
|
261
269
|
const session = readSession(element);
|
|
262
270
|
if (!isRecord(session)) {
|
|
@@ -307,7 +315,7 @@ function sweepPendingSessions(root, options, result) {
|
|
|
307
315
|
}
|
|
308
316
|
catch (error) {
|
|
309
317
|
failed = true;
|
|
310
|
-
|
|
318
|
+
logger?.warn(`[${caller}] ${element.tagName.toLowerCase()} failed to commit its pending session`, error);
|
|
311
319
|
}
|
|
312
320
|
finally {
|
|
313
321
|
element.removeEventListener("session-changed", witness, true);
|
|
@@ -331,19 +339,39 @@ function sweepPendingSessions(root, options, result) {
|
|
|
331
339
|
detail: {
|
|
332
340
|
component: element.tagName.toLowerCase(),
|
|
333
341
|
session: JSON.parse(signature),
|
|
334
|
-
sessionCommitReason:
|
|
342
|
+
...(reason ? { sessionCommitReason: reason } : {}),
|
|
335
343
|
},
|
|
336
344
|
}));
|
|
337
345
|
writeObservedSignature(element, signature);
|
|
338
346
|
result.synthesized += 1;
|
|
339
347
|
}
|
|
340
348
|
catch (error) {
|
|
341
|
-
|
|
349
|
+
logger?.warn(`[${caller}] ${element.tagName.toLowerCase()} session commit dispatch failed`, error);
|
|
342
350
|
result.skipped += 1;
|
|
343
351
|
}
|
|
344
352
|
}
|
|
345
353
|
return result;
|
|
346
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Deliver every pending `session-changed` now, as an ordinary announcement.
|
|
357
|
+
*
|
|
358
|
+
* The flush a player runs when focus leaves it, ahead of whatever the learner
|
|
359
|
+
* is moving to, so the host hears the response while the item it belongs to is
|
|
360
|
+
* still current. Same two paths and the same discriminant as the commit, with
|
|
361
|
+
* no `sessionCommitReason`: an adopted element's event is exactly what its
|
|
362
|
+
* debounce would have delivered, only earlier, and the next seam finds nothing
|
|
363
|
+
* pending.
|
|
364
|
+
*/
|
|
365
|
+
export function flushPendingSessionNotifications(root, options = {}) {
|
|
366
|
+
const result = {
|
|
367
|
+
committed: 0,
|
|
368
|
+
synthesized: 0,
|
|
369
|
+
skipped: 0,
|
|
370
|
+
};
|
|
371
|
+
if (!root)
|
|
372
|
+
return result;
|
|
373
|
+
return sweepPendingSessions(root, null, options.logger, result);
|
|
374
|
+
}
|
|
347
375
|
/**
|
|
348
376
|
* Commit on the page going away rather than on the player being removed from it.
|
|
349
377
|
*
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-local snapshot of a committed session.
|
|
3
|
+
*
|
|
4
|
+
* A crash or an OS kill fires no lifecycle event, so the only state that
|
|
5
|
+
* survives it is state already written. The snapshot writes each committed
|
|
6
|
+
* session to device storage and offers it back on the next load.
|
|
7
|
+
*
|
|
8
|
+
* It is offered, never applied. School devices are shared, and a player cannot
|
|
9
|
+
* tell a legitimate recovery from a previous student's draft, so the host
|
|
10
|
+
* decides.
|
|
11
|
+
*
|
|
12
|
+
* `sessionStorage` is the default, matching the annotation toolbar's existing
|
|
13
|
+
* use of it for learner annotations: it is tab-scoped and cleared when the tab
|
|
14
|
+
* closes, so a response does not outlive the sitting, and browser session
|
|
15
|
+
* restore brings it back after a crash. A host that wants recovery across a
|
|
16
|
+
* full browser restart injects a `localStorage`-backed store and owns the
|
|
17
|
+
* retention consequences.
|
|
18
|
+
*/
|
|
19
|
+
export interface SessionSnapshotStore {
|
|
20
|
+
read(key: string): string | null;
|
|
21
|
+
write(key: string, value: string): void;
|
|
22
|
+
clear(key: string): void;
|
|
23
|
+
}
|
|
24
|
+
export type SessionSnapshotConfig = boolean | {
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
/** Defaults to `sessionStorage`. */
|
|
27
|
+
store?: SessionSnapshotStore;
|
|
28
|
+
/** Defaults to a key derived from the delivery identity. */
|
|
29
|
+
key?: string;
|
|
30
|
+
};
|
|
31
|
+
export interface SessionSnapshotRecord {
|
|
32
|
+
key: string;
|
|
33
|
+
session: unknown;
|
|
34
|
+
/** `Date.now()` at the time of the write. */
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
export interface SessionSnapshot {
|
|
38
|
+
readonly key: string;
|
|
39
|
+
write(session: unknown): void;
|
|
40
|
+
read(): SessionSnapshotRecord | null;
|
|
41
|
+
clear(): void;
|
|
42
|
+
}
|
|
43
|
+
export type SessionSnapshotIdentity = {
|
|
44
|
+
itemId?: string;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
assignmentId?: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Whether an identity names one sitting.
|
|
50
|
+
*
|
|
51
|
+
* `sessionId` is the only field that distinguishes one learner's attempt from
|
|
52
|
+
* another's. Keyed on the item alone, a shared device offers the previous
|
|
53
|
+
* student's draft to the next one - and the offer is itself a disclosure, before
|
|
54
|
+
* the host ever decides whether to apply it. So without a `sessionId` there is
|
|
55
|
+
* no snapshot, and a host driving the player by props alone opts in by passing
|
|
56
|
+
* an explicit `key`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isSnapshotIdentitySpecific(identity: SessionSnapshotIdentity): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Key built from the delivery identity, so a snapshot is only offered back to
|
|
61
|
+
* the sitting that wrote it.
|
|
62
|
+
*/
|
|
63
|
+
export declare function sessionSnapshotKey(identity: SessionSnapshotIdentity): string;
|
|
64
|
+
/**
|
|
65
|
+
* `sessionStorage`-backed store. Every access is guarded: private mode and
|
|
66
|
+
* blocked site data make the accessor throw, and a player must keep working
|
|
67
|
+
* without a snapshot rather than fail.
|
|
68
|
+
*/
|
|
69
|
+
export declare function sessionStorageSnapshotStore(): SessionSnapshotStore;
|
|
70
|
+
export declare function isSessionSnapshotEnabled(config: SessionSnapshotConfig | null | undefined): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Returns `null` when the host has not enabled a snapshot, and when the
|
|
73
|
+
* identity does not name one sitting. PIE writes learner responses to the
|
|
74
|
+
* device only on an explicit opt-in, and only under a key no other learner
|
|
75
|
+
* shares.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createSessionSnapshot(args: {
|
|
78
|
+
config: SessionSnapshotConfig | null | undefined;
|
|
79
|
+
identity: SessionSnapshotIdentity;
|
|
80
|
+
}): SessionSnapshot | null;
|