@superbuilders/primer-tives 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  // src/client.ts
2
- import * as errors8 from "@superbuilders/errors";
2
+ import * as errors10 from "@superbuilders/errors";
3
3
 
4
4
  // src/errors.ts
5
5
  import * as errors from "@superbuilders/errors";
6
6
  var ErrNetwork = errors.new("network");
7
7
  var ErrJsonParse = errors.new("json parse");
8
8
  var ErrUnsupportedPci = errors.new("unsupported pci");
9
- var ErrInvalidPublishableKey = errors.new("invalid publishable key");
9
+ var ErrInvalidAccessToken = errors.new("invalid access token");
10
+ var ErrMalformedAccessToken = errors.new("malformed access token");
11
+ var ErrTokenExpired = errors.new("access token expired");
10
12
  var ErrBadRequest = errors.new("bad request");
11
13
  var ErrServerError = errors.new("server error");
12
14
  var ErrTimeout = errors.new("timeout");
@@ -17,7 +19,6 @@ var ErrRateLimited = errors.new("rate limited");
17
19
  var ErrServiceUnavailable = errors.new("service unavailable");
18
20
  var ErrNotSerializable = errors.new("PrimerState is live in-memory state and must not be serialized or stored");
19
21
  var ErrInvalidSubmission = errors.new("invalid submission");
20
- var ErrMalformedPublishableKey = errors.new("malformed publishable key");
21
22
 
22
23
  // src/transport.ts
23
24
  import * as errors2 from "@superbuilders/errors";
@@ -27,7 +28,7 @@ function httpSentinel(status) {
27
28
  return ErrBadRequest;
28
29
  }
29
30
  if (status === 401) {
30
- return ErrInvalidPublishableKey;
31
+ return ErrInvalidAccessToken;
31
32
  }
32
33
  if (status === 403) {
33
34
  return ErrForbidden;
@@ -66,15 +67,15 @@ function createTransport(tc) {
66
67
  }
67
68
  async function transport(body) {
68
69
  log?.debug("transport request", {
69
- studentId: body.studentId,
70
- intentKind: body.intent.kind
70
+ intentKind: body.intent.kind,
71
+ subject: body.subject
71
72
  });
72
73
  const url = `${tc.origin}${ADVANCE_PATH}`;
73
74
  const fetchResult = await fetchFn(url, {
74
75
  method: "POST",
75
76
  headers: {
76
77
  "Content-Type": "application/json",
77
- Authorization: `Bearer ${tc.publishableKey}`
78
+ Authorization: `Bearer ${tc.accessToken}`
78
79
  },
79
80
  body: JSON.stringify(body),
80
81
  signal: transportSignal()
@@ -86,13 +87,11 @@ function createTransport(tc) {
86
87
  if (!fetchResult.ok) {
87
88
  if (isAbortError(fetchResult.error)) {
88
89
  log?.error("transport timeout", {
89
- studentId: body.studentId,
90
90
  intentKind: body.intent.kind
91
91
  });
92
92
  return { ok: false, error: errors2.wrap(ErrTimeout, fetchResult.error.message) };
93
93
  }
94
94
  log?.error("transport network error", {
95
- studentId: body.studentId,
96
95
  error: fetchResult.error
97
96
  });
98
97
  return { ok: false, error: errors2.wrap(ErrNetwork, fetchResult.error.message) };
@@ -104,7 +103,6 @@ function createTransport(tc) {
104
103
  });
105
104
  const sentinel = res.status === 422 ? ErrUnsupportedPci : httpSentinel(res.status);
106
105
  log?.error("transport http error", {
107
- studentId: body.studentId,
108
106
  status: res.status
109
107
  });
110
108
  return { ok: false, error: errors2.wrap(sentinel, text) };
@@ -116,13 +114,11 @@ function createTransport(tc) {
116
114
  });
117
115
  if (!jsonResult.ok) {
118
116
  log?.error("transport json parse failed", {
119
- studentId: body.studentId,
120
117
  error: jsonResult.error
121
118
  });
122
119
  return { ok: false, error: errors2.wrap(ErrJsonParse, jsonResult.error.message) };
123
120
  }
124
121
  log?.debug("transport success", {
125
- studentId: body.studentId,
126
122
  intentKind: body.intent.kind
127
123
  });
128
124
  return { ok: true, data: jsonResult.data };
@@ -131,7 +127,7 @@ function createTransport(tc) {
131
127
  }
132
128
 
133
129
  // src/session.ts
134
- import * as errors7 from "@superbuilders/errors";
130
+ import * as errors9 from "@superbuilders/errors";
135
131
 
136
132
  // src/consumed.ts
137
133
  function poisonToJSON() {
@@ -173,7 +169,45 @@ function validateChoiceSubmission(log, selectedKeys, options, maxChoices, minCho
173
169
  return null;
174
170
  }
175
171
  function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices) {
176
- let pending;
172
+ let submitPending;
173
+ let submitKey;
174
+ let timeoutPending;
175
+ function beginSubmit(selectedKeys) {
176
+ const submission = { type: "choice", selectedKeys };
177
+ const key = JSON.stringify(submission);
178
+ if (timeoutPending) {
179
+ return Promise.resolve(ctx.errored(errors3.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
180
+ }
181
+ if (submitPending) {
182
+ if (submitKey === key) {
183
+ return submitPending;
184
+ }
185
+ return Promise.resolve(ctx.errored(errors3.wrap(ErrConflict, "cannot submit a different choice payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
186
+ }
187
+ const validationError = validateChoiceSubmission(ctx.log, selectedKeys, options, maxChoices, minChoices);
188
+ if (validationError) {
189
+ return Promise.resolve(ctx.errored(validationError, "interaction", { kind: "interaction", submission }));
190
+ }
191
+ submitKey = key;
192
+ submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
193
+ submitPending = undefined;
194
+ submitKey = undefined;
195
+ });
196
+ return submitPending;
197
+ }
198
+ function beginTimeout() {
199
+ const intent = { kind: "timeout" };
200
+ if (submitPending) {
201
+ return Promise.resolve(ctx.errored(errors3.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
202
+ }
203
+ if (timeoutPending) {
204
+ return timeoutPending;
205
+ }
206
+ timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
207
+ timeoutPending = undefined;
208
+ });
209
+ return timeoutPending;
210
+ }
177
211
  return {
178
212
  phase: "interaction",
179
213
  kind: "choice",
@@ -182,27 +216,8 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
182
216
  options,
183
217
  maxChoices,
184
218
  minChoices,
185
- submitChoice: function submitChoice(selectedKeys) {
186
- if (pending) {
187
- return pending;
188
- }
189
- const validationError = validateChoiceSubmission(ctx.log, selectedKeys, options, maxChoices, minChoices);
190
- if (validationError) {
191
- return Promise.resolve(ctx.errored(validationError, "interaction", {
192
- kind: "interaction",
193
- submission: { type: "choice", selectedKeys }
194
- }));
195
- }
196
- pending = ctx.execute({ kind: "interaction", submission: { type: "choice", selectedKeys } }, "interaction");
197
- return pending;
198
- },
199
- timeout: function timeout() {
200
- if (pending) {
201
- return pending;
202
- }
203
- pending = ctx.execute({ kind: "timeout" }, "timeout");
204
- return pending;
205
- },
219
+ submitChoice: beginSubmit,
220
+ timeout: beginTimeout,
206
221
  toJSON: poisonToJSON
207
222
  };
208
223
  }
@@ -222,66 +237,107 @@ function validateExtendedTextSubmission(log, values, minStrings, maxStrings) {
222
237
  }
223
238
  function extendedTextState(ctx, stimulus, interaction) {
224
239
  if (interaction.cardinality === "single") {
225
- let pending2;
240
+ let submitText = function(value) {
241
+ const submission = { type: "extended-text", values: [value] };
242
+ const key = JSON.stringify(submission);
243
+ if (timeoutPending2) {
244
+ return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
245
+ }
246
+ if (submitPending2) {
247
+ if (submitKey2 === key) {
248
+ return submitPending2;
249
+ }
250
+ return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
251
+ }
252
+ submitKey2 = key;
253
+ submitPending2 = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
254
+ submitPending2 = undefined;
255
+ submitKey2 = undefined;
256
+ });
257
+ return submitPending2;
258
+ }, timeout2 = function() {
259
+ const intent = { kind: "timeout" };
260
+ if (submitPending2) {
261
+ return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
262
+ }
263
+ if (timeoutPending2) {
264
+ return timeoutPending2;
265
+ }
266
+ timeoutPending2 = ctx.execute(intent, "timeout").finally(function clearPending() {
267
+ timeoutPending2 = undefined;
268
+ });
269
+ return timeoutPending2;
270
+ };
271
+ let submitPending2;
272
+ let submitKey2;
273
+ let timeoutPending2;
226
274
  return {
227
275
  phase: "interaction",
228
276
  kind: "extended-text",
229
277
  cardinality: "single",
230
278
  stimulus,
231
279
  interaction,
232
- submitText: function submitText(value) {
233
- if (pending2) {
234
- return pending2;
235
- }
236
- pending2 = ctx.execute({ kind: "interaction", submission: { type: "extended-text", values: [value] } }, "interaction");
237
- return pending2;
238
- },
239
- timeout: function timeout() {
240
- if (pending2) {
241
- return pending2;
242
- }
243
- pending2 = ctx.execute({ kind: "timeout" }, "timeout");
244
- return pending2;
245
- },
280
+ submitText,
281
+ timeout: timeout2,
246
282
  toJSON: poisonToJSON
247
283
  };
248
284
  }
249
- let pending;
285
+ const multi = interaction;
286
+ let submitPending;
287
+ let submitKey;
288
+ let timeoutPending;
289
+ function submitTexts(values) {
290
+ const submission = { type: "extended-text", values };
291
+ const key = JSON.stringify(submission);
292
+ if (timeoutPending) {
293
+ return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
294
+ }
295
+ if (submitPending) {
296
+ if (submitKey === key) {
297
+ return submitPending;
298
+ }
299
+ return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
300
+ }
301
+ const validationError = validateExtendedTextSubmission(ctx.log, values, multi.minStrings, multi.maxStrings);
302
+ if (validationError) {
303
+ return Promise.resolve(ctx.errored(validationError, "interaction", { kind: "interaction", submission }));
304
+ }
305
+ submitKey = key;
306
+ submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
307
+ submitPending = undefined;
308
+ submitKey = undefined;
309
+ });
310
+ return submitPending;
311
+ }
312
+ function timeout() {
313
+ const intent = { kind: "timeout" };
314
+ if (submitPending) {
315
+ return Promise.resolve(ctx.errored(errors4.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
316
+ }
317
+ if (timeoutPending) {
318
+ return timeoutPending;
319
+ }
320
+ timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
321
+ timeoutPending = undefined;
322
+ });
323
+ return timeoutPending;
324
+ }
250
325
  return {
251
326
  phase: "interaction",
252
327
  kind: "extended-text",
253
328
  cardinality: "multiple",
254
329
  stimulus,
255
- interaction,
256
- maxStrings: interaction.maxStrings,
257
- minStrings: interaction.minStrings,
258
- submitTexts: function submitTexts(values) {
259
- if (pending) {
260
- return pending;
261
- }
262
- const validationError = validateExtendedTextSubmission(ctx.log, values, interaction.minStrings, interaction.maxStrings);
263
- if (validationError) {
264
- return Promise.resolve(ctx.errored(validationError, "interaction", {
265
- kind: "interaction",
266
- submission: { type: "extended-text", values }
267
- }));
268
- }
269
- pending = ctx.execute({ kind: "interaction", submission: { type: "extended-text", values } }, "interaction");
270
- return pending;
271
- },
272
- timeout: function timeout() {
273
- if (pending) {
274
- return pending;
275
- }
276
- pending = ctx.execute({ kind: "timeout" }, "timeout");
277
- return pending;
278
- },
330
+ interaction: multi,
331
+ maxStrings: multi.maxStrings,
332
+ minStrings: multi.minStrings,
333
+ submitTexts,
334
+ timeout,
279
335
  toJSON: poisonToJSON
280
336
  };
281
337
  }
282
338
 
283
339
  // src/feedback-state.ts
284
- function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedbackContent, correctAnswer) {
340
+ function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedbackContent, review) {
285
341
  let pending;
286
342
  return {
287
343
  phase: "feedback",
@@ -290,7 +346,7 @@ function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedba
290
346
  submission,
291
347
  isCorrect,
292
348
  feedbackContent,
293
- correctAnswer,
349
+ review,
294
350
  advance: function advance() {
295
351
  if (pending) {
296
352
  return pending;
@@ -391,7 +447,45 @@ function validateMatchSubmission(log, pairs, sourceChoices, targetChoices, minAs
391
447
  return null;
392
448
  }
393
449
  function matchState(ctx, stimulus, interaction) {
394
- let pending;
450
+ let submitPending;
451
+ let submitKey;
452
+ let timeoutPending;
453
+ function submitMatch(pairs) {
454
+ const submission = { type: "match", pairs };
455
+ const key = JSON.stringify(submission);
456
+ if (timeoutPending) {
457
+ return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
458
+ }
459
+ if (submitPending) {
460
+ if (submitKey === key) {
461
+ return submitPending;
462
+ }
463
+ return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit a different match payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
464
+ }
465
+ const validationError = validateMatchSubmission(ctx.log, pairs, interaction.sourceChoices, interaction.targetChoices, interaction.minAssociations, interaction.maxAssociations);
466
+ if (validationError) {
467
+ return Promise.resolve(ctx.errored(validationError, "interaction", { kind: "interaction", submission }));
468
+ }
469
+ submitKey = key;
470
+ submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
471
+ submitPending = undefined;
472
+ submitKey = undefined;
473
+ });
474
+ return submitPending;
475
+ }
476
+ function timeout() {
477
+ const intent = { kind: "timeout" };
478
+ if (submitPending) {
479
+ return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
480
+ }
481
+ if (timeoutPending) {
482
+ return timeoutPending;
483
+ }
484
+ timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
485
+ timeoutPending = undefined;
486
+ });
487
+ return timeoutPending;
488
+ }
395
489
  return {
396
490
  phase: "interaction",
397
491
  kind: "match",
@@ -401,27 +495,8 @@ function matchState(ctx, stimulus, interaction) {
401
495
  targetChoices: interaction.targetChoices,
402
496
  minAssociations: interaction.minAssociations,
403
497
  maxAssociations: interaction.maxAssociations,
404
- submitMatch: function submitMatch(pairs) {
405
- if (pending) {
406
- return pending;
407
- }
408
- const validationError = validateMatchSubmission(ctx.log, pairs, interaction.sourceChoices, interaction.targetChoices, interaction.minAssociations, interaction.maxAssociations);
409
- if (validationError) {
410
- return Promise.resolve(ctx.errored(validationError, "interaction", {
411
- kind: "interaction",
412
- submission: { type: "match", pairs }
413
- }));
414
- }
415
- pending = ctx.execute({ kind: "interaction", submission: { type: "match", pairs } }, "interaction");
416
- return pending;
417
- },
418
- timeout: function timeout() {
419
- if (pending) {
420
- return pending;
421
- }
422
- pending = ctx.execute({ kind: "timeout" }, "timeout");
423
- return pending;
424
- },
498
+ submitMatch,
499
+ timeout,
425
500
  toJSON: poisonToJSON
426
501
  };
427
502
  }
@@ -478,7 +553,45 @@ function validateOrderSubmission(log, orderedKeys, choices, minChoices, maxChoic
478
553
  return null;
479
554
  }
480
555
  function orderState(ctx, stimulus, interaction) {
481
- let pending;
556
+ let submitPending;
557
+ let submitKey;
558
+ let timeoutPending;
559
+ function submitOrder(orderedKeys) {
560
+ const submission = { type: "order", orderedKeys };
561
+ const key = JSON.stringify(submission);
562
+ if (timeoutPending) {
563
+ return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
564
+ }
565
+ if (submitPending) {
566
+ if (submitKey === key) {
567
+ return submitPending;
568
+ }
569
+ return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit a different order payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
570
+ }
571
+ const validationError = validateOrderSubmission(ctx.log, orderedKeys, interaction.choices, interaction.minChoices, interaction.maxChoices);
572
+ if (validationError) {
573
+ return Promise.resolve(ctx.errored(validationError, "interaction", { kind: "interaction", submission }));
574
+ }
575
+ submitKey = key;
576
+ submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
577
+ submitPending = undefined;
578
+ submitKey = undefined;
579
+ });
580
+ return submitPending;
581
+ }
582
+ function timeout() {
583
+ const intent = { kind: "timeout" };
584
+ if (submitPending) {
585
+ return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
586
+ }
587
+ if (timeoutPending) {
588
+ return timeoutPending;
589
+ }
590
+ timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
591
+ timeoutPending = undefined;
592
+ });
593
+ return timeoutPending;
594
+ }
482
595
  return {
483
596
  phase: "interaction",
484
597
  kind: "order",
@@ -487,51 +600,52 @@ function orderState(ctx, stimulus, interaction) {
487
600
  choices: interaction.choices,
488
601
  minChoices: interaction.minChoices,
489
602
  maxChoices: interaction.maxChoices,
490
- submitOrder: function submitOrder(orderedKeys) {
491
- if (pending) {
492
- return pending;
493
- }
494
- const validationError = validateOrderSubmission(ctx.log, orderedKeys, interaction.choices, interaction.minChoices, interaction.maxChoices);
495
- if (validationError) {
496
- return Promise.resolve(ctx.errored(validationError, "interaction", {
497
- kind: "interaction",
498
- submission: { type: "order", orderedKeys }
499
- }));
500
- }
501
- pending = ctx.execute({ kind: "interaction", submission: { type: "order", orderedKeys } }, "interaction");
502
- return pending;
503
- },
504
- timeout: function timeout() {
505
- if (pending) {
506
- return pending;
507
- }
508
- pending = ctx.execute({ kind: "timeout" }, "timeout");
509
- return pending;
510
- },
603
+ submitOrder,
604
+ timeout,
511
605
  toJSON: poisonToJSON
512
606
  };
513
607
  }
514
608
 
515
609
  // src/pci-state.ts
610
+ import * as errors7 from "@superbuilders/errors";
516
611
  function pciInteractionState(ctx, stimulus, interaction) {
517
- let pending;
612
+ let submitPending;
613
+ let submitKey;
614
+ let timeoutPending;
518
615
  const { pciId, properties } = interaction;
519
616
  function submit(value) {
520
- if (pending) {
521
- return pending;
617
+ const submission = { type: "portable-custom", pciId, value };
618
+ const key = JSON.stringify(submission);
619
+ if (timeoutPending) {
620
+ return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
621
+ }
622
+ if (submitPending) {
623
+ if (submitKey === key) {
624
+ return submitPending;
625
+ }
626
+ return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
522
627
  }
523
628
  ctx.log?.debug("pci submit", { pciId });
524
- const submission = { type: "portable-custom", pciId, value };
525
- pending = ctx.execute({ kind: "interaction", submission }, "interaction");
526
- return pending;
629
+ submitKey = key;
630
+ submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
631
+ submitPending = undefined;
632
+ submitKey = undefined;
633
+ });
634
+ return submitPending;
527
635
  }
528
636
  function timeout() {
529
- if (pending) {
530
- return pending;
637
+ const intent = { kind: "timeout" };
638
+ if (submitPending) {
639
+ return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
640
+ }
641
+ if (timeoutPending) {
642
+ return timeoutPending;
531
643
  }
532
644
  ctx.log?.debug("pci timeout", { pciId });
533
- pending = ctx.execute({ kind: "timeout" }, "timeout");
534
- return pending;
645
+ timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
646
+ timeoutPending = undefined;
647
+ });
648
+ return timeoutPending;
535
649
  }
536
650
  return {
537
651
  phase: "interaction",
@@ -547,27 +661,50 @@ function pciInteractionState(ctx, stimulus, interaction) {
547
661
  }
548
662
 
549
663
  // src/text-entry-state.ts
664
+ import * as errors8 from "@superbuilders/errors";
550
665
  function textEntryState(ctx, stimulus, interaction) {
551
- let pending;
666
+ let submitPending;
667
+ let submitKey;
668
+ let timeoutPending;
669
+ function submitText(value) {
670
+ const submission = { type: "text-entry", value };
671
+ const key = JSON.stringify(submission);
672
+ if (timeoutPending) {
673
+ return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
674
+ }
675
+ if (submitPending) {
676
+ if (submitKey === key) {
677
+ return submitPending;
678
+ }
679
+ return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit a different text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
680
+ }
681
+ submitKey = key;
682
+ submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
683
+ submitPending = undefined;
684
+ submitKey = undefined;
685
+ });
686
+ return submitPending;
687
+ }
688
+ function timeout() {
689
+ const intent = { kind: "timeout" };
690
+ if (submitPending) {
691
+ return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
692
+ }
693
+ if (timeoutPending) {
694
+ return timeoutPending;
695
+ }
696
+ timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
697
+ timeoutPending = undefined;
698
+ });
699
+ return timeoutPending;
700
+ }
552
701
  return {
553
702
  phase: "interaction",
554
703
  kind: "text-entry",
555
704
  stimulus,
556
705
  interaction,
557
- submitText: function submitText(value) {
558
- if (pending) {
559
- return pending;
560
- }
561
- pending = ctx.execute({ kind: "interaction", submission: { type: "text-entry", value } }, "interaction");
562
- return pending;
563
- },
564
- timeout: function timeout() {
565
- if (pending) {
566
- return pending;
567
- }
568
- pending = ctx.execute({ kind: "timeout" }, "timeout");
569
- return pending;
570
- },
706
+ submitText,
707
+ timeout,
571
708
  toJSON: poisonToJSON
572
709
  };
573
710
  }
@@ -575,19 +712,23 @@ function textEntryState(ctx, stimulus, interaction) {
575
712
  // src/session.ts
576
713
  var FATAL_SENTINELS = [
577
714
  ErrBadRequest,
578
- ErrInvalidPublishableKey,
715
+ ErrInvalidAccessToken,
716
+ ErrTokenExpired,
579
717
  ErrForbidden,
580
718
  ErrNotFound,
581
719
  ErrUnsupportedPci
582
720
  ];
583
721
  function isFatalError(err) {
584
722
  for (const sentinel of FATAL_SENTINELS) {
585
- if (errors7.is(err, sentinel)) {
723
+ if (errors9.is(err, sentinel)) {
586
724
  return true;
587
725
  }
588
726
  }
589
727
  return false;
590
728
  }
729
+ function isRetriableError(err) {
730
+ return !errors9.is(err, ErrInvalidSubmission);
731
+ }
591
732
  function makeSession(sc) {
592
733
  const log = sc.log;
593
734
  function resolve(result) {
@@ -595,18 +736,23 @@ function makeSession(sc) {
595
736
  case "advanced":
596
737
  return fromAdvanced(result.stimulus, result.interaction);
597
738
  case "submitted":
598
- return feedbackState(ctx, result.stimulus, result.interaction, result.submission, result.isCorrect, result.feedbackContent, result.correctAnswer);
739
+ return feedbackState(ctx, result.stimulus, result.interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
599
740
  case "completed":
600
741
  return { phase: "completed", toJSON: poisonToJSON };
601
742
  }
602
743
  }
603
744
  function errored(error, failedPhase, intent) {
604
745
  let pending;
605
- return {
746
+ const retriable = isRetriableError(error);
747
+ const state = {
606
748
  phase: "errored",
607
749
  error,
608
- failedPhase,
750
+ retriable,
609
751
  retry: function retry() {
752
+ if (!retriable) {
753
+ log?.debug("retry ignored for non-retriable error", { failedPhase });
754
+ return Promise.resolve(state);
755
+ }
610
756
  if (pending) {
611
757
  return pending;
612
758
  }
@@ -616,18 +762,19 @@ function makeSession(sc) {
616
762
  },
617
763
  toJSON: poisonToJSON
618
764
  };
765
+ return state;
619
766
  }
620
767
  async function execute(intent, phase) {
621
768
  const body = {
622
- studentId: sc.studentId,
623
769
  supportedPcis: sc.supportedPcis,
624
- intent
770
+ intent,
771
+ subject: sc.subject
625
772
  };
626
773
  const result = await sc.transport(body);
627
774
  if (!result.ok) {
628
775
  if (isFatalError(result.error)) {
629
776
  log?.error("fatal transport error", { error: result.error, phase });
630
- return { phase: "fatal", error: result.error, toJSON: poisonToJSON };
777
+ return { phase: "fatal", error: result.error, retriable: false, toJSON: poisonToJSON };
631
778
  }
632
779
  return errored(result.error, phase, intent);
633
780
  }
@@ -650,7 +797,8 @@ function makeSession(sc) {
650
797
  log?.error("unsupported pci in frame", { pciId: interaction.pciId });
651
798
  return {
652
799
  phase: "fatal",
653
- error: errors7.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
800
+ error: errors9.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
801
+ retriable: false,
654
802
  toJSON: poisonToJSON
655
803
  };
656
804
  }
@@ -678,15 +826,22 @@ function makeSession(sc) {
678
826
  }
679
827
 
680
828
  // src/client.ts
681
- var PK_PREFIX = "pk_";
829
+ var ACCESS_TOKEN_PREFIX = "eyJ";
830
+ function isMalformedJws(token) {
831
+ if (!token.startsWith(ACCESS_TOKEN_PREFIX)) {
832
+ return true;
833
+ }
834
+ const dotCount = token.split(".").length - 1;
835
+ return dotCount !== 2;
836
+ }
682
837
  function create(config) {
683
838
  const log = config.logger;
684
- if (!config.publishableKey.startsWith(PK_PREFIX)) {
685
- log?.error("malformed publishable key", { prefix: PK_PREFIX });
686
- throw errors8.wrap(ErrMalformedPublishableKey, `key must start with '${PK_PREFIX}'`);
839
+ if (isMalformedJws(config.accessToken)) {
840
+ log?.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
841
+ throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
687
842
  }
688
843
  const transport = createTransport({
689
- publishableKey: config.publishableKey,
844
+ accessToken: config.accessToken,
690
845
  supportedPcis: config.supportedPcis,
691
846
  origin: config.origin,
692
847
  fetch: config.fetch,
@@ -694,27 +849,29 @@ function create(config) {
694
849
  log
695
850
  });
696
851
  let startPromise;
697
- async function doStart(studentId) {
698
- log?.debug("start", { studentId });
852
+ async function doStart() {
853
+ log?.debug("start", { subject: config.subject });
699
854
  const s = makeSession({
700
- studentId,
701
855
  supportedPcis: config.supportedPcis,
856
+ subject: config.subject,
702
857
  log,
703
858
  transport
704
859
  });
705
860
  return s.execute({ kind: "observation" }, "observation");
706
861
  }
707
862
  return {
708
- start(studentId) {
863
+ start() {
709
864
  if (startPromise) {
710
865
  log?.debug("start already called");
711
866
  return startPromise;
712
867
  }
713
- startPromise = doStart(studentId);
868
+ startPromise = doStart();
714
869
  return startPromise;
715
870
  }
716
871
  };
717
872
  }
873
+ // src/subject.ts
874
+ var SUBJECTS = ["math", "vocabulary"];
718
875
  // src/content.ts
719
876
  function inlinesToPlainText(nodes) {
720
877
  const parts = [];
@@ -743,7 +900,9 @@ export {
743
900
  inlinesToPlainText,
744
901
  create,
745
902
  blocksToPlainText,
903
+ SUBJECTS,
746
904
  ErrUnsupportedPci,
905
+ ErrTokenExpired,
747
906
  ErrTimeout,
748
907
  ErrServiceUnavailable,
749
908
  ErrServerError,
@@ -751,14 +910,14 @@ export {
751
910
  ErrNotSerializable,
752
911
  ErrNotFound,
753
912
  ErrNetwork,
754
- ErrMalformedPublishableKey,
913
+ ErrMalformedAccessToken,
755
914
  ErrJsonParse,
756
915
  ErrInvalidSubmission,
757
- ErrInvalidPublishableKey,
916
+ ErrInvalidAccessToken,
758
917
  ErrForbidden,
759
918
  ErrConflict,
760
919
  ErrBadRequest,
761
920
  ADVANCE_PATH
762
921
  };
763
922
 
764
- //# debugId=FEF5C86C2285295364756E2164756E21
923
+ //# debugId=7D25EB69305CCB9264756E2164756E21