@pie-players/pie-players-shared 0.3.47 → 0.3.49

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.
Files changed (65) hide show
  1. package/README.md +30 -0
  2. package/dist/i18n/simple-i18n.js.map +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/instrumentation/debug-panel-stream.js.map +1 -1
  6. package/dist/instrumentation/providers/CompositeInstrumentationProvider.js.map +1 -1
  7. package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js +2 -1
  8. package/dist/instrumentation/providers/DebugPanelInstrumentationProvider.js.map +1 -1
  9. package/dist/loaders/ElementLoader.js +2 -1
  10. package/dist/loaders/ElementLoader.js.map +1 -1
  11. package/dist/loaders/element-loader.js +28 -4
  12. package/dist/loaders/element-loader.js.map +1 -1
  13. package/dist/loaders/esm-adapter.d.ts +22 -5
  14. package/dist/loaders/esm-adapter.js +320 -60
  15. package/dist/loaders/esm-adapter.js.map +1 -1
  16. package/dist/loaders/iife-adapter.js +1 -2
  17. package/dist/loaders/iife-adapter.js.map +1 -1
  18. package/dist/loaders/index.d.ts +1 -1
  19. package/dist/loaders/index.js.map +1 -1
  20. package/dist/pie/authoring.js +4 -2
  21. package/dist/pie/authoring.js.map +1 -1
  22. package/dist/pie/config.js +4 -1
  23. package/dist/pie/config.js.map +1 -1
  24. package/dist/pie/configure-initialization.js.map +1 -1
  25. package/dist/pie/correct-response-env.js.map +1 -1
  26. package/dist/pie/index.d.ts +1 -1
  27. package/dist/pie/index.js +1 -1
  28. package/dist/pie/index.js.map +1 -1
  29. package/dist/pie/initialization.js +2 -1
  30. package/dist/pie/initialization.js.map +1 -1
  31. package/dist/pie/instrumentation-event-bridge.js.map +1 -1
  32. package/dist/pie/instrumentation-event-map.js.map +1 -1
  33. package/dist/pie/instrumentation-provider-resolution.js.map +1 -1
  34. package/dist/pie/item-controller-storage.js.map +1 -1
  35. package/dist/pie/item-controller.d.ts +15 -0
  36. package/dist/pie/item-controller.js +22 -2
  37. package/dist/pie/item-controller.js.map +1 -1
  38. package/dist/pie/item-session-contract.d.ts +1 -0
  39. package/dist/pie/item-session-contract.js +28 -13
  40. package/dist/pie/item-session-contract.js.map +1 -1
  41. package/dist/pie/math-rendering.js.map +1 -1
  42. package/dist/pie/overrides.js +2 -1
  43. package/dist/pie/overrides.js.map +1 -1
  44. package/dist/pie/resource-monitor.js.map +1 -1
  45. package/dist/pie/stage-tracker.js.map +1 -1
  46. package/dist/pie/types.d.ts +7 -0
  47. package/dist/pie/types.js.map +1 -1
  48. package/dist/pie/updates.d.ts +3 -3
  49. package/dist/pie/updates.js +48 -25
  50. package/dist/pie/updates.js.map +1 -1
  51. package/dist/security/index.d.ts +1 -0
  52. package/dist/security/index.js +1 -0
  53. package/dist/security/index.js.map +1 -1
  54. package/dist/security/sanitize-item-markup.js.map +1 -1
  55. package/dist/security/wrap-model-rich-content.d.ts +1 -0
  56. package/dist/security/wrap-model-rich-content.js +41 -0
  57. package/dist/security/wrap-model-rich-content.js.map +1 -0
  58. package/dist/security/wrap-overwide-images.js +1 -2
  59. package/dist/security/wrap-overwide-images.js.map +1 -1
  60. package/dist/security/wrap-overwide-tables.js +1 -2
  61. package/dist/security/wrap-overwide-tables.js.map +1 -1
  62. package/dist/server/npm-registry.js.map +1 -1
  63. package/dist/types/index.d.ts +15 -3
  64. package/dist/types/index.js.map +1 -1
  65. package/package.json +1 -1
@@ -8,12 +8,12 @@ import type { UpdatePieElementOptions } from "./types.js";
8
8
  /**
9
9
  * Update a PIE element by ref (direct Element reference)
10
10
  */
11
- export declare const updatePieElementWithRef: (el: Element, opts: UpdatePieElementOptions) => void;
11
+ export declare const updatePieElementWithRef: (el: Element, opts: UpdatePieElementOptions) => Promise<void>;
12
12
  /**
13
13
  * Update a PIE element by name (finds all matching elements in DOM)
14
14
  */
15
- export declare const updatePieElement: (elName: string, opts: UpdatePieElementOptions) => void;
15
+ export declare const updatePieElement: (elName: string, opts: UpdatePieElementOptions) => Promise<void>;
16
16
  /**
17
17
  * Update all PIE elements in a config
18
18
  */
19
- export declare const updatePieElements: (config: ConfigEntity, session: any[], env: Env, container?: Element | Document) => void;
19
+ export declare const updatePieElements: (config: ConfigEntity, session: any[], env: Env, container?: Element | Document, onElementSessionUpdate?: UpdatePieElementOptions["onElementSessionUpdate"]) => Promise<void>;
@@ -4,6 +4,7 @@
4
4
  * Functions for updating PIE elements with new models, sessions, and env.
5
5
  */
6
6
  import { mergeObjectsIgnoringNullUndefined } from "../object/index.js";
7
+ import { wrapModelRichContent } from "../security/wrap-model-rich-content.js";
7
8
  import { createPieLogger, isGlobalDebugEnabled } from "./logger.js";
8
9
  import { findPieController } from "./scoring.js";
9
10
  import { defaultPieElementOptions } from "./types.js";
@@ -58,17 +59,23 @@ const emitControllerError = (pieElement, detail) => {
58
59
  * Helper function to apply controller to element
59
60
  * Extracted to eliminate duplication and ensure consistent controller invocation
60
61
  */
61
- const applyControllerToElement = async (element, model, elementSession, controller, env, logPrefix) => {
62
+ const applyControllerToElement = async (element, model, elementSession, controller, env, logPrefix, onElementSessionUpdate) => {
62
63
  logger.debug(`${logPrefix} Using controller, env:`, env);
63
64
  logger.debug(`${logPrefix} Model before filter:`, {
64
65
  id: model.id,
65
66
  element: model.element,
66
67
  hasCorrectResponse: "correctResponse" in model,
67
68
  });
68
- // Create updateSession callback for controller to save shuffle order
69
+ // Create updateSession callback for controller to save derived state (e.g.
70
+ // shuffle order). Mutate the in-flight element session so this render uses it,
71
+ // AND propagate the write to the authoritative session via the host callback
72
+ // so subsequent renders reuse it instead of regenerating it. The write-back is
73
+ // keyed by the canonical model id/element (the entry findOrAddSession resolved),
74
+ // which also tolerates controllers that pass an undefined id/element.
69
75
  const updateSession = (id, _elementName, properties) => {
70
76
  logger.debug(`${logPrefix} updateSession called for ${id} with:`, properties);
71
77
  Object.assign(elementSession, properties);
78
+ onElementSessionUpdate?.(model.id, model.element, properties);
72
79
  return Promise.resolve();
73
80
  };
74
81
  try {
@@ -86,14 +93,15 @@ const applyControllerToElement = async (element, model, elementSession, controll
86
93
  element: model.element,
87
94
  ...controllerResultObject,
88
95
  };
96
+ const wrappedModel = wrapModelRichContent(filteredModel);
89
97
  logger.debug(`${logPrefix} ✅ Controller filtered model:`, {
90
- id: filteredModel.id,
91
- element: filteredModel.element,
92
- hasCorrectResponse: "correctResponse" in filteredModel,
98
+ id: wrappedModel.id,
99
+ element: wrappedModel.element,
100
+ hasCorrectResponse: "correctResponse" in wrappedModel,
93
101
  mode: env.mode,
94
102
  role: env.role,
95
103
  });
96
- element.model = filteredModel;
104
+ element.model = wrappedModel;
97
105
  element.session = elementSession;
98
106
  }
99
107
  catch (err) {
@@ -102,7 +110,7 @@ const applyControllerToElement = async (element, model, elementSession, controll
102
110
  }
103
111
  };
104
112
  const resolveAndValidateUpdateOptions = (opts) => {
105
- const { config, session, env, eventListeners, invokeControllerForModel } = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);
113
+ const { config, session, env, eventListeners, invokeControllerForModel, onElementSessionUpdate, } = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);
106
114
  if (!env) {
107
115
  throw new Error("env is required");
108
116
  }
@@ -112,10 +120,17 @@ const resolveAndValidateUpdateOptions = (opts) => {
112
120
  if (!config) {
113
121
  throw new Error("config is required");
114
122
  }
115
- return { config, session, env, eventListeners, invokeControllerForModel };
123
+ return {
124
+ config,
125
+ session,
126
+ env,
127
+ eventListeners,
128
+ invokeControllerForModel,
129
+ onElementSessionUpdate,
130
+ };
116
131
  };
117
- const updateSinglePieElement = (pieElement, controllerLookupTag, options, logContext) => {
118
- const { config, session, env, eventListeners, invokeControllerForModel } = options;
132
+ const updateSinglePieElement = async (pieElement, controllerLookupTag, options, logContext) => {
133
+ const { config, session, env, eventListeners, invokeControllerForModel, onElementSessionUpdate, } = options;
119
134
  const model = config.models?.find((m) => m.id === pieElement.id);
120
135
  if (!model) {
121
136
  logger.error(`${logContext} Model not found for`, controllerLookupTag);
@@ -132,7 +147,7 @@ const updateSinglePieElement = (pieElement, controllerLookupTag, options, logCon
132
147
  const controller = findPieController(controllerLookupTag);
133
148
  if (!controller) {
134
149
  logger.debug(`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`);
135
- pieElement.model = model;
150
+ pieElement.model = wrapModelRichContent(model);
136
151
  pieElement.session = elementSession;
137
152
  return;
138
153
  }
@@ -141,7 +156,14 @@ const updateSinglePieElement = (pieElement, controllerLookupTag, options, logCon
141
156
  role: env.role,
142
157
  hasCorrectResponse: "correctResponse" in model,
143
158
  });
144
- applyControllerToElement(pieElement, model, elementSession, controller, env, `${logContext}(${controllerLookupTag}#${pieElement.id})`).catch((err) => {
159
+ // Await the controller so any updateSession write-back completes before the
160
+ // caller computes/emits the session signature; otherwise a late, async
161
+ // shuffle write lands after the cycle that read the session and the order
162
+ // never round-trips (PIE-631).
163
+ try {
164
+ await applyControllerToElement(pieElement, model, elementSession, controller, env, `${logContext}(${controllerLookupTag}#${pieElement.id})`, onElementSessionUpdate);
165
+ }
166
+ catch (err) {
145
167
  const errorMessage = err instanceof Error ? err.message : String(err);
146
168
  const isContractError = errorMessage.includes("Controller contract mismatch");
147
169
  logger.error(`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`, err);
@@ -156,13 +178,13 @@ const updateSinglePieElement = (pieElement, controllerLookupTag, options, logCon
156
178
  cause: errorMessage,
157
179
  });
158
180
  // Fall back to raw model on controller error
159
- pieElement.model = model;
181
+ pieElement.model = wrapModelRichContent(model);
160
182
  pieElement.session = elementSession;
161
- });
183
+ }
162
184
  }
163
185
  else {
164
186
  logger.debug(`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`);
165
- pieElement.model = model;
187
+ pieElement.model = wrapModelRichContent(model);
166
188
  pieElement.session = elementSession;
167
189
  }
168
190
  };
@@ -173,7 +195,7 @@ export const updatePieElementWithRef = (el, opts) => {
173
195
  const options = resolveAndValidateUpdateOptions(opts);
174
196
  const pieElement = el;
175
197
  const elName = pieElement.tagName.toLowerCase();
176
- updateSinglePieElement(pieElement, elName, options, "[updatePieElementWithRef]");
198
+ return updateSinglePieElement(pieElement, elName, options, "[updatePieElementWithRef]");
177
199
  };
178
200
  /**
179
201
  * Update a PIE element by name (finds all matching elements in DOM)
@@ -186,20 +208,21 @@ export const updatePieElement = (elName, opts) => {
186
208
  const pieElements = searchRoot.querySelectorAll(elName);
187
209
  if (!pieElements || pieElements.length === 0) {
188
210
  logger.debug(`no elements found for ${elName}`);
189
- return;
211
+ return Promise.resolve();
190
212
  }
191
- pieElements.forEach((el) => {
192
- const pieElement = el;
193
- updateSinglePieElement(pieElement, elName, options, "[updatePieElement]");
194
- });
213
+ return Promise.all(Array.from(pieElements, (el) => updateSinglePieElement(el, elName, options, "[updatePieElement]"))).then(() => undefined);
195
214
  };
196
215
  /**
197
216
  * Update all PIE elements in a config
198
217
  */
199
- export const updatePieElements = (config, session, env, container) => {
218
+ export const updatePieElements = (config, session, env, container, onElementSessionUpdate) => {
200
219
  logger.debug("[updatePieElements] Updating all elements with env:", env);
201
- Object.entries(config.elements).forEach(([elName, _pkg]) => {
202
- updatePieElement(elName, { config, session, env, container });
203
- });
220
+ return Promise.all(Object.entries(config.elements).map(([elName, _pkg]) => updatePieElement(elName, {
221
+ config,
222
+ session,
223
+ env,
224
+ container,
225
+ onElementSessionUpdate,
226
+ }))).then(() => undefined);
204
227
  };
205
228
  //# sourceMappingURL=updates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/pie/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PIE Updates Module\n *\n * Functions for updating PIE elements with new models, sessions, and env.\n */\n\nimport { mergeObjectsIgnoringNullUndefined } from \"../object/index.js\";\nimport type { ConfigEntity, Env, PieModel } from \"../types/index.js\";\nimport { createPieLogger, isGlobalDebugEnabled } from \"./logger.js\";\nimport { findPieController } from \"./scoring.js\";\nimport type { PieElement, UpdatePieElementOptions } from \"./types.js\";\nimport { defaultPieElementOptions } from \"./types.js\";\nimport { findOrAddSession } from \"./utils.js\";\n\n// Create module-level logger (respects global debug flag - pass function for dynamic checking)\nconst logger = createPieLogger(\"pie-updates\", () => isGlobalDebugEnabled());\n\ntype ControllerErrorDetail = {\n\tcode: \"PIE_CONTROLLER_CONTRACT_ERROR\" | \"PIE_CONTROLLER_RUNTIME_ERROR\";\n\tmessage: string;\n\telementName: string;\n\telementId: string;\n\tcontrollerShape?: string;\n\tcause?: string;\n};\n\nconst describeControllerShape = (controller: unknown): string => {\n\tif (!controller) return \"missing\";\n\tif (typeof controller === \"function\") return \"function\";\n\tif (typeof controller !== \"object\") return typeof controller;\n\tconst keys = Object.keys(controller as Record<string, unknown>);\n\tconst defaultValue = (controller as Record<string, unknown>).default;\n\tconst defaultKeys =\n\t\tdefaultValue && typeof defaultValue === \"object\"\n\t\t\t? Object.keys(defaultValue as Record<string, unknown>)\n\t\t\t: [];\n\treturn defaultKeys.length > 0\n\t\t? `object(keys=[${keys.join(\",\")}],defaultKeys=[${defaultKeys.join(\",\")}])`\n\t\t: `object(keys=[${keys.join(\",\")}])`;\n};\n\nconst resolveControllerModelFunction = (\n\tcontroller: unknown,\n): ((model: PieModel, session: any, env: Env, updateSession: any) => unknown) | null => {\n\tif (!controller) return null;\n\tif (typeof controller === \"function\") {\n\t\treturn controller as (model: PieModel, session: any, env: Env, updateSession: any) => unknown;\n\t}\n\tif (typeof controller !== \"object\") return null;\n\tconst direct = (controller as Record<string, unknown>).model;\n\tif (typeof direct === \"function\") {\n\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t(direct as Function).call(controller, model, sessionData, env, updateSession);\n\t}\n\tconst fromDefault = (controller as Record<string, unknown>).default;\n\tif (fromDefault && typeof fromDefault === \"object\") {\n\t\tconst nested = (fromDefault as Record<string, unknown>).model;\n\t\tif (typeof nested === \"function\") {\n\t\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t\t(nested as Function).call(fromDefault, model, sessionData, env, updateSession);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst emitControllerError = (\n\tpieElement: PieElement,\n\tdetail: ControllerErrorDetail,\n): void => {\n\tpieElement.dispatchEvent(\n\t\tnew CustomEvent(\"pie-controller-error\", {\n\t\t\tdetail,\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t}),\n\t);\n};\n\n/**\n * Helper function to apply controller to element\n * Extracted to eliminate duplication and ensure consistent controller invocation\n */\nconst applyControllerToElement = async (\n\telement: PieElement,\n\tmodel: PieModel,\n\telementSession: any,\n\tcontroller: unknown,\n\tenv: Env,\n\tlogPrefix: string,\n): Promise<void> => {\n\tlogger.debug(`${logPrefix} Using controller, env:`, env);\n\tlogger.debug(`${logPrefix} Model before filter:`, {\n\t\tid: model.id,\n\t\telement: model.element,\n\t\thasCorrectResponse: \"correctResponse\" in model,\n\t});\n\n\t// Create updateSession callback for controller to save shuffle order\n\tconst updateSession = (id: string, _elementName: string, properties: any) => {\n\t\tlogger.debug(\n\t\t\t`${logPrefix} updateSession called for ${id} with:`,\n\t\t\tproperties,\n\t\t);\n\t\tObject.assign(elementSession, properties);\n\t\treturn Promise.resolve();\n\t};\n\n\ttry {\n\t\tconst modelFunction = resolveControllerModelFunction(controller);\n\t\tif (!modelFunction) {\n\t\t\tthrow new Error(\n\t\t\t\t`Controller contract mismatch: expected a model() function but received ${describeControllerShape(\n\t\t\t\t\tcontroller,\n\t\t\t\t)}`,\n\t\t\t);\n\t\t}\n\t\tconst controllerResult = await modelFunction(model, elementSession, env, updateSession);\n\n\t\t// Merge controller result with id and element (like server-side PieControllerExecutor does)\n\t\tconst controllerResultObject =\n\t\t\tcontrollerResult && typeof controllerResult === \"object\"\n\t\t\t\t? (controllerResult as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst filteredModel = {\n\t\t\tid: model.id,\n\t\t\telement: model.element,\n\t\t\t...controllerResultObject,\n\t\t};\n\n\t\tlogger.debug(`${logPrefix} ✅ Controller filtered model:`, {\n\t\t\tid: filteredModel.id,\n\t\t\telement: filteredModel.element,\n\t\t\thasCorrectResponse: \"correctResponse\" in filteredModel,\n\t\t\tmode: env.mode,\n\t\t\trole: env.role,\n\t\t});\n\n\t\telement.model = filteredModel;\n\t\telement.session = elementSession;\n\t} catch (err) {\n\t\tlogger.error(`${logPrefix} ❌ Controller error:`, err);\n\t\tthrow err; // Re-throw - controller errors are fatal\n\t}\n};\n\ntype ResolvedUpdateOptions = Pick<\n\tUpdatePieElementOptions,\n\t\"config\" | \"session\" | \"env\" | \"eventListeners\" | \"invokeControllerForModel\"\n>;\n\nconst resolveAndValidateUpdateOptions = (\n\topts: UpdatePieElementOptions,\n): ResolvedUpdateOptions => {\n\tconst { config, session, env, eventListeners, invokeControllerForModel } =\n\t\tmergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);\n\tif (!env) {\n\t\tthrow new Error(\"env is required\");\n\t}\n\tif (!session) {\n\t\tthrow new Error(\"session is required\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"config is required\");\n\t}\n\treturn { config, session, env, eventListeners, invokeControllerForModel };\n};\n\nconst updateSinglePieElement = (\n\tpieElement: PieElement,\n\tcontrollerLookupTag: string,\n\toptions: ResolvedUpdateOptions,\n\tlogContext: string,\n): void => {\n\tconst { config, session, env, eventListeners, invokeControllerForModel } =\n\t\toptions;\n\tconst model = config.models?.find((m) => m.id === pieElement.id) as\n\t\t| PieModel\n\t\t| undefined;\n\tif (!model) {\n\t\tlogger.error(`${logContext} Model not found for`, controllerLookupTag);\n\t\tthrow new Error(`model not found for ${controllerLookupTag}`);\n\t}\n\n\tconst elementSession = findOrAddSession(session, model.id, model.element);\n\n\t// Always attach event listeners (don't skip them for no-controller case)\n\tif (eventListeners) {\n\t\tObject.entries(eventListeners).forEach(([evt, fn]) => {\n\t\t\tpieElement.addEventListener(evt as any, fn);\n\t\t});\n\t}\n\n\tif (env && invokeControllerForModel) {\n\t\tconst controller = findPieController(controllerLookupTag);\n\t\tif (!controller) {\n\t\t\tlogger.debug(\n\t\t\t\t`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`,\n\t\t\t);\n\t\t\tpieElement.model = model;\n\t\t\tpieElement.session = elementSession;\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`${logContext} Invoking controller for ${controllerLookupTag}#${pieElement.id}`,\n\t\t\t{\n\t\t\t\tmode: env.mode,\n\t\t\t\trole: env.role,\n\t\t\t\thasCorrectResponse: \"correctResponse\" in model,\n\t\t\t},\n\t\t);\n\n\t\tapplyControllerToElement(\n\t\t\tpieElement,\n\t\t\tmodel,\n\t\t\telementSession,\n\t\t\tcontroller,\n\t\t\tenv,\n\t\t\t`${logContext}(${controllerLookupTag}#${pieElement.id})`,\n\t\t).catch((err) => {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\tconst isContractError = errorMessage.includes(\n\t\t\t\t\"Controller contract mismatch\",\n\t\t\t);\n\t\t\tlogger.error(\n\t\t\t\t`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\temitControllerError(pieElement, {\n\t\t\t\tcode: isContractError\n\t\t\t\t\t? \"PIE_CONTROLLER_CONTRACT_ERROR\"\n\t\t\t\t\t: \"PIE_CONTROLLER_RUNTIME_ERROR\",\n\t\t\t\tmessage: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,\n\t\t\t\telementName: controllerLookupTag,\n\t\t\t\telementId: pieElement.id,\n\t\t\t\tcontrollerShape: describeControllerShape(controller),\n\t\t\t\tcause: errorMessage,\n\t\t\t});\n\t\t\t// Fall back to raw model on controller error\n\t\t\tpieElement.model = model;\n\t\t\tpieElement.session = elementSession;\n\t\t});\n\t} else {\n\t\tlogger.debug(\n\t\t\t`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`,\n\t\t);\n\t\tpieElement.model = model;\n\t\tpieElement.session = elementSession;\n\t}\n};\n\n/**\n * Update a PIE element by ref (direct Element reference)\n */\nexport const updatePieElementWithRef = (\n\tel: Element,\n\topts: UpdatePieElementOptions,\n): void => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst pieElement = el as PieElement;\n\tconst elName = pieElement.tagName.toLowerCase();\n\tupdateSinglePieElement(\n\t\tpieElement,\n\t\telName,\n\t\toptions,\n\t\t\"[updatePieElementWithRef]\",\n\t);\n};\n\n/**\n * Update a PIE element by name (finds all matching elements in DOM)\n */\nexport const updatePieElement = (\n\telName: string,\n\topts: UpdatePieElementOptions,\n): void => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst { container } = mergeObjectsIgnoringNullUndefined(\n\t\tdefaultPieElementOptions,\n\t\topts,\n\t);\n\t// Use container for scoped query or fallback to document for global query\n\tconst searchRoot = container || document;\n\tconst pieElements = searchRoot.querySelectorAll(elName);\n\tif (!pieElements || pieElements.length === 0) {\n\t\tlogger.debug(`no elements found for ${elName}`);\n\t\treturn;\n\t}\n\tpieElements.forEach((el) => {\n\t\tconst pieElement = el as PieElement;\n\t\tupdateSinglePieElement(pieElement, elName, options, \"[updatePieElement]\");\n\t});\n};\n\n/**\n * Update all PIE elements in a config\n */\nexport const updatePieElements = (\n\tconfig: ConfigEntity,\n\tsession: any[],\n\tenv: Env,\n\tcontainer?: Element | Document,\n): void => {\n\tlogger.debug(\"[updatePieElements] Updating all elements with env:\", env);\n\tObject.entries(config.elements).forEach(([elName, _pkg]) => {\n\t\tupdatePieElement(elName, { config, session, env, container });\n\t});\n};\n"]}
1
+ {"version":3,"file":"updates.js","sourceRoot":"","sources":["../../src/pie/updates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PIE Updates Module\n *\n * Functions for updating PIE elements with new models, sessions, and env.\n */\n\nimport { mergeObjectsIgnoringNullUndefined } from \"../object/index.js\";\nimport { wrapModelRichContent } from \"../security/wrap-model-rich-content.js\";\nimport type { ConfigEntity, Env, PieModel } from \"../types/index.js\";\nimport { createPieLogger, isGlobalDebugEnabled } from \"./logger.js\";\nimport { findPieController } from \"./scoring.js\";\nimport type { PieElement, UpdatePieElementOptions } from \"./types.js\";\nimport { defaultPieElementOptions } from \"./types.js\";\nimport { findOrAddSession } from \"./utils.js\";\n\n// Create module-level logger (respects global debug flag - pass function for dynamic checking)\nconst logger = createPieLogger(\"pie-updates\", () => isGlobalDebugEnabled());\n\ntype ControllerErrorDetail = {\n\tcode: \"PIE_CONTROLLER_CONTRACT_ERROR\" | \"PIE_CONTROLLER_RUNTIME_ERROR\";\n\tmessage: string;\n\telementName: string;\n\telementId: string;\n\tcontrollerShape?: string;\n\tcause?: string;\n};\n\nconst describeControllerShape = (controller: unknown): string => {\n\tif (!controller) return \"missing\";\n\tif (typeof controller === \"function\") return \"function\";\n\tif (typeof controller !== \"object\") return typeof controller;\n\tconst keys = Object.keys(controller as Record<string, unknown>);\n\tconst defaultValue = (controller as Record<string, unknown>).default;\n\tconst defaultKeys =\n\t\tdefaultValue && typeof defaultValue === \"object\"\n\t\t\t? Object.keys(defaultValue as Record<string, unknown>)\n\t\t\t: [];\n\treturn defaultKeys.length > 0\n\t\t? `object(keys=[${keys.join(\",\")}],defaultKeys=[${defaultKeys.join(\",\")}])`\n\t\t: `object(keys=[${keys.join(\",\")}])`;\n};\n\nconst resolveControllerModelFunction = (\n\tcontroller: unknown,\n):\n\t| ((model: PieModel, session: any, env: Env, updateSession: any) => unknown)\n\t| null => {\n\tif (!controller) return null;\n\tif (typeof controller === \"function\") {\n\t\treturn controller as (\n\t\t\tmodel: PieModel,\n\t\t\tsession: any,\n\t\t\tenv: Env,\n\t\t\tupdateSession: any,\n\t\t) => unknown;\n\t}\n\tif (typeof controller !== \"object\") return null;\n\tconst direct = (controller as Record<string, unknown>).model;\n\tif (typeof direct === \"function\") {\n\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t(direct as Function).call(\n\t\t\t\tcontroller,\n\t\t\t\tmodel,\n\t\t\t\tsessionData,\n\t\t\t\tenv,\n\t\t\t\tupdateSession,\n\t\t\t);\n\t}\n\tconst fromDefault = (controller as Record<string, unknown>).default;\n\tif (fromDefault && typeof fromDefault === \"object\") {\n\t\tconst nested = (fromDefault as Record<string, unknown>).model;\n\t\tif (typeof nested === \"function\") {\n\t\t\treturn (model, sessionData, env, updateSession) =>\n\t\t\t\t(nested as Function).call(\n\t\t\t\t\tfromDefault,\n\t\t\t\t\tmodel,\n\t\t\t\t\tsessionData,\n\t\t\t\t\tenv,\n\t\t\t\t\tupdateSession,\n\t\t\t\t);\n\t\t}\n\t}\n\treturn null;\n};\n\nconst emitControllerError = (\n\tpieElement: PieElement,\n\tdetail: ControllerErrorDetail,\n): void => {\n\tpieElement.dispatchEvent(\n\t\tnew CustomEvent(\"pie-controller-error\", {\n\t\t\tdetail,\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t}),\n\t);\n};\n\n/**\n * Helper function to apply controller to element\n * Extracted to eliminate duplication and ensure consistent controller invocation\n */\nconst applyControllerToElement = async (\n\telement: PieElement,\n\tmodel: PieModel,\n\telementSession: any,\n\tcontroller: unknown,\n\tenv: Env,\n\tlogPrefix: string,\n\tonElementSessionUpdate?: (\n\t\telementId: string,\n\t\telementName: string,\n\t\tproperties: Record<string, unknown>,\n\t) => void,\n): Promise<void> => {\n\tlogger.debug(`${logPrefix} Using controller, env:`, env);\n\tlogger.debug(`${logPrefix} Model before filter:`, {\n\t\tid: model.id,\n\t\telement: model.element,\n\t\thasCorrectResponse: \"correctResponse\" in model,\n\t});\n\n\t// Create updateSession callback for controller to save derived state (e.g.\n\t// shuffle order). Mutate the in-flight element session so this render uses it,\n\t// AND propagate the write to the authoritative session via the host callback\n\t// so subsequent renders reuse it instead of regenerating it. The write-back is\n\t// keyed by the canonical model id/element (the entry findOrAddSession resolved),\n\t// which also tolerates controllers that pass an undefined id/element.\n\tconst updateSession = (id: string, _elementName: string, properties: any) => {\n\t\tlogger.debug(\n\t\t\t`${logPrefix} updateSession called for ${id} with:`,\n\t\t\tproperties,\n\t\t);\n\t\tObject.assign(elementSession, properties);\n\t\tonElementSessionUpdate?.(model.id, model.element, properties);\n\t\treturn Promise.resolve();\n\t};\n\n\ttry {\n\t\tconst modelFunction = resolveControllerModelFunction(controller);\n\t\tif (!modelFunction) {\n\t\t\tthrow new Error(\n\t\t\t\t`Controller contract mismatch: expected a model() function but received ${describeControllerShape(\n\t\t\t\t\tcontroller,\n\t\t\t\t)}`,\n\t\t\t);\n\t\t}\n\t\tconst controllerResult = await modelFunction(\n\t\t\tmodel,\n\t\t\telementSession,\n\t\t\tenv,\n\t\t\tupdateSession,\n\t\t);\n\n\t\t// Merge controller result with id and element (like server-side PieControllerExecutor does)\n\t\tconst controllerResultObject =\n\t\t\tcontrollerResult && typeof controllerResult === \"object\"\n\t\t\t\t? (controllerResult as Record<string, unknown>)\n\t\t\t\t: {};\n\t\tconst filteredModel = {\n\t\t\tid: model.id,\n\t\t\telement: model.element,\n\t\t\t...controllerResultObject,\n\t\t};\n\t\tconst wrappedModel = wrapModelRichContent(filteredModel);\n\n\t\tlogger.debug(`${logPrefix} ✅ Controller filtered model:`, {\n\t\t\tid: wrappedModel.id,\n\t\t\telement: wrappedModel.element,\n\t\t\thasCorrectResponse: \"correctResponse\" in wrappedModel,\n\t\t\tmode: env.mode,\n\t\t\trole: env.role,\n\t\t});\n\n\t\telement.model = wrappedModel;\n\t\telement.session = elementSession;\n\t} catch (err) {\n\t\tlogger.error(`${logPrefix} ❌ Controller error:`, err);\n\t\tthrow err; // Re-throw - controller errors are fatal\n\t}\n};\n\ntype ResolvedUpdateOptions = Pick<\n\tUpdatePieElementOptions,\n\t| \"config\"\n\t| \"session\"\n\t| \"env\"\n\t| \"eventListeners\"\n\t| \"invokeControllerForModel\"\n\t| \"onElementSessionUpdate\"\n>;\n\nconst resolveAndValidateUpdateOptions = (\n\topts: UpdatePieElementOptions,\n): ResolvedUpdateOptions => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = mergeObjectsIgnoringNullUndefined(defaultPieElementOptions, opts);\n\tif (!env) {\n\t\tthrow new Error(\"env is required\");\n\t}\n\tif (!session) {\n\t\tthrow new Error(\"session is required\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"config is required\");\n\t}\n\treturn {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t};\n};\n\nconst updateSinglePieElement = async (\n\tpieElement: PieElement,\n\tcontrollerLookupTag: string,\n\toptions: ResolvedUpdateOptions,\n\tlogContext: string,\n): Promise<void> => {\n\tconst {\n\t\tconfig,\n\t\tsession,\n\t\tenv,\n\t\teventListeners,\n\t\tinvokeControllerForModel,\n\t\tonElementSessionUpdate,\n\t} = options;\n\tconst model = config.models?.find((m) => m.id === pieElement.id) as\n\t\t| PieModel\n\t\t| undefined;\n\tif (!model) {\n\t\tlogger.error(`${logContext} Model not found for`, controllerLookupTag);\n\t\tthrow new Error(`model not found for ${controllerLookupTag}`);\n\t}\n\n\tconst elementSession = findOrAddSession(session, model.id, model.element);\n\n\t// Always attach event listeners (don't skip them for no-controller case)\n\tif (eventListeners) {\n\t\tObject.entries(eventListeners).forEach(([evt, fn]) => {\n\t\t\tpieElement.addEventListener(evt as any, fn);\n\t\t});\n\t}\n\n\tif (env && invokeControllerForModel) {\n\t\tconst controller = findPieController(controllerLookupTag);\n\t\tif (!controller) {\n\t\t\tlogger.debug(\n\t\t\t\t`${logContext} ℹ️ No controller for ${controllerLookupTag}, using server-processed model`,\n\t\t\t);\n\t\t\tpieElement.model = wrapModelRichContent(model);\n\t\t\tpieElement.session = elementSession;\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`${logContext} Invoking controller for ${controllerLookupTag}#${pieElement.id}`,\n\t\t\t{\n\t\t\t\tmode: env.mode,\n\t\t\t\trole: env.role,\n\t\t\t\thasCorrectResponse: \"correctResponse\" in model,\n\t\t\t},\n\t\t);\n\n\t\t// Await the controller so any updateSession write-back completes before the\n\t\t// caller computes/emits the session signature; otherwise a late, async\n\t\t// shuffle write lands after the cycle that read the session and the order\n\t\t// never round-trips (PIE-631).\n\t\ttry {\n\t\t\tawait applyControllerToElement(\n\t\t\t\tpieElement,\n\t\t\t\tmodel,\n\t\t\t\telementSession,\n\t\t\t\tcontroller,\n\t\t\t\tenv,\n\t\t\t\t`${logContext}(${controllerLookupTag}#${pieElement.id})`,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tconst errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\tconst isContractError = errorMessage.includes(\n\t\t\t\t\"Controller contract mismatch\",\n\t\t\t);\n\t\t\tlogger.error(\n\t\t\t\t`${logContext} Controller failed for ${controllerLookupTag}#${pieElement.id}:`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\temitControllerError(pieElement, {\n\t\t\t\tcode: isContractError\n\t\t\t\t\t? \"PIE_CONTROLLER_CONTRACT_ERROR\"\n\t\t\t\t\t: \"PIE_CONTROLLER_RUNTIME_ERROR\",\n\t\t\t\tmessage: `${controllerLookupTag} controller failed while applying model for ${pieElement.id}. ${errorMessage}`,\n\t\t\t\telementName: controllerLookupTag,\n\t\t\t\telementId: pieElement.id,\n\t\t\t\tcontrollerShape: describeControllerShape(controller),\n\t\t\t\tcause: errorMessage,\n\t\t\t});\n\t\t\t// Fall back to raw model on controller error\n\t\t\tpieElement.model = wrapModelRichContent(model);\n\t\t\tpieElement.session = elementSession;\n\t\t}\n\t} else {\n\t\tlogger.debug(\n\t\t\t`${logContext} Direct model assignment for ${controllerLookupTag}#${pieElement.id} (no controller invocation requested)`,\n\t\t);\n\t\tpieElement.model = wrapModelRichContent(model);\n\t\tpieElement.session = elementSession;\n\t}\n};\n\n/**\n * Update a PIE element by ref (direct Element reference)\n */\nexport const updatePieElementWithRef = (\n\tel: Element,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst pieElement = el as PieElement;\n\tconst elName = pieElement.tagName.toLowerCase();\n\treturn updateSinglePieElement(\n\t\tpieElement,\n\t\telName,\n\t\toptions,\n\t\t\"[updatePieElementWithRef]\",\n\t);\n};\n\n/**\n * Update a PIE element by name (finds all matching elements in DOM)\n */\nexport const updatePieElement = (\n\telName: string,\n\topts: UpdatePieElementOptions,\n): Promise<void> => {\n\tconst options = resolveAndValidateUpdateOptions(opts);\n\tconst { container } = mergeObjectsIgnoringNullUndefined(\n\t\tdefaultPieElementOptions,\n\t\topts,\n\t);\n\t// Use container for scoped query or fallback to document for global query\n\tconst searchRoot = container || document;\n\tconst pieElements = searchRoot.querySelectorAll(elName);\n\tif (!pieElements || pieElements.length === 0) {\n\t\tlogger.debug(`no elements found for ${elName}`);\n\t\treturn Promise.resolve();\n\t}\n\treturn Promise.all(\n\t\tArray.from(pieElements, (el) =>\n\t\t\tupdateSinglePieElement(\n\t\t\t\tel as PieElement,\n\t\t\t\telName,\n\t\t\t\toptions,\n\t\t\t\t\"[updatePieElement]\",\n\t\t\t),\n\t\t),\n\t).then(() => undefined);\n};\n\n/**\n * Update all PIE elements in a config\n */\nexport const updatePieElements = (\n\tconfig: ConfigEntity,\n\tsession: any[],\n\tenv: Env,\n\tcontainer?: Element | Document,\n\tonElementSessionUpdate?: UpdatePieElementOptions[\"onElementSessionUpdate\"],\n): Promise<void> => {\n\tlogger.debug(\"[updatePieElements] Updating all elements with env:\", env);\n\treturn Promise.all(\n\t\tObject.entries(config.elements).map(([elName, _pkg]) =>\n\t\t\tupdatePieElement(elName, {\n\t\t\t\tconfig,\n\t\t\t\tsession,\n\t\t\t\tenv,\n\t\t\t\tcontainer,\n\t\t\t\tonElementSessionUpdate,\n\t\t\t}),\n\t\t),\n\t).then(() => undefined);\n};\n"]}
@@ -2,4 +2,5 @@ export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifie
2
2
  export { parseAllowedStyleOrigins, validateExternalStyleUrl, type StyleUrlValidationError, type StyleUrlValidationOk, type StyleUrlValidationOptions, type StyleUrlValidationResult, } from "./validate-style-url.js";
3
3
  export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
4
4
  export { wrapOverwideImages } from "./wrap-overwide-images.js";
5
+ export { wrapModelRichContent } from "./wrap-model-rich-content.js";
5
6
  export { wrapOverwideTables } from "./wrap-overwide-tables.js";
@@ -2,5 +2,6 @@ export { buildAuthoringAllowList, createDefaultItemMarkupSanitizer, resetPurifie
2
2
  export { parseAllowedStyleOrigins, validateExternalStyleUrl, } from "./validate-style-url.js";
3
3
  export { resetSvgSanitizerForTesting, sanitizeSvgIcon, } from "./sanitize-svg-icon.js";
4
4
  export { wrapOverwideImages } from "./wrap-overwide-images.js";
5
+ export { wrapModelRichContent } from "./wrap-model-rich-content.js";
5
6
  export { wrapOverwideTables } from "./wrap-overwide-tables.js";
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,uBAAuB,EACvB,gCAAgC,EAChC,uBAAuB,EACvB,kBAAkB,GAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GAKxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,2BAA2B,EAC3B,eAAe,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n\tbuildAuthoringAllowList,\n\tcreateDefaultItemMarkupSanitizer,\n\tresetPurifierForTesting,\n\tsanitizeItemMarkup,\n\ttype ItemMarkupSanitizer,\n\ttype SanitizeItemMarkupOptions,\n} from \"./sanitize-item-markup.js\";\nexport {\n\tparseAllowedStyleOrigins,\n\tvalidateExternalStyleUrl,\n\ttype StyleUrlValidationError,\n\ttype StyleUrlValidationOk,\n\ttype StyleUrlValidationOptions,\n\ttype StyleUrlValidationResult,\n} from \"./validate-style-url.js\";\nexport {\n\tresetSvgSanitizerForTesting,\n\tsanitizeSvgIcon,\n} from \"./sanitize-svg-icon.js\";\nexport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nexport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,uBAAuB,EACvB,gCAAgC,EAChC,uBAAuB,EACvB,kBAAkB,GAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GAKxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,2BAA2B,EAC3B,eAAe,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n\tbuildAuthoringAllowList,\n\tcreateDefaultItemMarkupSanitizer,\n\tresetPurifierForTesting,\n\tsanitizeItemMarkup,\n\ttype ItemMarkupSanitizer,\n\ttype SanitizeItemMarkupOptions,\n} from \"./sanitize-item-markup.js\";\nexport {\n\tparseAllowedStyleOrigins,\n\tvalidateExternalStyleUrl,\n\ttype StyleUrlValidationError,\n\ttype StyleUrlValidationOk,\n\ttype StyleUrlValidationOptions,\n\ttype StyleUrlValidationResult,\n} from \"./validate-style-url.js\";\nexport {\n\tresetSvgSanitizerForTesting,\n\tsanitizeSvgIcon,\n} from \"./sanitize-svg-icon.js\";\nexport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nexport { wrapModelRichContent } from \"./wrap-model-rich-content.js\";\nexport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize-item-markup.js","sourceRoot":"","sources":["../../src/security/sanitize-item-markup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Default sanitizer for PIE item / passage markup.\n *\n * Used by `PieItemPlayer.svelte` to strip scripts, event-handler attributes,\n * and unknown tags before injecting authored markup via `{@html}`. Hosts can\n * opt out with the `trust-markup` attribute on the `<pie-*-player>` element,\n * or supply their own sanitizer function if they need a stricter / looser\n * allow-list.\n */\n\nimport DOMPurify from \"dompurify\";\n\nimport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nexport type ItemMarkupSanitizer = (markup: string) => string;\n\nexport interface SanitizeItemMarkupOptions {\n\t/**\n\t * Extra custom-element tag names that should survive sanitization in\n\t * addition to the default `pie-*` allow-list. Useful for authoring-mode\n\t * tags that rewrite to `pie-*-config` or host-registered extensions.\n\t */\n\tallowedCustomElements?: string[];\n}\n\n// Attributes every PIE element / wrapper is allowed to carry.\nconst BASE_ALLOWED_ATTRS = [\n\t\"slot\",\n\t\"role\",\n\t\"tabindex\",\n\t\"id\",\n\t\"class\",\n\t\"style\",\n\t\"href\",\n\t\"src\",\n\t\"alt\",\n\t\"title\",\n\t\"hidden\",\n\t\"disabled\",\n\t\"lang\",\n\t\"dir\",\n];\n\nconst BASE_URI_SAFE_ATTRS = [\"pie-id\"];\n\nconst FORBIDDEN_TAGS = [\n\t\"script\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"base\",\n\t\"form\",\n\t\"meta\",\n\t\"link\",\n\t// <foreignObject> inside an <svg> is a well-known escape hatch back\n\t// into HTML context; match the SVG-icon sanitizer and forbid it here\n\t// so both sanitizers agree on the surface.\n\t\"foreignobject\",\n];\n\n// DOMPurify already strips `on*` handlers via its default block-list;\n// these entries guarantee they stay stripped even if a consumer tweaks\n// defaults, and they cover the common SVG / math sinks.\nconst FORBIDDEN_ATTRS = [\n\t\"onerror\",\n\t\"onload\",\n\t\"onclick\",\n\t\"onmouseover\",\n\t\"onmouseout\",\n\t\"onmouseenter\",\n\t\"onmouseleave\",\n\t\"onfocus\",\n\t\"onblur\",\n\t\"onkeydown\",\n\t\"onkeyup\",\n\t\"onkeypress\",\n\t\"onsubmit\",\n\t\"onchange\",\n\t\"onbeforeunload\",\n\t\"formaction\",\n\t\"xlink:href\",\n];\n\n// Any tag that looks like a custom element (contains a hyphen) is permitted\n// provided it starts with `pie-` or is explicitly named in\n// `allowedCustomElements`. This intentionally keeps third-party unknown\n// custom elements out unless the host opts in.\nconst PIE_CUSTOM_ELEMENT_REGEX = /^pie-[a-z0-9-]+$/i;\n\n// Attribute names that custom elements are allowed to declare. We stay\n// permissive for the PIE element contract (`model-*`, `session-*`, ...) and\n// the standard `data-*` / `aria-*` families.\nconst CUSTOM_ELEMENT_ATTR_REGEX =\n\t/^(id|class|style|slot|role|tabindex|hidden|disabled|lang|dir|data-[\\w-]+|aria-[\\w-]+|pie-[\\w-]+|model-[\\w-]+|session-[\\w-]+|config-[\\w-]+|context-[\\w-]+)$/i;\n\ninterface DOMPurifyInstance {\n\tsanitize: (\n\t\tsource: string,\n\t\tconfig?: Record<string, unknown>,\n\t) => string | Node | DocumentFragment;\n}\n\nlet purifierInstance: DOMPurifyInstance | null = null;\n\nfunction resolvePurifier(): DOMPurifyInstance | null {\n\tif (purifierInstance) return purifierInstance;\n\tif (typeof window === \"undefined\" || !window.document) return null;\n\t// DOMPurify's default export is both the instance and the factory.\n\t// Calling it with a window binds the instance to that document.\n\tconst factory = DOMPurify as unknown as (\n\t\twin: Window & typeof globalThis,\n\t) => DOMPurifyInstance;\n\tpurifierInstance =\n\t\ttypeof factory === \"function\"\n\t\t\t? factory(window as Window & typeof globalThis)\n\t\t\t: (DOMPurify as unknown as DOMPurifyInstance);\n\treturn purifierInstance;\n}\n\n/**\n * Sanitize raw item/passage markup before it is injected into the DOM.\n *\n * - Strips `<script>`, event-handler attributes, unknown protocols and\n * a standard set of dangerous tags (`iframe`, `object`, `embed`, `base`,\n * `form`, `meta`, `link`).\n * - Preserves PIE custom elements (`pie-*`) and any extra tags listed in\n * `allowedCustomElements`.\n * - During SSR (no `window`) returns an empty string so untrusted markup\n * never reaches the prerender output; the live renderer will re-run the\n * sanitizer on hydrate.\n */\nexport function sanitizeItemMarkup(\n\tmarkup: string,\n\toptions: SanitizeItemMarkupOptions = {},\n): string {\n\tif (!markup) return \"\";\n\tconst purifier = resolvePurifier();\n\tif (!purifier) return \"\";\n\n\tconst allowedCustomElements = (options.allowedCustomElements ?? []).map(\n\t\t(name) => name.toLowerCase(),\n\t);\n\tconst explicitCustomElementSet = new Set(allowedCustomElements);\n\n\tconst result = purifier.sanitize(markup, {\n\t\tADD_TAGS: allowedCustomElements,\n\t\tADD_ATTR: BASE_ALLOWED_ATTRS,\n\t\tADD_URI_SAFE_ATTR: BASE_URI_SAFE_ATTRS,\n\t\tFORBID_TAGS: FORBIDDEN_TAGS,\n\t\tFORBID_ATTR: FORBIDDEN_ATTRS,\n\t\tALLOW_UNKNOWN_PROTOCOLS: false,\n\t\tSANITIZE_DOM: true,\n\t\t// pie-item contract compatibility: PIE models are matched to DOM\n\t\t// elements via strict `id` equality (see `updateSinglePieElement`\n\t\t// in players-shared/src/pie/updates.ts). `SANITIZE_NAMED_PROPS`\n\t\t// would prefix every `id`/`name` with `user-content-`, which silently\n\t\t// breaks model lookup for every item. `SANITIZE_DOM: true` above\n\t\t// still provides the core DOM-clobbering defenses we rely on.\n\t\tSANITIZE_NAMED_PROPS: false,\n\t\tWHOLE_DOCUMENT: false,\n\t\tALLOW_DATA_ATTR: true,\n\t\tALLOW_ARIA_ATTR: true,\n\t\tCUSTOM_ELEMENT_HANDLING: {\n\t\t\ttagNameCheck: (tagName: string) => {\n\t\t\t\tconst lower = tagName.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tPIE_CUSTOM_ELEMENT_REGEX.test(lower) ||\n\t\t\t\t\texplicitCustomElementSet.has(lower)\n\t\t\t\t);\n\t\t\t},\n\t\t\tattributeNameCheck: (attrName: string) =>\n\t\t\t\tCUSTOM_ELEMENT_ATTR_REGEX.test(attrName),\n\t\t\tallowCustomizedBuiltInElements: false,\n\t\t},\n\t\tRETURN_TRUSTED_TYPE: false,\n\t});\n\n\tconst sanitized =\n\t\ttypeof result === \"string\" ? result : String(result ?? \"\");\n\t// PIE-94: wrap overwide authored images in a horizontal-scroll container\n\t// so they don't get clipped by ancestor `overflow-x: hidden` regions in\n\t// the section player (and match WCAG 1.4.10 Reflow at 400% zoom).\n\t// Tables get the same treatment so wide data grids reflow into a\n\t// scrollable region instead of forcing the page itself to scroll.\n\treturn wrapOverwideTables(wrapOverwideImages(sanitized));\n}\n\n/**\n * Build the default `ItemMarkupSanitizer` used by the players. The returned\n * function is stable for a given set of allowed custom elements so callers\n * can safely use reference equality when deciding whether to re-sanitize.\n */\nexport function createDefaultItemMarkupSanitizer(\n\toptions: SanitizeItemMarkupOptions = {},\n): ItemMarkupSanitizer {\n\tconst { allowedCustomElements } = options;\n\treturn (markup: string) =>\n\t\tsanitizeItemMarkup(markup, { allowedCustomElements });\n}\n\n/**\n * Derive the authoring-mode allow-list (`pie-*-config`) from a set of PIE\n * element tag names. Used by `transformMarkupForAuthoring` so the sanitizer\n * keeps the rewritten `-config` tags instead of stripping them.\n */\nexport function buildAuthoringAllowList(\n\telementTagNames: Iterable<string>,\n): string[] {\n\tconst out = new Set<string>();\n\tfor (const tag of elementTagNames) {\n\t\tif (!tag) continue;\n\t\tconst lower = tag.toLowerCase();\n\t\tout.add(lower);\n\t\tout.add(`${lower}-config`);\n\t}\n\treturn [...out];\n}\n\n/** Reset the memoised DOMPurify instance. Only intended for tests. */\nexport function resetPurifierForTesting() {\n\tpurifierInstance = null;\n}\n"]}
1
+ {"version":3,"file":"sanitize-item-markup.js","sourceRoot":"","sources":["../../src/security/sanitize-item-markup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Default sanitizer for PIE item / passage markup.\n *\n * Used by `PieItemPlayer.svelte` to strip scripts, event-handler attributes,\n * and unknown tags before injecting authored markup via `{@html}`. Hosts can\n * opt out with the `trust-markup` attribute on the `<pie-*-player>` element,\n * or supply their own sanitizer function if they need a stricter / looser\n * allow-list.\n */\n\nimport DOMPurify from \"dompurify\";\n\nimport { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nexport type ItemMarkupSanitizer = (markup: string) => string;\n\nexport interface SanitizeItemMarkupOptions {\n\t/**\n\t * Extra custom-element tag names that should survive sanitization in\n\t * addition to the default `pie-*` allow-list. Useful for authoring-mode\n\t * tags that rewrite to `pie-*-config` or host-registered extensions.\n\t */\n\tallowedCustomElements?: string[];\n}\n\n// Attributes every PIE element / wrapper is allowed to carry.\nconst BASE_ALLOWED_ATTRS = [\n\t\"slot\",\n\t\"role\",\n\t\"tabindex\",\n\t\"id\",\n\t\"class\",\n\t\"style\",\n\t\"href\",\n\t\"src\",\n\t\"alt\",\n\t\"title\",\n\t\"hidden\",\n\t\"disabled\",\n\t\"lang\",\n\t\"dir\",\n];\n\nconst BASE_URI_SAFE_ATTRS = [\"pie-id\"];\n\nconst FORBIDDEN_TAGS = [\n\t\"script\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"base\",\n\t\"form\",\n\t\"meta\",\n\t\"link\",\n\t// <foreignObject> inside an <svg> is a well-known escape hatch back\n\t// into HTML context; match the SVG-icon sanitizer and forbid it here\n\t// so both sanitizers agree on the surface.\n\t\"foreignobject\",\n];\n\n// DOMPurify already strips `on*` handlers via its default block-list;\n// these entries guarantee they stay stripped even if a consumer tweaks\n// defaults, and they cover the common SVG / math sinks.\nconst FORBIDDEN_ATTRS = [\n\t\"onerror\",\n\t\"onload\",\n\t\"onclick\",\n\t\"onmouseover\",\n\t\"onmouseout\",\n\t\"onmouseenter\",\n\t\"onmouseleave\",\n\t\"onfocus\",\n\t\"onblur\",\n\t\"onkeydown\",\n\t\"onkeyup\",\n\t\"onkeypress\",\n\t\"onsubmit\",\n\t\"onchange\",\n\t\"onbeforeunload\",\n\t\"formaction\",\n\t\"xlink:href\",\n];\n\n// Any tag that looks like a custom element (contains a hyphen) is permitted\n// provided it starts with `pie-` or is explicitly named in\n// `allowedCustomElements`. This intentionally keeps third-party unknown\n// custom elements out unless the host opts in.\nconst PIE_CUSTOM_ELEMENT_REGEX = /^pie-[a-z0-9-]+$/i;\n\n// Attribute names that custom elements are allowed to declare. We stay\n// permissive for the PIE element contract (`model-*`, `session-*`, ...) and\n// the standard `data-*` / `aria-*` families.\nconst CUSTOM_ELEMENT_ATTR_REGEX =\n\t/^(id|class|style|slot|role|tabindex|hidden|disabled|lang|dir|data-[\\w-]+|aria-[\\w-]+|pie-[\\w-]+|model-[\\w-]+|session-[\\w-]+|config-[\\w-]+|context-[\\w-]+)$/i;\n\ninterface DOMPurifyInstance {\n\tsanitize: (\n\t\tsource: string,\n\t\tconfig?: Record<string, unknown>,\n\t) => string | Node | DocumentFragment;\n}\n\nlet purifierInstance: DOMPurifyInstance | null = null;\n\nfunction resolvePurifier(): DOMPurifyInstance | null {\n\tif (purifierInstance) return purifierInstance;\n\tif (typeof window === \"undefined\" || !window.document) return null;\n\t// DOMPurify's default export is both the instance and the factory.\n\t// Calling it with a window binds the instance to that document.\n\tconst factory = DOMPurify as unknown as (\n\t\twin: Window & typeof globalThis,\n\t) => DOMPurifyInstance;\n\tpurifierInstance =\n\t\ttypeof factory === \"function\"\n\t\t\t? factory(window as Window & typeof globalThis)\n\t\t\t: (DOMPurify as unknown as DOMPurifyInstance);\n\treturn purifierInstance;\n}\n\n/**\n * Sanitize raw item/passage markup before it is injected into the DOM.\n *\n * - Strips `<script>`, event-handler attributes, unknown protocols and\n * a standard set of dangerous tags (`iframe`, `object`, `embed`, `base`,\n * `form`, `meta`, `link`).\n * - Preserves PIE custom elements (`pie-*`) and any extra tags listed in\n * `allowedCustomElements`.\n * - During SSR (no `window`) returns an empty string so untrusted markup\n * never reaches the prerender output; the live renderer will re-run the\n * sanitizer on hydrate.\n */\nexport function sanitizeItemMarkup(\n\tmarkup: string,\n\toptions: SanitizeItemMarkupOptions = {},\n): string {\n\tif (!markup) return \"\";\n\tconst purifier = resolvePurifier();\n\tif (!purifier) return \"\";\n\n\tconst allowedCustomElements = (options.allowedCustomElements ?? []).map(\n\t\t(name) => name.toLowerCase(),\n\t);\n\tconst explicitCustomElementSet = new Set(allowedCustomElements);\n\n\tconst result = purifier.sanitize(markup, {\n\t\tADD_TAGS: allowedCustomElements,\n\t\tADD_ATTR: BASE_ALLOWED_ATTRS,\n\t\tADD_URI_SAFE_ATTR: BASE_URI_SAFE_ATTRS,\n\t\tFORBID_TAGS: FORBIDDEN_TAGS,\n\t\tFORBID_ATTR: FORBIDDEN_ATTRS,\n\t\tALLOW_UNKNOWN_PROTOCOLS: false,\n\t\tSANITIZE_DOM: true,\n\t\t// pie-item contract compatibility: PIE models are matched to DOM\n\t\t// elements via strict `id` equality (see `updateSinglePieElement`\n\t\t// in players-shared/src/pie/updates.ts). `SANITIZE_NAMED_PROPS`\n\t\t// would prefix every `id`/`name` with `user-content-`, which silently\n\t\t// breaks model lookup for every item. `SANITIZE_DOM: true` above\n\t\t// still provides the core DOM-clobbering defenses we rely on.\n\t\tSANITIZE_NAMED_PROPS: false,\n\t\tWHOLE_DOCUMENT: false,\n\t\tALLOW_DATA_ATTR: true,\n\t\tALLOW_ARIA_ATTR: true,\n\t\tCUSTOM_ELEMENT_HANDLING: {\n\t\t\ttagNameCheck: (tagName: string) => {\n\t\t\t\tconst lower = tagName.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tPIE_CUSTOM_ELEMENT_REGEX.test(lower) ||\n\t\t\t\t\texplicitCustomElementSet.has(lower)\n\t\t\t\t);\n\t\t\t},\n\t\t\tattributeNameCheck: (attrName: string) =>\n\t\t\t\tCUSTOM_ELEMENT_ATTR_REGEX.test(attrName),\n\t\t\tallowCustomizedBuiltInElements: false,\n\t\t},\n\t\tRETURN_TRUSTED_TYPE: false,\n\t});\n\n\tconst sanitized = typeof result === \"string\" ? result : String(result ?? \"\");\n\t// PIE-94: wrap overwide authored images in a horizontal-scroll container\n\t// so they don't get clipped by ancestor `overflow-x: hidden` regions in\n\t// the section player (and match WCAG 1.4.10 Reflow at 400% zoom).\n\t// Tables get the same treatment so wide data grids reflow into a\n\t// scrollable region instead of forcing the page itself to scroll.\n\treturn wrapOverwideTables(wrapOverwideImages(sanitized));\n}\n\n/**\n * Build the default `ItemMarkupSanitizer` used by the players. The returned\n * function is stable for a given set of allowed custom elements so callers\n * can safely use reference equality when deciding whether to re-sanitize.\n */\nexport function createDefaultItemMarkupSanitizer(\n\toptions: SanitizeItemMarkupOptions = {},\n): ItemMarkupSanitizer {\n\tconst { allowedCustomElements } = options;\n\treturn (markup: string) =>\n\t\tsanitizeItemMarkup(markup, { allowedCustomElements });\n}\n\n/**\n * Derive the authoring-mode allow-list (`pie-*-config`) from a set of PIE\n * element tag names. Used by `transformMarkupForAuthoring` so the sanitizer\n * keeps the rewritten `-config` tags instead of stripping them.\n */\nexport function buildAuthoringAllowList(\n\telementTagNames: Iterable<string>,\n): string[] {\n\tconst out = new Set<string>();\n\tfor (const tag of elementTagNames) {\n\t\tif (!tag) continue;\n\t\tconst lower = tag.toLowerCase();\n\t\tout.add(lower);\n\t\tout.add(`${lower}-config`);\n\t}\n\treturn [...out];\n}\n\n/** Reset the memoised DOMPurify instance. Only intended for tests. */\nexport function resetPurifierForTesting() {\n\tpurifierInstance = null;\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function wrapModelRichContent<T>(model: T): T;
@@ -0,0 +1,41 @@
1
+ import { wrapOverwideImages } from "./wrap-overwide-images.js";
2
+ import { wrapOverwideTables } from "./wrap-overwide-tables.js";
3
+ const RICH_CONTENT_TAG_REGEX = /<(?:img|table)\b/i;
4
+ const SKIPPED_KEYS = new Set(["accessibilityCatalogs"]);
5
+ function maybeWrapRichContent(value) {
6
+ if (!RICH_CONTENT_TAG_REGEX.test(value))
7
+ return value;
8
+ return wrapOverwideTables(wrapOverwideImages(value));
9
+ }
10
+ function wrapValue(value, parentKey) {
11
+ if (parentKey && SKIPPED_KEYS.has(parentKey))
12
+ return value;
13
+ if (typeof value === "string")
14
+ return maybeWrapRichContent(value);
15
+ if (!value || typeof value !== "object")
16
+ return value;
17
+ if (Array.isArray(value)) {
18
+ let changed = false;
19
+ const next = value.map((entry) => {
20
+ const wrapped = wrapValue(entry);
21
+ if (wrapped !== entry)
22
+ changed = true;
23
+ return wrapped;
24
+ });
25
+ return changed ? next : value;
26
+ }
27
+ const record = value;
28
+ let changed = false;
29
+ const next = {};
30
+ for (const [key, entry] of Object.entries(record)) {
31
+ const wrapped = wrapValue(entry, key);
32
+ if (wrapped !== entry)
33
+ changed = true;
34
+ next[key] = wrapped;
35
+ }
36
+ return changed ? next : value;
37
+ }
38
+ export function wrapModelRichContent(model) {
39
+ return wrapValue(model);
40
+ }
41
+ //# sourceMappingURL=wrap-model-rich-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap-model-rich-content.js","sourceRoot":"","sources":["../../src/security/wrap-model-rich-content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AACnD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC;AAExD,SAAS,oBAAoB,CAAC,KAAa;IAC1C,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,kBAAkB,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,SAAkB;IACpD,IAAI,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAClE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEtD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAChC,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,IAAI,OAAO,KAAK,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAC;YACtC,OAAO,OAAO,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,KAAK;YAAE,OAAO,GAAG,IAAI,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,KAAQ;IAC/C,OAAO,SAAS,CAAC,KAAK,CAAM,CAAC;AAC9B,CAAC","sourcesContent":["import { wrapOverwideImages } from \"./wrap-overwide-images.js\";\nimport { wrapOverwideTables } from \"./wrap-overwide-tables.js\";\n\nconst RICH_CONTENT_TAG_REGEX = /<(?:img|table)\\b/i;\nconst SKIPPED_KEYS = new Set([\"accessibilityCatalogs\"]);\n\nfunction maybeWrapRichContent(value: string): string {\n\tif (!RICH_CONTENT_TAG_REGEX.test(value)) return value;\n\treturn wrapOverwideTables(wrapOverwideImages(value));\n}\n\nfunction wrapValue(value: unknown, parentKey?: string): unknown {\n\tif (parentKey && SKIPPED_KEYS.has(parentKey)) return value;\n\tif (typeof value === \"string\") return maybeWrapRichContent(value);\n\tif (!value || typeof value !== \"object\") return value;\n\n\tif (Array.isArray(value)) {\n\t\tlet changed = false;\n\t\tconst next = value.map((entry) => {\n\t\t\tconst wrapped = wrapValue(entry);\n\t\t\tif (wrapped !== entry) changed = true;\n\t\t\treturn wrapped;\n\t\t});\n\t\treturn changed ? next : value;\n\t}\n\n\tconst record = value as Record<string, unknown>;\n\tlet changed = false;\n\tconst next: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(record)) {\n\t\tconst wrapped = wrapValue(entry, key);\n\t\tif (wrapped !== entry) changed = true;\n\t\tnext[key] = wrapped;\n\t}\n\treturn changed ? next : value;\n}\n\nexport function wrapModelRichContent<T>(model: T): T {\n\treturn wrapValue(model) as T;\n}\n"]}
@@ -71,8 +71,7 @@ export function wrapOverwideImages(markup) {
71
71
  if (!parent)
72
72
  continue;
73
73
  // Idempotency — already wrapped.
74
- if (parent.classList &&
75
- parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
74
+ if (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
76
75
  continue;
77
76
  }
78
77
  // Leave PIE custom-element internals alone.
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-overwide-images.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-images.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<img>` elements with a horizontally scrollable container so\n * images that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions (PIE-94).\n *\n * The wrapper is rendered as\n * `<span class=\"pie-image-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so small images stay visually unchanged: a scrollbar only\n * appears when the image's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels, which is the original driver\n * for this change — WCAG 1.4.10 Reflow at 400% zoom).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-image-scroll\";\n\nfunction isInsidePieCustomElement(\n\timage: Element,\n\troot: Element,\n): boolean {\n\tlet ancestor: Element | null = image.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(image: Element): string {\n\tconst alt = image.getAttribute(\"alt\");\n\tconst trimmed = alt ? alt.trim() : \"\";\n\treturn trimmed ? `Scrollable image: ${trimmed}` : \"Scrollable image\";\n}\n\n/**\n * Wrap `<img>` elements in `markup` with a horizontal-scroll container.\n *\n * - No-ops on empty input.\n * - No-ops during SSR (no `window` / `DOMParser`) — the markup is returned\n * unchanged; the browser re-run on hydrate will perform the wrap.\n * - Idempotent: images whose direct parent already carries the\n * `pie-image-scroll` class are left alone.\n * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are\n * rendered by the element's own template / shadow DOM and should not be\n * restructured by the authored-markup pipeline.\n */\nexport function wrapOverwideImages(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// images. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<img\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst images = Array.from(body.querySelectorAll(\"img\"));\n\tif (images.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const image of images) {\n\t\tconst parent = image.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (\n\t\t\tparent.classList &&\n\t\t\tparent.classList.contains(SCROLL_WRAPPER_CLASS)\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(image, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"span\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(image));\n\n\t\tparent.insertBefore(wrapper, image);\n\t\twrapper.appendChild(image);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
1
+ {"version":3,"file":"wrap-overwide-images.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-images.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<img>` elements with a horizontally scrollable container so\n * images that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions (PIE-94).\n *\n * The wrapper is rendered as\n * `<span class=\"pie-image-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so small images stay visually unchanged: a scrollbar only\n * appears when the image's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels, which is the original driver\n * for this change — WCAG 1.4.10 Reflow at 400% zoom).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-image-scroll\";\n\nfunction isInsidePieCustomElement(image: Element, root: Element): boolean {\n\tlet ancestor: Element | null = image.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(image: Element): string {\n\tconst alt = image.getAttribute(\"alt\");\n\tconst trimmed = alt ? alt.trim() : \"\";\n\treturn trimmed ? `Scrollable image: ${trimmed}` : \"Scrollable image\";\n}\n\n/**\n * Wrap `<img>` elements in `markup` with a horizontal-scroll container.\n *\n * - No-ops on empty input.\n * - No-ops during SSR (no `window` / `DOMParser`) — the markup is returned\n * unchanged; the browser re-run on hydrate will perform the wrap.\n * - Idempotent: images whose direct parent already carries the\n * `pie-image-scroll` class are left alone.\n * - Leaves images inside PIE custom elements (`<pie-*>`) alone. Those are\n * rendered by the element's own template / shadow DOM and should not be\n * restructured by the authored-markup pipeline.\n */\nexport function wrapOverwideImages(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// images. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<img\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst images = Array.from(body.querySelectorAll(\"img\"));\n\tif (images.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const image of images) {\n\t\tconst parent = image.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(image, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"span\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(image));\n\n\t\tparent.insertBefore(wrapper, image);\n\t\twrapper.appendChild(image);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
@@ -84,8 +84,7 @@ export function wrapOverwideTables(markup) {
84
84
  if (!parent)
85
85
  continue;
86
86
  // Idempotency — already wrapped.
87
- if (parent.classList &&
88
- parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
87
+ if (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {
89
88
  continue;
90
89
  }
91
90
  // Leave PIE custom-element internals alone.
@@ -1 +1 @@
1
- {"version":3,"file":"wrap-overwide-tables.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-tables.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<table>` elements with a horizontally scrollable container so\n * tables that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions.\n *\n * The wrapper is rendered as\n * `<div class=\"pie-table-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so narrow tables stay visually unchanged: a scrollbar only\n * appears when the table's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels — WCAG 1.4.10 Reflow at 400%\n * zoom is the same driver as for `wrapOverwideImages`).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-table-scroll\";\n\nfunction isInsidePieCustomElement(table: Element, root: Element): boolean {\n\tlet ancestor: Element | null = table.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(table: Element): string {\n\t// Authors commonly label tables via <caption>, aria-label, or aria-labelledby.\n\t// Prefer the most explicit signal and fall back to the generic label so\n\t// every wrapper still announces itself as a region.\n\tconst ariaLabel = table.getAttribute(\"aria-label\");\n\tif (ariaLabel?.trim()) {\n\t\treturn `Scrollable table: ${ariaLabel.trim()}`;\n\t}\n\tconst labelledBy = table.getAttribute(\"aria-labelledby\");\n\tif (labelledBy?.trim()) {\n\t\tconst ownerDocument = table.ownerDocument;\n\t\tconst ids = labelledBy.trim().split(/\\s+/);\n\t\tconst labels: string[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst labelEl = ownerDocument?.getElementById(id);\n\t\t\tconst text = labelEl?.textContent?.trim();\n\t\t\tif (text) labels.push(text);\n\t\t}\n\t\tif (labels.length > 0) {\n\t\t\treturn `Scrollable table: ${labels.join(\" \")}`;\n\t\t}\n\t}\n\tconst caption = table.querySelector(\"caption\");\n\tconst captionText = caption?.textContent?.trim();\n\tif (captionText) {\n\t\treturn `Scrollable table: ${captionText}`;\n\t}\n\treturn \"Scrollable table\";\n}\n\nexport function wrapOverwideTables(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// tables. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<table\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst tables = Array.from(body.querySelectorAll(\"table\"));\n\tif (tables.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const table of tables) {\n\t\tconst parent = table.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (\n\t\t\tparent.classList &&\n\t\t\tparent.classList.contains(SCROLL_WRAPPER_CLASS)\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(table, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"div\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(table));\n\n\t\tparent.insertBefore(wrapper, table);\n\t\twrapper.appendChild(table);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
1
+ {"version":3,"file":"wrap-overwide-tables.js","sourceRoot":"","sources":["../../src/security/wrap-overwide-tables.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wraps authored `<table>` elements with a horizontally scrollable container so\n * tables that are wider than their column surface a scrollbar instead of being\n * clipped by ancestor `overflow-x: hidden` regions.\n *\n * The wrapper is rendered as\n * `<div class=\"pie-table-scroll\" tabindex=\"0\" role=\"region\" aria-label=\"...\">`\n * and receives the accompanying CSS from `@pie-players/pie-theme`. The CSS uses\n * `overflow-x: auto` so narrow tables stay visually unchanged: a scrollbar only\n * appears when the table's intrinsic width exceeds the wrapper's available\n * space (including at higher browser-zoom levels — WCAG 1.4.10 Reflow at 400%\n * zoom is the same driver as for `wrapOverwideImages`).\n *\n * This helper runs as a post-sanitization step inside `sanitizeItemMarkup`, so\n * every host that renders authored markup through the shared\n * `pie-item-player` (including the section player) benefits uniformly.\n */\n\nconst PIE_CUSTOM_ELEMENT_TAG_REGEX = /^pie-/i;\nconst SCROLL_WRAPPER_CLASS = \"pie-table-scroll\";\n\nfunction isInsidePieCustomElement(table: Element, root: Element): boolean {\n\tlet ancestor: Element | null = table.parentElement;\n\twhile (ancestor && ancestor !== root) {\n\t\tif (PIE_CUSTOM_ELEMENT_TAG_REGEX.test(ancestor.tagName)) {\n\t\t\treturn true;\n\t\t}\n\t\tancestor = ancestor.parentElement;\n\t}\n\treturn false;\n}\n\nfunction buildAriaLabel(table: Element): string {\n\t// Authors commonly label tables via <caption>, aria-label, or aria-labelledby.\n\t// Prefer the most explicit signal and fall back to the generic label so\n\t// every wrapper still announces itself as a region.\n\tconst ariaLabel = table.getAttribute(\"aria-label\");\n\tif (ariaLabel?.trim()) {\n\t\treturn `Scrollable table: ${ariaLabel.trim()}`;\n\t}\n\tconst labelledBy = table.getAttribute(\"aria-labelledby\");\n\tif (labelledBy?.trim()) {\n\t\tconst ownerDocument = table.ownerDocument;\n\t\tconst ids = labelledBy.trim().split(/\\s+/);\n\t\tconst labels: string[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst labelEl = ownerDocument?.getElementById(id);\n\t\t\tconst text = labelEl?.textContent?.trim();\n\t\t\tif (text) labels.push(text);\n\t\t}\n\t\tif (labels.length > 0) {\n\t\t\treturn `Scrollable table: ${labels.join(\" \")}`;\n\t\t}\n\t}\n\tconst caption = table.querySelector(\"caption\");\n\tconst captionText = caption?.textContent?.trim();\n\tif (captionText) {\n\t\treturn `Scrollable table: ${captionText}`;\n\t}\n\treturn \"Scrollable table\";\n}\n\nexport function wrapOverwideTables(markup: string): string {\n\tif (!markup) return \"\";\n\n\t// Fast path: avoid the DOM round-trip entirely when the markup carries no\n\t// tables. Keeps the sanitize pipeline cheap for the common case.\n\tif (!/<table\\b/i.test(markup)) return markup;\n\n\tif (typeof window === \"undefined\" || !window.document) return markup;\n\n\tconst ParserCtor =\n\t\ttypeof DOMParser !== \"undefined\"\n\t\t\t? DOMParser\n\t\t\t: (window as unknown as { DOMParser?: typeof DOMParser }).DOMParser;\n\tif (!ParserCtor) return markup;\n\n\tconst doc = new ParserCtor().parseFromString(\n\t\t`<!DOCTYPE html><html><body>${markup}</body></html>`,\n\t\t\"text/html\",\n\t);\n\tconst body = doc.body;\n\tif (!body) return markup;\n\n\tconst tables = Array.from(body.querySelectorAll(\"table\"));\n\tif (tables.length === 0) return markup;\n\n\tlet mutated = false;\n\tfor (const table of tables) {\n\t\tconst parent = table.parentElement;\n\t\tif (!parent) continue;\n\n\t\t// Idempotency — already wrapped.\n\t\tif (parent.classList && parent.classList.contains(SCROLL_WRAPPER_CLASS)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Leave PIE custom-element internals alone.\n\t\tif (isInsidePieCustomElement(table, body)) continue;\n\n\t\tconst wrapper = doc.createElement(\"div\");\n\t\twrapper.className = SCROLL_WRAPPER_CLASS;\n\t\twrapper.setAttribute(\"tabindex\", \"0\");\n\t\twrapper.setAttribute(\"role\", \"region\");\n\t\twrapper.setAttribute(\"aria-label\", buildAriaLabel(table));\n\n\t\tparent.insertBefore(wrapper, table);\n\t\twrapper.appendChild(table);\n\t\tmutated = true;\n\t}\n\n\treturn mutated ? body.innerHTML : markup;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"npm-registry.js","sourceRoot":"","sources":["../../src/server/npm-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import semver from \"semver\";\n\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\n\nif (typeof window !== \"undefined\") {\n\tthrow new Error(\n\t\t\"[npm-registry] @pie-players/pie-players-shared/server/npm-registry is server-only and cannot be imported in browser code\",\n\t);\n}\n\nconst DEFAULT_FILTER = (v: string) =>\n\tv.indexOf(\"print\") === -1 &&\n\tv.indexOf(\"ps\") === -1 &&\n\tv.indexOf(\"strikethrough\") === -1 &&\n\tv.indexOf(\"alpha\") === -1 &&\n\tv.indexOf(\"bm\") === -1;\n\ntype NpmFetch = (\n\tinput: RequestInfo | URL,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Server-only helper for npm package version lookups.\n * Do not import this module from browser-executed code.\n */\nexport const getNpmPackageVersions = async (\n\telement: string,\n\tfetch: NpmFetch,\n\tfilter: (v: string) => boolean = DEFAULT_FILTER,\n\tlimit?: number,\n\tsearchTerm = \"\",\n): Promise<string[]> => {\n\tif (typeof window !== \"undefined\") {\n\t\tthrow new Error(\n\t\t\t\"[npm-registry] server-only module imported in browser context\",\n\t\t);\n\t}\n\n\ttry {\n\t\tconst response = await fetch(`${NPM_REGISTRY_URL}/${element}`);\n\t\tif (!response.ok) {\n\t\t\tconsole.warn(\n\t\t\t\t`[npm-registry] npm status ${response.status} for package ${element}`,\n\t\t\t);\n\t\t\treturn searchTerm ? [] : [\"latest\"];\n\t\t}\n\n\t\tconst json = await response.json();\n\t\tconst versions = json?.versions as Record<\n\t\t\tstring,\n\t\t\t{ deprecated?: string }\n\t\t> | null;\n\t\tif (!versions || typeof versions !== \"object\") {\n\t\t\tconsole.warn(\n\t\t\t\t`[npm-registry] response for ${element} missing 'versions' property`,\n\t\t\t);\n\t\t\treturn searchTerm ? [] : [\"latest\"];\n\t\t}\n\n\t\tlet sorted = Object.keys(versions)\n\t\t\t.filter(filter)\n\t\t\t.filter((v) => !versions[v]?.deprecated)\n\t\t\t.filter((v) => !/^11\\.0\\.\\d+-esm\\.\\d+/.test(v))\n\t\t\t.filter(\n\t\t\t\t(v) =>\n\t\t\t\t\t!searchTerm || v.toLowerCase().includes(searchTerm.toLowerCase()),\n\t\t\t)\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst verA = semver.valid(semver.clean(a));\n\t\t\t\tconst verB = semver.valid(semver.clean(b));\n\t\t\t\tif (!verA && !verB) return 0;\n\t\t\t\tif (!verA) return 1;\n\t\t\t\tif (!verB) return -1;\n\t\t\t\treturn semver.rcompare(verA, verB);\n\t\t\t});\n\n\t\tif (typeof limit === \"number\" && limit > 0) {\n\t\t\tsorted = sorted.slice(0, limit);\n\t\t}\n\n\t\tif (!searchTerm) {\n\t\t\tsorted.unshift(\"latest\");\n\t\t}\n\n\t\treturn sorted;\n\t} catch (error) {\n\t\tconsole.warn(\n\t\t\t`[npm-registry] failed package lookup for ${element}:`,\n\t\t\terror,\n\t\t);\n\t\treturn searchTerm ? [] : [\"latest\"];\n\t}\n};\n"]}
1
+ {"version":3,"file":"npm-registry.js","sourceRoot":"","sources":["../../src/server/npm-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import semver from \"semver\";\n\nconst NPM_REGISTRY_URL = \"https://registry.npmjs.org\";\n\nif (typeof window !== \"undefined\") {\n\tthrow new Error(\n\t\t\"[npm-registry] @pie-players/pie-players-shared/server/npm-registry is server-only and cannot be imported in browser code\",\n\t);\n}\n\nconst DEFAULT_FILTER = (v: string) =>\n\tv.indexOf(\"print\") === -1 &&\n\tv.indexOf(\"ps\") === -1 &&\n\tv.indexOf(\"strikethrough\") === -1 &&\n\tv.indexOf(\"alpha\") === -1 &&\n\tv.indexOf(\"bm\") === -1;\n\ntype NpmFetch = (\n\tinput: RequestInfo | URL,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Server-only helper for npm package version lookups.\n * Do not import this module from browser-executed code.\n */\nexport const getNpmPackageVersions = async (\n\telement: string,\n\tfetch: NpmFetch,\n\tfilter: (v: string) => boolean = DEFAULT_FILTER,\n\tlimit?: number,\n\tsearchTerm = \"\",\n): Promise<string[]> => {\n\tif (typeof window !== \"undefined\") {\n\t\tthrow new Error(\n\t\t\t\"[npm-registry] server-only module imported in browser context\",\n\t\t);\n\t}\n\n\ttry {\n\t\tconst response = await fetch(`${NPM_REGISTRY_URL}/${element}`);\n\t\tif (!response.ok) {\n\t\t\tconsole.warn(\n\t\t\t\t`[npm-registry] npm status ${response.status} for package ${element}`,\n\t\t\t);\n\t\t\treturn searchTerm ? [] : [\"latest\"];\n\t\t}\n\n\t\tconst json = await response.json();\n\t\tconst versions = json?.versions as Record<\n\t\t\tstring,\n\t\t\t{ deprecated?: string }\n\t\t> | null;\n\t\tif (!versions || typeof versions !== \"object\") {\n\t\t\tconsole.warn(\n\t\t\t\t`[npm-registry] response for ${element} missing 'versions' property`,\n\t\t\t);\n\t\t\treturn searchTerm ? [] : [\"latest\"];\n\t\t}\n\n\t\tlet sorted = Object.keys(versions)\n\t\t\t.filter(filter)\n\t\t\t.filter((v) => !versions[v]?.deprecated)\n\t\t\t.filter((v) => !/^11\\.0\\.\\d+-esm\\.\\d+/.test(v))\n\t\t\t.filter(\n\t\t\t\t(v) =>\n\t\t\t\t\t!searchTerm || v.toLowerCase().includes(searchTerm.toLowerCase()),\n\t\t\t)\n\t\t\t.sort((a, b) => {\n\t\t\t\tconst verA = semver.valid(semver.clean(a));\n\t\t\t\tconst verB = semver.valid(semver.clean(b));\n\t\t\t\tif (!verA && !verB) return 0;\n\t\t\t\tif (!verA) return 1;\n\t\t\t\tif (!verB) return -1;\n\t\t\t\treturn semver.rcompare(verA, verB);\n\t\t\t});\n\n\t\tif (typeof limit === \"number\" && limit > 0) {\n\t\t\tsorted = sorted.slice(0, limit);\n\t\t}\n\n\t\tif (!searchTerm) {\n\t\t\tsorted.unshift(\"latest\");\n\t\t}\n\n\t\treturn sorted;\n\t} catch (error) {\n\t\tconsole.warn(`[npm-registry] failed package lookup for ${element}:`, error);\n\t\treturn searchTerm ? [] : [\"latest\"];\n\t}\n};\n"]}
@@ -481,10 +481,14 @@ export type BundleInfo = {
481
481
  url: string;
482
482
  hash: string;
483
483
  };
484
- interface PieDefaultModel {
484
+ export type ItemSession = {
485
+ id: string;
486
+ data: any[];
487
+ };
488
+ export interface PieDefaultModel {
485
489
  [x: string]: any;
486
490
  }
487
- interface PieContent {
491
+ export interface PieContent {
488
492
  id: string;
489
493
  /**
490
494
  * Set of elements to include in the pie, provided in the format `{'element-name': 'mpm-package-name'}`
@@ -494,11 +498,19 @@ interface PieContent {
494
498
  models: PieModel[];
495
499
  markup: string;
496
500
  bundle?: BundleInfo;
501
+ resources?: ConfigResource;
497
502
  defaultExtraModels?: {
498
503
  [key: string]: PieDefaultModel;
499
504
  };
500
505
  }
501
- interface AdvancedItemConfig {
506
+ export interface ConfigResource {
507
+ stylesheets?: {
508
+ url: string;
509
+ }[];
510
+ containerClass?: string;
511
+ passageContainerClass?: string;
512
+ }
513
+ export interface AdvancedItemConfig {
502
514
  id: string;
503
515
  pie: PieContent;
504
516
  passage?: PieContent;