@pie-players/pie-section-player 0.3.71 → 0.3.73

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 CHANGED
@@ -908,6 +908,36 @@ await controller?.updateItemSession?.("q1", {
908
908
  The same controller snapshot is what the persistence strategy saves/loads.
909
909
  When a controller is reused for the same `sectionId`/`attemptId`, `updateInput()` refreshes composition input while preserving in-memory section session data.
910
910
 
911
+ ### Commit at a section boundary
912
+
913
+ A delivery element coalesces its `session-changed` dispatch, so a response the
914
+ learner has finished entering can still be pending when the section moves. The
915
+ controller commits pending element sessions before item navigation, before
916
+ `updateInput()` snapshots the session for a section swap, and before `persist()`.
917
+ The player also commits when an item shell tears down and when the page goes
918
+ hidden.
919
+
920
+ The commit reaches the controller, and the controller's own
921
+ `PIE_ITEM_SESSION_CHANGED_EVENT` is what reaches a host. A raw `session-changed`
922
+ does not leave a section: `<pie-item-shell>` stops it and re-dispatches the
923
+ normalized event, so a host listening on `document` for `session-changed`
924
+ receives nothing from inside a section player, before this change or after it.
925
+ Host code persists from the controller's events or from its session snapshot.
926
+
927
+ Navigation inside a section keeps every item mounted, so nothing is discarded
928
+ and the element's own debounce would complete on its own. The commit still runs
929
+ there because the response belongs to the item being left: a host that persists
930
+ on the navigation event, or a `persist()` that follows it, would otherwise
931
+ snapshot a session the learner had already changed.
932
+
933
+ `SectionController` stays DOM-free; the player supplies the commit through
934
+ `setPendingSessionCommit()`, declared on `SectionControllerHandle`. The player
935
+ registers it both on the controllers it creates and on one the toolkit has
936
+ already built, since the first section's controller usually exists before the
937
+ player can override the factory. A host-built controller that leaves the method
938
+ unimplemented keeps the behaviour it had before the hook existed, and loses a
939
+ pending response at those boundaries.
940
+
911
941
  ## Content trust boundary
912
942
 
913
943
  Section-player layouts embed `<pie-item-player>` elements for each item.
@@ -28,134 +28,141 @@ var i = {
28
28
  path: "/api/authoring/release"
29
29
  }
30
30
  };
31
- function g(e) {
32
- return e ? e.endsWith("/") ? e.slice(0, -1) : e : "";
31
+ function S(n) {
32
+ return n ? n.endsWith("/") ? n.slice(0, -1) : n : "";
33
33
  }
34
- function r(e, o) {
35
- return e ? typeof e == "string" ? {
34
+ function r(n, o) {
35
+ return n ? typeof n == "string" ? {
36
36
  method: o.method,
37
- path: e
37
+ path: n
38
38
  } : {
39
- method: e.method ?? o.method,
40
- path: e.path
39
+ method: n.method ?? o.method,
40
+ path: n.path
41
41
  } : o;
42
42
  }
43
- function d(e, o) {
44
- return /^https?:\/\//i.test(o) ? o : `${g(e)}${o.startsWith("/") ? o : `/${o}`}`;
43
+ function d(n, o) {
44
+ if (/^https?:\/\//i.test(o)) return o;
45
+ const e = S(n), t = o.startsWith("/") ? o : `/${o}`;
46
+ return e.endsWith("/api") && t.startsWith("/api/") ? `${e}${t.slice(4)}` : `${e}${t}`;
45
47
  }
46
- async function l(e, o) {
47
- const t = e ?? o;
48
- if (!t) return null;
49
- if (typeof t.getToken == "function") {
50
- const n = await t.getToken();
51
- return n ? String(n) : null;
48
+ async function l(n, o) {
49
+ const e = n ?? o;
50
+ if (!e) return null;
51
+ if (typeof e.getToken == "function") {
52
+ const t = await e.getToken();
53
+ return t ? String(t) : null;
52
54
  }
53
- return t.token ? String(t.token) : null;
55
+ return e.token ? String(e.token) : null;
54
56
  }
55
- async function c(e, o, t, n, a) {
56
- const s = typeof AbortController < "u" ? new AbortController() : null, p = typeof n?.timeoutMs == "number" && n.timeoutMs > 0 ? n.timeoutMs : 0, m = s && p > 0 ? setTimeout(() => s.abort(), p) : null;
57
+ var w = 65536;
58
+ function P(n) {
59
+ return (typeof TextEncoder < "u" ? new TextEncoder().encode(n).length : n.length) > w;
60
+ }
61
+ async function c(n, o, e, t, s, a) {
62
+ const p = JSON.stringify(e ?? {}), g = a?.keepalive === !0 && !P(p), h = typeof AbortController < "u" ? new AbortController() : null, I = !g && typeof t?.timeoutMs == "number" && t.timeoutMs > 0 ? t.timeoutMs : 0, m = h && I > 0 ? setTimeout(() => h.abort(), I) : null;
57
63
  try {
58
- const v = {
64
+ const f = {
59
65
  "content-type": "application/json",
60
- ...n?.headers || {}
66
+ ...t?.headers || {}
61
67
  };
62
- a && (v.authorization = `Bearer ${a}`);
63
- const h = await fetch(e, {
68
+ s && (f.authorization = `Bearer ${s}`);
69
+ const v = await fetch(n, {
64
70
  method: o,
65
- headers: v,
66
- body: JSON.stringify(t ?? {}),
67
- signal: s?.signal
68
- }), u = await h.json().catch(() => null);
69
- if (!h.ok) {
70
- const I = (u && typeof u == "object" && "error" in u ? String(u.error) : "") || `Backend request failed with status ${h.status}`;
71
- throw new Error(I);
71
+ headers: f,
72
+ body: p,
73
+ signal: m ? h?.signal : void 0,
74
+ keepalive: g
75
+ }), u = await v.json().catch(() => null);
76
+ if (!v.ok) {
77
+ const y = (u && typeof u == "object" && "error" in u ? String(u.error) : "") || `Backend request failed with status ${v.status}`;
78
+ throw new Error(y);
72
79
  }
73
80
  return u;
74
81
  } finally {
75
82
  m && clearTimeout(m);
76
83
  }
77
84
  }
78
- async function y(e, o, t) {
79
- const n = r(e.endpoints?.load, i.load), a = await l(e.auth, o);
80
- return c(d(e.baseUrl, n.path), n.method, {
81
- itemId: t.itemId,
82
- sessionId: t.sessionId,
83
- assignmentId: t.assignmentId,
84
- env: t.env,
85
- overrides: t.requestOptions?.overrides
86
- }, e.request, a);
85
+ async function b(n, o, e) {
86
+ const t = r(n.endpoints?.load, i.load), s = await l(n.auth, o);
87
+ return c(d(n.baseUrl, t.path), t.method, {
88
+ itemId: e.itemId,
89
+ sessionId: e.sessionId,
90
+ assignmentId: e.assignmentId,
91
+ env: e.env,
92
+ overrides: e.requestOptions?.overrides
93
+ }, n.request, s);
87
94
  }
88
- async function f(e, o, t) {
89
- const n = r(e.endpoints?.saveSession, i.saveSession), a = await l(e.auth, o), s = t.session.id || t.sessionId;
90
- return c(d(e.baseUrl, n.path), n.method, {
91
- sessionId: s,
92
- data: t.session.data,
93
- env: t.env,
94
- itemId: t.itemId,
95
- assignmentId: t.assignmentId,
96
- models: t.models,
97
- passageModels: t.passageModels,
98
- overrides: t.requestOptions?.overrides
99
- }, e.request, a);
95
+ async function T(n, o, e, t) {
96
+ const s = r(n.endpoints?.saveSession, i.saveSession), a = await l(n.auth, o), p = e.session.id || e.sessionId;
97
+ return c(d(n.baseUrl, s.path), s.method, {
98
+ sessionId: p,
99
+ data: e.session.data,
100
+ env: e.env,
101
+ itemId: e.itemId,
102
+ assignmentId: e.assignmentId,
103
+ models: e.models,
104
+ passageModels: e.passageModels,
105
+ overrides: e.requestOptions?.overrides
106
+ }, n.request, a, { keepalive: t?.keepalive === !0 });
100
107
  }
101
- async function S(e, o, t) {
102
- const n = r(e.endpoints?.model, i.model), a = await l(e.auth, o), s = t.session.id || t.sessionId;
103
- return c(d(e.baseUrl, n.path), n.method, {
104
- sessionId: s,
105
- data: t.session.data,
106
- env: t.env,
107
- itemId: t.itemId,
108
- assignmentId: t.assignmentId,
109
- models: t.models,
110
- passageModels: t.passageModels,
111
- overrides: t.requestOptions?.overrides
112
- }, e.request, a);
108
+ async function A(n, o, e) {
109
+ const t = r(n.endpoints?.model, i.model), s = await l(n.auth, o), a = e.session.id || e.sessionId;
110
+ return c(d(n.baseUrl, t.path), t.method, {
111
+ sessionId: a,
112
+ data: e.session.data,
113
+ env: e.env,
114
+ itemId: e.itemId,
115
+ assignmentId: e.assignmentId,
116
+ models: e.models,
117
+ passageModels: e.passageModels,
118
+ overrides: e.requestOptions?.overrides
119
+ }, n.request, s);
113
120
  }
114
- async function w(e, o, t) {
115
- const n = r(e.endpoints?.score, i.score), a = await l(e.auth, o), s = t.session.id || t.sessionId;
116
- return c(d(e.baseUrl, n.path), n.method, {
117
- ...t.options,
118
- sessionId: s,
119
- data: t.session.data,
120
- env: t.env,
121
- itemId: t.itemId,
122
- assignmentId: t.assignmentId,
123
- overrides: t.requestOptions?.overrides
124
- }, e.request, a);
121
+ async function O(n, o, e) {
122
+ const t = r(n.endpoints?.score, i.score), s = await l(n.auth, o), a = e.session.id || e.sessionId;
123
+ return c(d(n.baseUrl, t.path), t.method, {
124
+ ...e.options,
125
+ sessionId: a,
126
+ data: e.session.data,
127
+ env: e.env,
128
+ itemId: e.itemId,
129
+ assignmentId: e.assignmentId,
130
+ overrides: e.requestOptions?.overrides
131
+ }, n.request, s);
125
132
  }
126
- async function P(e, o, t) {
127
- const n = r(e.endpoints?.load, i.authoringLoad), a = await l(e.auth, o);
128
- return c(d(e.baseUrl, n.path), n.method, {
129
- contentId: t.contentId,
130
- collectionId: t.collectionId,
131
- env: t.env
132
- }, e.request, a);
133
+ async function q(n, o, e) {
134
+ const t = r(n.endpoints?.load, i.authoringLoad), s = await l(n.auth, o);
135
+ return c(d(n.baseUrl, t.path), t.method, {
136
+ contentId: e.contentId,
137
+ collectionId: e.collectionId,
138
+ env: e.env
139
+ }, n.request, s);
133
140
  }
134
- async function b(e, o, t) {
135
- const n = r(e.endpoints?.saveContent, i.authoringSaveContent), a = await l(e.auth, o);
136
- return c(d(e.baseUrl, n.path), n.method, {
137
- contentId: t.contentId,
138
- collectionId: t.collectionId,
139
- config: t.config,
140
- env: t.env,
141
- options: t.options
142
- }, e.request, a);
141
+ async function k(n, o, e) {
142
+ const t = r(n.endpoints?.saveContent, i.authoringSaveContent), s = await l(n.auth, o);
143
+ return c(d(n.baseUrl, t.path), t.method, {
144
+ contentId: e.contentId,
145
+ collectionId: e.collectionId,
146
+ config: e.config,
147
+ env: e.env,
148
+ options: e.options
149
+ }, n.request, s);
143
150
  }
144
- async function A(e, o, t) {
145
- const n = r(e.endpoints?.releaseContent, i.authoringReleaseContent), a = await l(e.auth, o);
146
- return c(d(e.baseUrl, n.path), n.method, {
147
- contentId: t.contentId,
148
- collectionId: t.collectionId,
149
- env: t.env,
150
- options: t.options
151
- }, e.request, a);
151
+ async function C(n, o, e) {
152
+ const t = r(n.endpoints?.releaseContent, i.authoringReleaseContent), s = await l(n.auth, o);
153
+ return c(d(n.baseUrl, t.path), t.method, {
154
+ contentId: e.contentId,
155
+ collectionId: e.collectionId,
156
+ env: e.env,
157
+ options: e.options
158
+ }, n.request, s);
152
159
  }
153
160
  export {
154
- P as callPieApiAuthoringLoad,
155
- A as callPieApiAuthoringReleaseContent,
156
- b as callPieApiAuthoringSaveContent,
157
- y as callPieApiDeliveryLoad,
158
- S as callPieApiDeliveryModel,
159
- f as callPieApiDeliverySave,
160
- w as callPieApiDeliveryScore
161
+ q as callPieApiAuthoringLoad,
162
+ C as callPieApiAuthoringReleaseContent,
163
+ k as callPieApiAuthoringSaveContent,
164
+ b as callPieApiDeliveryLoad,
165
+ A as callPieApiDeliveryModel,
166
+ T as callPieApiDeliverySave,
167
+ O as callPieApiDeliveryScore
161
168
  };
@@ -9,6 +9,7 @@ export declare class SectionController implements SectionControllerHandle {
9
9
  private readonly sessionService;
10
10
  private readonly itemNavigationService;
11
11
  private sessionPersistence;
12
+ private pendingSessionCommit;
12
13
  private state;
13
14
  private readonly listeners;
14
15
  private readonly trackedRenderables;
@@ -38,6 +39,13 @@ export declare class SectionController implements SectionControllerHandle {
38
39
  private mediaAttachWatch;
39
40
  private emitChange;
40
41
  subscribe(listener: SectionControllerChangeListener): () => void;
42
+ /**
43
+ * Register a commit for pending element sessions, run before a boundary this
44
+ * controller owns. The owner of the section's DOM supplies it; a host-built
45
+ * controller that never registers one keeps today's behaviour.
46
+ */
47
+ setPendingSessionCommit(commit: (() => void) | null): void;
48
+ private commitPendingItemSessions;
41
49
  initialize(input?: unknown): Promise<void>;
42
50
  updateInput(input?: unknown): Promise<void>;
43
51
  configureSessionPersistence(config: SectionSessionPersistenceConfig): void;