@superbuilders/primer-tives 1.2.0 → 2.2.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/README.md +744 -533
- package/dist/client/choice-state.d.ts +2 -1
- package/dist/client/choice-state.d.ts.map +1 -1
- package/dist/client/create.d.ts +23 -7
- package/dist/client/create.d.ts.map +1 -1
- package/dist/client/extended-text-state.d.ts +2 -1
- package/dist/client/extended-text-state.d.ts.map +1 -1
- package/dist/client/feedback-state.d.ts +2 -2
- package/dist/client/feedback-state.d.ts.map +1 -1
- package/dist/client/index.js +226 -109
- package/dist/client/index.js.map +21 -17
- package/dist/client/match-state.d.ts +2 -1
- package/dist/client/match-state.d.ts.map +1 -1
- package/dist/client/observation-state.d.ts +2 -1
- package/dist/client/observation-state.d.ts.map +1 -1
- package/dist/client/order-state.d.ts +2 -1
- package/dist/client/order-state.d.ts.map +1 -1
- package/dist/client/pci-state.d.ts +2 -1
- package/dist/client/pci-state.d.ts.map +1 -1
- package/dist/client/session-context.d.ts +1 -1
- package/dist/client/session-context.d.ts.map +1 -1
- package/dist/client/session.d.ts +2 -2
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/text-entry-state.d.ts +2 -1
- package/dist/client/text-entry-state.d.ts.map +1 -1
- package/dist/client/transport.d.ts +13 -11
- package/dist/client/transport.d.ts.map +1 -1
- package/dist/client/types.d.ts +10 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +4 -3
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +42 -36
- package/dist/contracts/index.js.map +6 -5
- package/dist/contracts/pci-schemas.d.ts +24 -20
- package/dist/contracts/pci-schemas.d.ts.map +1 -1
- package/dist/contracts/pci.d.ts +26 -27
- package/dist/contracts/pci.d.ts.map +1 -1
- package/dist/contracts/types.d.ts +5 -9
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/validation.d.ts +34 -23
- package/dist/contracts/validation.d.ts.map +1 -1
- package/dist/errors.d.ts +3 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -11
- package/dist/errors.js.map +3 -3
- package/dist/server/create-server.d.ts +3 -33
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/exchange.d.ts +7 -14
- package/dist/server/exchange.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +32 -407
- package/dist/server/index.js.map +6 -9
- package/dist/subject-pcis.d.ts +13 -0
- package/dist/subject-pcis.d.ts.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +5 -1
- package/dist/server/hints.d.ts +0 -25
- package/dist/server/hints.d.ts.map +0 -1
- package/dist/server/students.d.ts +0 -12
- package/dist/server/students.d.ts.map +0 -1
package/dist/client/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as errors from "@superbuilders/errors";
|
|
|
3
3
|
var ErrNetwork = errors.new("network");
|
|
4
4
|
var ErrJsonParse = errors.new("json parse");
|
|
5
5
|
var ErrUnsupportedPci = errors.new("unsupported pci");
|
|
6
|
+
var ErrMissingRequiredPci = errors.new("missing required pci");
|
|
6
7
|
var ErrInvalidAccessToken = errors.new("invalid access token");
|
|
7
8
|
var ErrMalformedAccessToken = errors.new("malformed access token");
|
|
8
9
|
var ErrTokenExpired = errors.new("access token expired");
|
|
@@ -12,16 +13,12 @@ var ErrTimeout = errors.new("timeout");
|
|
|
12
13
|
var ErrForbidden = errors.new("forbidden");
|
|
13
14
|
var ErrNotFound = errors.new("not found");
|
|
14
15
|
var ErrConflict = errors.new("conflict");
|
|
15
|
-
var ErrExternalAuthorityRequired = errors.new("external authority required");
|
|
16
16
|
var ErrRateLimited = errors.new("rate limited");
|
|
17
17
|
var ErrServiceUnavailable = errors.new("service unavailable");
|
|
18
18
|
var ErrNotSerializable = errors.new("PrimerState is live in-memory state and must not be serialized or stored");
|
|
19
19
|
var ErrInvalidSubmission = errors.new("invalid submission");
|
|
20
20
|
var ErrInvalidSecretKey = errors.new("invalid secret key");
|
|
21
|
-
var
|
|
22
|
-
var ErrUnsupportedGrade = errors.new("unsupported grade");
|
|
23
|
-
var ErrTimebackUnavailable = errors.new("timeback unavailable");
|
|
24
|
-
var ErrNeedsHints = errors.new("student needs hints set before /advance");
|
|
21
|
+
var ErrSdkUpgradeRequired = errors.new("sdk upgrade required");
|
|
25
22
|
// src/contracts/content.ts
|
|
26
23
|
function inlinesToPlainText(nodes) {
|
|
27
24
|
const parts = [];
|
|
@@ -46,31 +43,44 @@ function blocksToPlainText(blocks) {
|
|
|
46
43
|
}).join(`
|
|
47
44
|
`);
|
|
48
45
|
}
|
|
46
|
+
// src/contracts/pci.ts
|
|
47
|
+
var PCI_IDS = ["urn:primer:pci:fraction-input"];
|
|
48
|
+
function isPciId(value) {
|
|
49
|
+
for (const id of PCI_IDS) {
|
|
50
|
+
if (id === value) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
49
56
|
// src/contracts/validation.ts
|
|
50
57
|
import { z as z2 } from "zod";
|
|
51
58
|
|
|
52
59
|
// src/contracts/pci-schemas.ts
|
|
53
60
|
import { z } from "zod";
|
|
54
|
-
var
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
58
|
-
var DivisionRemainderSubmissionSchema = z.object({
|
|
59
|
-
quotient: z.string(),
|
|
60
|
-
remainder: z.string()
|
|
61
|
-
});
|
|
62
|
-
var FractionOperandSchema = z.object({
|
|
63
|
-
numerator: z.number(),
|
|
64
|
-
denominator: z.number()
|
|
65
|
-
});
|
|
66
|
-
var FractionAdditionPropsSchema = z.object({
|
|
67
|
-
left: FractionOperandSchema,
|
|
68
|
-
right: FractionOperandSchema
|
|
69
|
-
});
|
|
70
|
-
var FractionAdditionSubmissionSchema = z.object({
|
|
71
|
-
numerator: z.string(),
|
|
72
|
-
denominator: z.string()
|
|
61
|
+
var FractionInputPropsSchema = z.object({
|
|
62
|
+
form: z.enum(["whole", "proper", "improper", "mixed"]),
|
|
63
|
+
requireSimplified: z.boolean()
|
|
73
64
|
});
|
|
65
|
+
var FractionInputSubmissionSchema = z.discriminatedUnion("form", [
|
|
66
|
+
z.object({ form: z.literal("whole"), whole: z.string() }),
|
|
67
|
+
z.object({
|
|
68
|
+
form: z.literal("proper"),
|
|
69
|
+
numerator: z.string(),
|
|
70
|
+
denominator: z.string()
|
|
71
|
+
}),
|
|
72
|
+
z.object({
|
|
73
|
+
form: z.literal("improper"),
|
|
74
|
+
numerator: z.string(),
|
|
75
|
+
denominator: z.string()
|
|
76
|
+
}),
|
|
77
|
+
z.object({
|
|
78
|
+
form: z.literal("mixed"),
|
|
79
|
+
whole: z.string(),
|
|
80
|
+
numerator: z.string(),
|
|
81
|
+
denominator: z.string()
|
|
82
|
+
})
|
|
83
|
+
]);
|
|
74
84
|
|
|
75
85
|
// src/contracts/validation.ts
|
|
76
86
|
var MatchPairSchema = z2.object({
|
|
@@ -97,15 +107,10 @@ var MatchSubmissionSchema = z2.object({
|
|
|
97
107
|
type: z2.literal("match"),
|
|
98
108
|
pairs: z2.array(MatchPairSchema)
|
|
99
109
|
});
|
|
100
|
-
var
|
|
101
|
-
type: z2.literal("portable-custom"),
|
|
102
|
-
pciId: z2.literal("urn:primer:pci:division-remainder"),
|
|
103
|
-
value: DivisionRemainderSubmissionSchema
|
|
104
|
-
});
|
|
105
|
-
var FractionAdditionPciSubmissionSchema = z2.object({
|
|
110
|
+
var FractionInputPciSubmissionSchema = z2.object({
|
|
106
111
|
type: z2.literal("portable-custom"),
|
|
107
|
-
pciId: z2.literal("urn:primer:pci:fraction-
|
|
108
|
-
value:
|
|
112
|
+
pciId: z2.literal("urn:primer:pci:fraction-input"),
|
|
113
|
+
value: FractionInputSubmissionSchema
|
|
109
114
|
});
|
|
110
115
|
var RendererSubmissionSchema = z2.union([
|
|
111
116
|
ChoiceSubmissionSchema,
|
|
@@ -113,8 +118,7 @@ var RendererSubmissionSchema = z2.union([
|
|
|
113
118
|
ExtendedTextSubmissionSchema,
|
|
114
119
|
OrderSubmissionSchema,
|
|
115
120
|
MatchSubmissionSchema,
|
|
116
|
-
|
|
117
|
-
FractionAdditionPciSubmissionSchema
|
|
121
|
+
FractionInputPciSubmissionSchema
|
|
118
122
|
]);
|
|
119
123
|
function duplicates(values, keyOf) {
|
|
120
124
|
const seen = new Set;
|
|
@@ -166,10 +170,8 @@ function validateUsageBounds(choices, counts, side) {
|
|
|
166
170
|
}
|
|
167
171
|
function pciSubmissionSchema(pciId) {
|
|
168
172
|
switch (pciId) {
|
|
169
|
-
case "urn:primer:pci:
|
|
170
|
-
return
|
|
171
|
-
case "urn:primer:pci:fraction-addition":
|
|
172
|
-
return FractionAdditionSubmissionSchema;
|
|
173
|
+
case "urn:primer:pci:fraction-input":
|
|
174
|
+
return FractionInputSubmissionSchema;
|
|
173
175
|
}
|
|
174
176
|
const exhaustiveCheck = pciId;
|
|
175
177
|
return exhaustiveCheck;
|
|
@@ -331,23 +333,29 @@ function validateSubmissionForInteraction(interaction, submission) {
|
|
|
331
333
|
function submissionValidationMessage(result) {
|
|
332
334
|
return result.issues.join("; ");
|
|
333
335
|
}
|
|
336
|
+
// src/subject.ts
|
|
337
|
+
var SUBJECTS = ["math", "vocabulary", "science"];
|
|
334
338
|
// src/client/create.ts
|
|
335
339
|
import * as errors10 from "@superbuilders/errors";
|
|
336
340
|
|
|
337
341
|
// src/client/transport.ts
|
|
338
342
|
import * as errors2 from "@superbuilders/errors";
|
|
343
|
+
|
|
344
|
+
// src/version.ts
|
|
345
|
+
var SDK_VERSION = "2.2.0";
|
|
346
|
+
var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
|
|
347
|
+
|
|
348
|
+
// src/client/transport.ts
|
|
339
349
|
var ADVANCE_PATH = "/api/v0/advance";
|
|
340
|
-
function
|
|
341
|
-
if (
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
if ("error" in value && value.error !== undefined && typeof value.error !== "string") {
|
|
345
|
-
return false;
|
|
350
|
+
function readStringField(value, key) {
|
|
351
|
+
if (!(key in value)) {
|
|
352
|
+
return;
|
|
346
353
|
}
|
|
347
|
-
|
|
348
|
-
|
|
354
|
+
const v = Reflect.get(value, key);
|
|
355
|
+
if (typeof v !== "string") {
|
|
356
|
+
return;
|
|
349
357
|
}
|
|
350
|
-
return
|
|
358
|
+
return v;
|
|
351
359
|
}
|
|
352
360
|
function parseAdvanceErrorBody(body) {
|
|
353
361
|
if (body.length === 0) {
|
|
@@ -359,13 +367,39 @@ function parseAdvanceErrorBody(body) {
|
|
|
359
367
|
if (parsed.error) {
|
|
360
368
|
return null;
|
|
361
369
|
}
|
|
362
|
-
|
|
370
|
+
const raw = parsed.data;
|
|
371
|
+
if (typeof raw !== "object" || raw === null) {
|
|
363
372
|
return null;
|
|
364
373
|
}
|
|
365
|
-
|
|
374
|
+
const result = {};
|
|
375
|
+
const error = readStringField(raw, "error");
|
|
376
|
+
if (error !== undefined) {
|
|
377
|
+
result.error = error;
|
|
378
|
+
}
|
|
379
|
+
const detail = readStringField(raw, "detail");
|
|
380
|
+
if (detail !== undefined) {
|
|
381
|
+
result.detail = detail;
|
|
382
|
+
}
|
|
383
|
+
const minimumSdkVersion = readStringField(raw, "minimumSdkVersion");
|
|
384
|
+
if (minimumSdkVersion !== undefined) {
|
|
385
|
+
result.minimumSdkVersion = minimumSdkVersion;
|
|
386
|
+
}
|
|
387
|
+
const receivedSdkVersion = readStringField(raw, "receivedSdkVersion");
|
|
388
|
+
if (receivedSdkVersion !== undefined) {
|
|
389
|
+
result.receivedSdkVersion = receivedSdkVersion;
|
|
390
|
+
}
|
|
391
|
+
const upgradeUrl = readStringField(raw, "upgradeUrl");
|
|
392
|
+
if (upgradeUrl !== undefined) {
|
|
393
|
+
result.upgradeUrl = upgradeUrl;
|
|
394
|
+
}
|
|
395
|
+
return result;
|
|
366
396
|
}
|
|
367
397
|
function httpSentinel(status, body) {
|
|
368
398
|
if (status === 400) {
|
|
399
|
+
const parsed = parseAdvanceErrorBody(body);
|
|
400
|
+
if (parsed?.error === "sdk_upgrade_required") {
|
|
401
|
+
return ErrSdkUpgradeRequired;
|
|
402
|
+
}
|
|
369
403
|
return ErrBadRequest;
|
|
370
404
|
}
|
|
371
405
|
if (status === 401) {
|
|
@@ -384,9 +418,6 @@ function httpSentinel(status, body) {
|
|
|
384
418
|
if (status === 409) {
|
|
385
419
|
return ErrConflict;
|
|
386
420
|
}
|
|
387
|
-
if (status === 412) {
|
|
388
|
-
return ErrNeedsHints;
|
|
389
|
-
}
|
|
390
421
|
if (status === 429) {
|
|
391
422
|
return ErrRateLimited;
|
|
392
423
|
}
|
|
@@ -395,6 +426,29 @@ function httpSentinel(status, body) {
|
|
|
395
426
|
}
|
|
396
427
|
return ErrServerError;
|
|
397
428
|
}
|
|
429
|
+
function buildSdkUpgradeRequiredError(sentinel, text, logger) {
|
|
430
|
+
const parsed = parseAdvanceErrorBody(text);
|
|
431
|
+
if (parsed === null) {
|
|
432
|
+
logger.error("sdk upgrade required", {
|
|
433
|
+
receivedSdkVersion: null,
|
|
434
|
+
minimumSdkVersion: "<unknown>",
|
|
435
|
+
upgradeUrl: NPM_PACKAGE_URL
|
|
436
|
+
});
|
|
437
|
+
const message2 = `<missing> < <unknown>; bump @superbuilders/primer-tives at ${NPM_PACKAGE_URL}`;
|
|
438
|
+
return errors2.wrap(sentinel, message2);
|
|
439
|
+
}
|
|
440
|
+
const minimumSdkVersion = parsed.minimumSdkVersion === undefined ? "<unknown>" : parsed.minimumSdkVersion;
|
|
441
|
+
const receivedSdkVersion = parsed.receivedSdkVersion === undefined ? null : parsed.receivedSdkVersion;
|
|
442
|
+
const receivedDisplay = receivedSdkVersion === null ? "<missing>" : receivedSdkVersion;
|
|
443
|
+
const upgradeUrl = parsed.upgradeUrl === undefined ? NPM_PACKAGE_URL : parsed.upgradeUrl;
|
|
444
|
+
logger.error("sdk upgrade required", {
|
|
445
|
+
receivedSdkVersion,
|
|
446
|
+
minimumSdkVersion,
|
|
447
|
+
upgradeUrl
|
|
448
|
+
});
|
|
449
|
+
const message = `${receivedDisplay} < ${minimumSdkVersion}; bump @superbuilders/primer-tives at ${upgradeUrl}`;
|
|
450
|
+
return errors2.wrap(sentinel, message);
|
|
451
|
+
}
|
|
398
452
|
function isAbortError(err) {
|
|
399
453
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
400
454
|
return true;
|
|
@@ -406,7 +460,7 @@ function isAbortError(err) {
|
|
|
406
460
|
}
|
|
407
461
|
function createTransport(tc) {
|
|
408
462
|
const fetchFn = tc.fetch ? tc.fetch : globalThis.fetch;
|
|
409
|
-
const
|
|
463
|
+
const logger = tc.logger;
|
|
410
464
|
function transportSignal() {
|
|
411
465
|
if (tc.abort) {
|
|
412
466
|
return tc.abort.signal;
|
|
@@ -414,7 +468,7 @@ function createTransport(tc) {
|
|
|
414
468
|
return;
|
|
415
469
|
}
|
|
416
470
|
async function transport(body) {
|
|
417
|
-
|
|
471
|
+
logger.debug("transport request", {
|
|
418
472
|
intentKind: body.intent.kind,
|
|
419
473
|
subject: body.subject
|
|
420
474
|
});
|
|
@@ -423,7 +477,8 @@ function createTransport(tc) {
|
|
|
423
477
|
method: "POST",
|
|
424
478
|
headers: {
|
|
425
479
|
"Content-Type": "application/json",
|
|
426
|
-
Authorization: `Bearer ${tc.accessToken}
|
|
480
|
+
Authorization: `Bearer ${tc.accessToken}`,
|
|
481
|
+
"X-Primer-SDK-Version": SDK_VERSION
|
|
427
482
|
},
|
|
428
483
|
body: JSON.stringify(body),
|
|
429
484
|
signal: transportSignal()
|
|
@@ -434,12 +489,12 @@ function createTransport(tc) {
|
|
|
434
489
|
});
|
|
435
490
|
if (!fetchResult.ok) {
|
|
436
491
|
if (isAbortError(fetchResult.error)) {
|
|
437
|
-
|
|
492
|
+
logger.error("transport timeout", {
|
|
438
493
|
intentKind: body.intent.kind
|
|
439
494
|
});
|
|
440
495
|
return { ok: false, error: errors2.wrap(ErrTimeout, fetchResult.error.message) };
|
|
441
496
|
}
|
|
442
|
-
|
|
497
|
+
logger.error("transport network error", {
|
|
443
498
|
error: fetchResult.error
|
|
444
499
|
});
|
|
445
500
|
return { ok: false, error: errors2.wrap(ErrNetwork, fetchResult.error.message) };
|
|
@@ -449,8 +504,11 @@ function createTransport(tc) {
|
|
|
449
504
|
const text = await res.text().catch(function fallback() {
|
|
450
505
|
return "";
|
|
451
506
|
});
|
|
452
|
-
const sentinel =
|
|
453
|
-
|
|
507
|
+
const sentinel = httpSentinel(res.status, text);
|
|
508
|
+
if (errors2.is(sentinel, ErrSdkUpgradeRequired)) {
|
|
509
|
+
return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
|
|
510
|
+
}
|
|
511
|
+
logger.error("transport http error", {
|
|
454
512
|
status: res.status
|
|
455
513
|
});
|
|
456
514
|
return { ok: false, error: errors2.wrap(sentinel, text) };
|
|
@@ -461,12 +519,12 @@ function createTransport(tc) {
|
|
|
461
519
|
return { ok: false, error: err };
|
|
462
520
|
});
|
|
463
521
|
if (!jsonResult.ok) {
|
|
464
|
-
|
|
522
|
+
logger.error("transport json parse failed", {
|
|
465
523
|
error: jsonResult.error
|
|
466
524
|
});
|
|
467
525
|
return { ok: false, error: errors2.wrap(ErrJsonParse, jsonResult.error.message) };
|
|
468
526
|
}
|
|
469
|
-
|
|
527
|
+
logger.debug("transport success", {
|
|
470
528
|
intentKind: body.intent.kind
|
|
471
529
|
});
|
|
472
530
|
return { ok: true, data: jsonResult.data };
|
|
@@ -484,7 +542,7 @@ function poisonToJSON() {
|
|
|
484
542
|
|
|
485
543
|
// src/client/choice-state.ts
|
|
486
544
|
import * as errors3 from "@superbuilders/errors";
|
|
487
|
-
function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices) {
|
|
545
|
+
function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minChoices) {
|
|
488
546
|
let submitPending;
|
|
489
547
|
let submitKey;
|
|
490
548
|
let timeoutPending;
|
|
@@ -502,7 +560,7 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
|
|
|
502
560
|
}
|
|
503
561
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
504
562
|
if (!validation.ok) {
|
|
505
|
-
ctx.
|
|
563
|
+
ctx.logger.error("choice submit invalid", { selectedKeys, issues: validation.issues });
|
|
506
564
|
return Promise.resolve(ctx.errored(errors3.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
507
565
|
}
|
|
508
566
|
submitKey = key;
|
|
@@ -528,6 +586,7 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
|
|
|
528
586
|
return {
|
|
529
587
|
phase: "interaction",
|
|
530
588
|
kind: "choice",
|
|
589
|
+
body,
|
|
531
590
|
stimulus,
|
|
532
591
|
interaction,
|
|
533
592
|
options,
|
|
@@ -541,7 +600,7 @@ function choiceState(ctx, stimulus, interaction, options, maxChoices, minChoices
|
|
|
541
600
|
|
|
542
601
|
// src/client/extended-text-state.ts
|
|
543
602
|
import * as errors4 from "@superbuilders/errors";
|
|
544
|
-
function extendedTextState(ctx, stimulus, interaction) {
|
|
603
|
+
function extendedTextState(ctx, body, stimulus, interaction) {
|
|
545
604
|
if (interaction.cardinality === "single") {
|
|
546
605
|
let submitText = function(value) {
|
|
547
606
|
const submission = { type: "extended-text", values: [value] };
|
|
@@ -557,7 +616,7 @@ function extendedTextState(ctx, stimulus, interaction) {
|
|
|
557
616
|
}
|
|
558
617
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
559
618
|
if (!validation.ok) {
|
|
560
|
-
ctx.
|
|
619
|
+
ctx.logger.error("extended-text submit invalid", { value, issues: validation.issues });
|
|
561
620
|
return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
562
621
|
}
|
|
563
622
|
submitKey2 = key;
|
|
@@ -586,6 +645,7 @@ function extendedTextState(ctx, stimulus, interaction) {
|
|
|
586
645
|
phase: "interaction",
|
|
587
646
|
kind: "extended-text",
|
|
588
647
|
cardinality: "single",
|
|
648
|
+
body,
|
|
589
649
|
stimulus,
|
|
590
650
|
interaction,
|
|
591
651
|
submitText,
|
|
@@ -611,7 +671,7 @@ function extendedTextState(ctx, stimulus, interaction) {
|
|
|
611
671
|
}
|
|
612
672
|
const validation = validateSubmissionForInteraction(multi, submission);
|
|
613
673
|
if (!validation.ok) {
|
|
614
|
-
ctx.
|
|
674
|
+
ctx.logger.error("extended-text submit invalid", { values, issues: validation.issues });
|
|
615
675
|
return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
616
676
|
}
|
|
617
677
|
submitKey = key;
|
|
@@ -638,6 +698,7 @@ function extendedTextState(ctx, stimulus, interaction) {
|
|
|
638
698
|
phase: "interaction",
|
|
639
699
|
kind: "extended-text",
|
|
640
700
|
cardinality: "multiple",
|
|
701
|
+
body,
|
|
641
702
|
stimulus,
|
|
642
703
|
interaction: multi,
|
|
643
704
|
maxStrings: multi.maxStrings,
|
|
@@ -649,10 +710,11 @@ function extendedTextState(ctx, stimulus, interaction) {
|
|
|
649
710
|
}
|
|
650
711
|
|
|
651
712
|
// src/client/feedback-state.ts
|
|
652
|
-
function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedbackContent, review) {
|
|
713
|
+
function feedbackState(ctx, body, stimulus, interaction, submission, isCorrect, feedbackContent, review) {
|
|
653
714
|
let pending;
|
|
654
715
|
return {
|
|
655
716
|
phase: "feedback",
|
|
717
|
+
body,
|
|
656
718
|
stimulus,
|
|
657
719
|
interaction,
|
|
658
720
|
submission,
|
|
@@ -672,7 +734,7 @@ function feedbackState(ctx, stimulus, interaction, submission, isCorrect, feedba
|
|
|
672
734
|
|
|
673
735
|
// src/client/match-state.ts
|
|
674
736
|
import * as errors5 from "@superbuilders/errors";
|
|
675
|
-
function matchState(ctx, stimulus, interaction) {
|
|
737
|
+
function matchState(ctx, body, stimulus, interaction) {
|
|
676
738
|
let submitPending;
|
|
677
739
|
let submitKey;
|
|
678
740
|
let timeoutPending;
|
|
@@ -690,7 +752,7 @@ function matchState(ctx, stimulus, interaction) {
|
|
|
690
752
|
}
|
|
691
753
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
692
754
|
if (!validation.ok) {
|
|
693
|
-
ctx.
|
|
755
|
+
ctx.logger.error("match submit invalid", { pairs, issues: validation.issues });
|
|
694
756
|
return Promise.resolve(ctx.errored(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
695
757
|
}
|
|
696
758
|
submitKey = key;
|
|
@@ -716,6 +778,7 @@ function matchState(ctx, stimulus, interaction) {
|
|
|
716
778
|
return {
|
|
717
779
|
phase: "interaction",
|
|
718
780
|
kind: "match",
|
|
781
|
+
body,
|
|
719
782
|
stimulus,
|
|
720
783
|
interaction,
|
|
721
784
|
sourceChoices: interaction.sourceChoices,
|
|
@@ -729,10 +792,11 @@ function matchState(ctx, stimulus, interaction) {
|
|
|
729
792
|
}
|
|
730
793
|
|
|
731
794
|
// src/client/observation-state.ts
|
|
732
|
-
function observationState(ctx, stimulus) {
|
|
795
|
+
function observationState(ctx, body, stimulus) {
|
|
733
796
|
let pending;
|
|
734
797
|
return {
|
|
735
798
|
phase: "observation",
|
|
799
|
+
body,
|
|
736
800
|
stimulus,
|
|
737
801
|
advance: function advance() {
|
|
738
802
|
if (pending) {
|
|
@@ -747,7 +811,7 @@ function observationState(ctx, stimulus) {
|
|
|
747
811
|
|
|
748
812
|
// src/client/order-state.ts
|
|
749
813
|
import * as errors6 from "@superbuilders/errors";
|
|
750
|
-
function orderState(ctx, stimulus, interaction) {
|
|
814
|
+
function orderState(ctx, body, stimulus, interaction) {
|
|
751
815
|
let submitPending;
|
|
752
816
|
let submitKey;
|
|
753
817
|
let timeoutPending;
|
|
@@ -765,7 +829,7 @@ function orderState(ctx, stimulus, interaction) {
|
|
|
765
829
|
}
|
|
766
830
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
767
831
|
if (!validation.ok) {
|
|
768
|
-
ctx.
|
|
832
|
+
ctx.logger.error("order submit invalid", { orderedKeys, issues: validation.issues });
|
|
769
833
|
return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
770
834
|
}
|
|
771
835
|
submitKey = key;
|
|
@@ -791,6 +855,7 @@ function orderState(ctx, stimulus, interaction) {
|
|
|
791
855
|
return {
|
|
792
856
|
phase: "interaction",
|
|
793
857
|
kind: "order",
|
|
858
|
+
body,
|
|
794
859
|
stimulus,
|
|
795
860
|
interaction,
|
|
796
861
|
choices: interaction.choices,
|
|
@@ -804,7 +869,7 @@ function orderState(ctx, stimulus, interaction) {
|
|
|
804
869
|
|
|
805
870
|
// src/client/pci-state.ts
|
|
806
871
|
import * as errors7 from "@superbuilders/errors";
|
|
807
|
-
function pciInteractionState(ctx, stimulus, interaction) {
|
|
872
|
+
function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
808
873
|
let submitPending;
|
|
809
874
|
let submitKey;
|
|
810
875
|
let timeoutPending;
|
|
@@ -821,7 +886,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
|
|
|
821
886
|
}
|
|
822
887
|
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
823
888
|
}
|
|
824
|
-
ctx.
|
|
889
|
+
ctx.logger.debug("pci submit", { pciId });
|
|
825
890
|
submitKey = key;
|
|
826
891
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
827
892
|
submitPending = undefined;
|
|
@@ -837,7 +902,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
|
|
|
837
902
|
if (timeoutPending) {
|
|
838
903
|
return timeoutPending;
|
|
839
904
|
}
|
|
840
|
-
ctx.
|
|
905
|
+
ctx.logger.debug("pci timeout", { pciId });
|
|
841
906
|
timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
|
|
842
907
|
timeoutPending = undefined;
|
|
843
908
|
});
|
|
@@ -846,6 +911,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
|
|
|
846
911
|
return {
|
|
847
912
|
phase: "interaction",
|
|
848
913
|
kind: "portable-custom",
|
|
914
|
+
body,
|
|
849
915
|
stimulus,
|
|
850
916
|
interaction,
|
|
851
917
|
pciId,
|
|
@@ -858,7 +924,7 @@ function pciInteractionState(ctx, stimulus, interaction) {
|
|
|
858
924
|
|
|
859
925
|
// src/client/text-entry-state.ts
|
|
860
926
|
import * as errors8 from "@superbuilders/errors";
|
|
861
|
-
function textEntryState(ctx, stimulus, interaction) {
|
|
927
|
+
function textEntryState(ctx, body, stimulus, interaction) {
|
|
862
928
|
let submitPending;
|
|
863
929
|
let submitKey;
|
|
864
930
|
let timeoutPending;
|
|
@@ -876,7 +942,7 @@ function textEntryState(ctx, stimulus, interaction) {
|
|
|
876
942
|
}
|
|
877
943
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
878
944
|
if (!validation.ok) {
|
|
879
|
-
ctx.
|
|
945
|
+
ctx.logger.error("text-entry submit invalid", { value, issues: validation.issues });
|
|
880
946
|
return Promise.resolve(ctx.errored(errors8.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
881
947
|
}
|
|
882
948
|
submitKey = key;
|
|
@@ -902,6 +968,7 @@ function textEntryState(ctx, stimulus, interaction) {
|
|
|
902
968
|
return {
|
|
903
969
|
phase: "interaction",
|
|
904
970
|
kind: "text-entry",
|
|
971
|
+
body,
|
|
905
972
|
stimulus,
|
|
906
973
|
interaction,
|
|
907
974
|
submitText,
|
|
@@ -917,7 +984,7 @@ var FATAL_SENTINELS = [
|
|
|
917
984
|
ErrTokenExpired,
|
|
918
985
|
ErrForbidden,
|
|
919
986
|
ErrNotFound,
|
|
920
|
-
|
|
987
|
+
ErrSdkUpgradeRequired,
|
|
921
988
|
ErrUnsupportedPci
|
|
922
989
|
];
|
|
923
990
|
function isFatalError(err) {
|
|
@@ -932,13 +999,24 @@ function isRetriableError(err) {
|
|
|
932
999
|
return !errors9.is(err, ErrInvalidSubmission);
|
|
933
1000
|
}
|
|
934
1001
|
function makeSession(sc) {
|
|
935
|
-
const
|
|
1002
|
+
const logger = sc.logger;
|
|
936
1003
|
function resolve(result) {
|
|
937
1004
|
switch (result.outcome) {
|
|
938
1005
|
case "advanced":
|
|
939
|
-
return fromAdvanced(result.stimulus, result.interaction);
|
|
940
|
-
case "submitted":
|
|
941
|
-
|
|
1006
|
+
return fromAdvanced(result.frame.body, result.frame.stimulus, result.frame.interaction);
|
|
1007
|
+
case "submitted": {
|
|
1008
|
+
const interaction = result.frame.interaction;
|
|
1009
|
+
if (interaction === null) {
|
|
1010
|
+
logger.error("submitted result without interaction");
|
|
1011
|
+
return {
|
|
1012
|
+
phase: "fatal",
|
|
1013
|
+
error: errors9.wrap(ErrBadRequest, "submitted result missing interaction"),
|
|
1014
|
+
retriable: false,
|
|
1015
|
+
toJSON: poisonToJSON
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
return feedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
|
|
1019
|
+
}
|
|
942
1020
|
case "completed":
|
|
943
1021
|
return { phase: "completed", toJSON: poisonToJSON };
|
|
944
1022
|
}
|
|
@@ -952,13 +1030,13 @@ function makeSession(sc) {
|
|
|
952
1030
|
retriable,
|
|
953
1031
|
retry: function retry() {
|
|
954
1032
|
if (!retriable) {
|
|
955
|
-
|
|
1033
|
+
logger.debug("retry ignored for non-retriable error", { failedPhase });
|
|
956
1034
|
return Promise.resolve(state);
|
|
957
1035
|
}
|
|
958
1036
|
if (pending) {
|
|
959
1037
|
return pending;
|
|
960
1038
|
}
|
|
961
|
-
|
|
1039
|
+
logger.debug("retrying from errored state", { failedPhase });
|
|
962
1040
|
pending = execute(intent, failedPhase);
|
|
963
1041
|
return pending;
|
|
964
1042
|
},
|
|
@@ -968,14 +1046,13 @@ function makeSession(sc) {
|
|
|
968
1046
|
}
|
|
969
1047
|
async function execute(intent, phase) {
|
|
970
1048
|
const body = {
|
|
971
|
-
|
|
972
|
-
intent
|
|
973
|
-
subject: sc.subject
|
|
1049
|
+
subject: sc.subject,
|
|
1050
|
+
intent
|
|
974
1051
|
};
|
|
975
1052
|
const result = await sc.transport(body);
|
|
976
1053
|
if (!result.ok) {
|
|
977
1054
|
if (isFatalError(result.error)) {
|
|
978
|
-
|
|
1055
|
+
logger.error("fatal transport error", { error: result.error, phase });
|
|
979
1056
|
return { phase: "fatal", error: result.error, retriable: false, toJSON: poisonToJSON };
|
|
980
1057
|
}
|
|
981
1058
|
return errored(result.error, phase, intent);
|
|
@@ -990,13 +1067,13 @@ function makeSession(sc) {
|
|
|
990
1067
|
}
|
|
991
1068
|
return false;
|
|
992
1069
|
}
|
|
993
|
-
function fromAdvanced(stimulus, interaction) {
|
|
1070
|
+
function fromAdvanced(body, stimulus, interaction) {
|
|
994
1071
|
if (interaction === null) {
|
|
995
|
-
return observationState(ctx, stimulus);
|
|
1072
|
+
return observationState(ctx, body, stimulus);
|
|
996
1073
|
}
|
|
997
1074
|
if (interaction.type === "portable-custom") {
|
|
998
1075
|
if (!isPciSupported(interaction.pciId)) {
|
|
999
|
-
|
|
1076
|
+
logger.error("unsupported pci in frame", { pciId: interaction.pciId });
|
|
1000
1077
|
return {
|
|
1001
1078
|
phase: "fatal",
|
|
1002
1079
|
error: errors9.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
|
|
@@ -1005,28 +1082,59 @@ function makeSession(sc) {
|
|
|
1005
1082
|
};
|
|
1006
1083
|
}
|
|
1007
1084
|
}
|
|
1008
|
-
return pendingInteractionState(stimulus, interaction);
|
|
1085
|
+
return pendingInteractionState(body, stimulus, interaction);
|
|
1009
1086
|
}
|
|
1010
|
-
function pendingInteractionState(stimulus, interaction) {
|
|
1087
|
+
function pendingInteractionState(body, stimulus, interaction) {
|
|
1011
1088
|
switch (interaction.type) {
|
|
1012
1089
|
case "choice":
|
|
1013
|
-
return choiceState(ctx, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices);
|
|
1090
|
+
return choiceState(ctx, body, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices);
|
|
1014
1091
|
case "text-entry":
|
|
1015
|
-
return textEntryState(ctx, stimulus, interaction);
|
|
1092
|
+
return textEntryState(ctx, body, stimulus, interaction);
|
|
1016
1093
|
case "extended-text":
|
|
1017
|
-
return extendedTextState(ctx, stimulus, interaction);
|
|
1094
|
+
return extendedTextState(ctx, body, stimulus, interaction);
|
|
1018
1095
|
case "order":
|
|
1019
|
-
return orderState(ctx, stimulus, interaction);
|
|
1096
|
+
return orderState(ctx, body, stimulus, interaction);
|
|
1020
1097
|
case "match":
|
|
1021
|
-
return matchState(ctx, stimulus, interaction);
|
|
1098
|
+
return matchState(ctx, body, stimulus, interaction);
|
|
1022
1099
|
case "portable-custom":
|
|
1023
|
-
return pciInteractionState(ctx, stimulus, interaction);
|
|
1100
|
+
return pciInteractionState(ctx, body, stimulus, interaction);
|
|
1024
1101
|
}
|
|
1025
1102
|
}
|
|
1026
|
-
const ctx = {
|
|
1103
|
+
const ctx = { logger, execute, errored };
|
|
1027
1104
|
return { execute };
|
|
1028
1105
|
}
|
|
1029
1106
|
|
|
1107
|
+
// src/subject-pcis.ts
|
|
1108
|
+
var REQUIRED_PCIS_BY_SUBJECT = {
|
|
1109
|
+
math: ["urn:primer:pci:fraction-input"],
|
|
1110
|
+
vocabulary: [],
|
|
1111
|
+
science: []
|
|
1112
|
+
};
|
|
1113
|
+
function requiredPcisForSubject(subject) {
|
|
1114
|
+
if (subject === "all") {
|
|
1115
|
+
const all = [];
|
|
1116
|
+
for (const s of SUBJECTS) {
|
|
1117
|
+
for (const pci of REQUIRED_PCIS_BY_SUBJECT[s]) {
|
|
1118
|
+
if (!all.includes(pci)) {
|
|
1119
|
+
all.push(pci);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return all;
|
|
1124
|
+
}
|
|
1125
|
+
return REQUIRED_PCIS_BY_SUBJECT[subject];
|
|
1126
|
+
}
|
|
1127
|
+
function missingPcisForSubject(subject, provided) {
|
|
1128
|
+
const required = requiredPcisForSubject(subject);
|
|
1129
|
+
const missing = [];
|
|
1130
|
+
for (const pci of required) {
|
|
1131
|
+
if (provided === undefined || !provided.includes(pci)) {
|
|
1132
|
+
missing.push(pci);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return missing;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1030
1138
|
// src/client/create.ts
|
|
1031
1139
|
var ACCESS_TOKEN_PREFIX = "eyJ";
|
|
1032
1140
|
function isMalformedJws(token) {
|
|
@@ -1037,26 +1145,35 @@ function isMalformedJws(token) {
|
|
|
1037
1145
|
return dotCount !== 2;
|
|
1038
1146
|
}
|
|
1039
1147
|
function create(config) {
|
|
1040
|
-
const
|
|
1148
|
+
const logger = config.logger;
|
|
1149
|
+
let supportedPcis = [];
|
|
1150
|
+
if (config.supportedPcis !== undefined) {
|
|
1151
|
+
supportedPcis = config.supportedPcis;
|
|
1152
|
+
}
|
|
1153
|
+
const missingPcis = missingPcisForSubject(config.subject, supportedPcis);
|
|
1154
|
+
if (missingPcis.length > 0) {
|
|
1155
|
+
logger.error("renderer missing required pcis", { subject: config.subject, missingPcis });
|
|
1156
|
+
throw errors10.wrap(ErrMissingRequiredPci, `subject '${config.subject}'`);
|
|
1157
|
+
}
|
|
1041
1158
|
if (isMalformedJws(config.accessToken)) {
|
|
1042
|
-
|
|
1159
|
+
logger.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
|
|
1043
1160
|
throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
|
|
1044
1161
|
}
|
|
1045
1162
|
const transport = createTransport({
|
|
1046
1163
|
accessToken: config.accessToken,
|
|
1047
|
-
|
|
1164
|
+
subject: config.subject,
|
|
1048
1165
|
origin: config.origin,
|
|
1049
1166
|
fetch: config.fetch,
|
|
1050
1167
|
abort: config.abort,
|
|
1051
|
-
|
|
1168
|
+
logger
|
|
1052
1169
|
});
|
|
1053
1170
|
let startPromise;
|
|
1054
1171
|
async function doStart() {
|
|
1055
|
-
|
|
1172
|
+
logger.debug("start");
|
|
1056
1173
|
const s = makeSession({
|
|
1057
|
-
supportedPcis: config.supportedPcis,
|
|
1058
1174
|
subject: config.subject,
|
|
1059
|
-
|
|
1175
|
+
supportedPcis,
|
|
1176
|
+
logger,
|
|
1060
1177
|
transport
|
|
1061
1178
|
});
|
|
1062
1179
|
return s.execute({ kind: "observation" }, "observation");
|
|
@@ -1064,7 +1181,7 @@ function create(config) {
|
|
|
1064
1181
|
return {
|
|
1065
1182
|
start() {
|
|
1066
1183
|
if (startPromise) {
|
|
1067
|
-
|
|
1184
|
+
logger.debug("start already called");
|
|
1068
1185
|
return startPromise;
|
|
1069
1186
|
}
|
|
1070
1187
|
startPromise = doStart();
|
|
@@ -1076,4 +1193,4 @@ export {
|
|
|
1076
1193
|
create
|
|
1077
1194
|
};
|
|
1078
1195
|
|
|
1079
|
-
//# debugId=
|
|
1196
|
+
//# debugId=81BED1C3D30435C964756E2164756E21
|