@superbuilders/primer-tives 0.9.0 → 1.1.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 +298 -63
- package/dist/client/choice-state.d.ts.map +1 -1
- package/dist/client/extended-text-state.d.ts.map +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +338 -179
- package/dist/client/index.js.map +13 -11
- package/dist/client/match-state.d.ts.map +1 -1
- package/dist/client/order-state.d.ts.map +1 -1
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/text-entry-state.d.ts.map +1 -1
- package/dist/client/transport.d.ts +1 -1
- package/dist/client/transport.d.ts.map +1 -1
- package/dist/client/types.d.ts +2 -115
- package/dist/client/types.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +4 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +305 -0
- package/dist/contracts/index.js.map +11 -0
- package/dist/contracts/pci-schemas.d.ts +25 -0
- package/dist/contracts/pci-schemas.d.ts.map +1 -0
- package/dist/contracts/types.d.ts +118 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/validation.d.ts +132 -0
- package/dist/contracts/validation.d.ts.map +1 -0
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +48 -0
- package/dist/errors.js.map +10 -0
- package/dist/server/create-server.d.ts +21 -19
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/exchange.d.ts +10 -5
- package/dist/server/exchange.d.ts.map +1 -1
- package/dist/server/hints.d.ts +25 -0
- package/dist/server/hints.d.ts.map +1 -0
- package/dist/server/index.d.ts +4 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +355 -83
- package/dist/server/index.js.map +9 -8
- package/dist/server/students.d.ts +3 -5
- package/dist/server/students.d.ts.map +1 -1
- package/package.json +17 -4
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/server/exchange.ts
|
|
2
|
-
import * as errors2 from "@superbuilders/errors";
|
|
3
|
-
|
|
4
1
|
// src/errors.ts
|
|
5
2
|
import * as errors from "@superbuilders/errors";
|
|
6
3
|
var ErrNetwork = errors.new("network");
|
|
@@ -15,6 +12,7 @@ var ErrTimeout = errors.new("timeout");
|
|
|
15
12
|
var ErrForbidden = errors.new("forbidden");
|
|
16
13
|
var ErrNotFound = errors.new("not found");
|
|
17
14
|
var ErrConflict = errors.new("conflict");
|
|
15
|
+
var ErrExternalAuthorityRequired = errors.new("external authority required");
|
|
18
16
|
var ErrRateLimited = errors.new("rate limited");
|
|
19
17
|
var ErrServiceUnavailable = errors.new("service unavailable");
|
|
20
18
|
var ErrNotSerializable = errors.new("PrimerState is live in-memory state and must not be serialized or stored");
|
|
@@ -23,17 +21,60 @@ var ErrInvalidSecretKey = errors.new("invalid secret key");
|
|
|
23
21
|
var ErrStudentNotFound = errors.new("student not found");
|
|
24
22
|
var ErrUnsupportedGrade = errors.new("unsupported grade");
|
|
25
23
|
var ErrTimebackUnavailable = errors.new("timeback unavailable");
|
|
26
|
-
|
|
27
24
|
// src/server/exchange.ts
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
import * as errors2 from "@superbuilders/errors";
|
|
26
|
+
var STUDENT_EXCHANGE_PATH = "/api/v0/auth/exchange";
|
|
27
|
+
var TIMEBACK_EXCHANGE_PATH = "/api/v0/auth/exchange/timeback";
|
|
28
|
+
function isObjectRecord(value) {
|
|
29
|
+
return typeof value === "object" && value !== null;
|
|
30
|
+
}
|
|
31
|
+
function isFiniteNumber(value) {
|
|
32
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
33
|
+
}
|
|
34
|
+
function toExchangeErrorBody(data) {
|
|
35
|
+
if (!isObjectRecord(data)) {
|
|
36
|
+
return {};
|
|
32
37
|
}
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
return typeof data.error === "string" ? { error: data.error } : {};
|
|
39
|
+
}
|
|
40
|
+
function parseSessionTokenSuccessBody(data) {
|
|
41
|
+
if (!isObjectRecord(data)) {
|
|
42
|
+
return;
|
|
35
43
|
}
|
|
36
|
-
|
|
44
|
+
if (typeof data.access_token !== "string") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (data.token_type !== "Bearer") {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!isFiniteNumber(data.expires_in)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (typeof data.scope !== "string") {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
access_token: data.access_token,
|
|
58
|
+
token_type: data.token_type,
|
|
59
|
+
expires_in: data.expires_in,
|
|
60
|
+
scope: data.scope
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseTimebackExchangeSuccessBody(data) {
|
|
64
|
+
const session = parseSessionTokenSuccessBody(data);
|
|
65
|
+
if (!session || !isObjectRecord(data) || typeof data.student_id !== "string") {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
student_id: data.student_id,
|
|
70
|
+
access_token: session.access_token,
|
|
71
|
+
token_type: session.token_type,
|
|
72
|
+
expires_in: session.expires_in,
|
|
73
|
+
scope: session.scope
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function isAbortError(err) {
|
|
77
|
+
return err.name === "AbortError" || err.name === "TimeoutError";
|
|
37
78
|
}
|
|
38
79
|
function mapBadRequest(body) {
|
|
39
80
|
if (body.error === "unsupported_grade") {
|
|
@@ -51,12 +92,15 @@ function httpSentinel(status, body) {
|
|
|
51
92
|
if (status === 404) {
|
|
52
93
|
return ErrStudentNotFound;
|
|
53
94
|
}
|
|
95
|
+
if (status === 409) {
|
|
96
|
+
if (body.error === "external_authority_required") {
|
|
97
|
+
return ErrExternalAuthorityRequired;
|
|
98
|
+
}
|
|
99
|
+
return ErrConflict;
|
|
100
|
+
}
|
|
54
101
|
if (status === 502) {
|
|
55
102
|
return ErrTimebackUnavailable;
|
|
56
103
|
}
|
|
57
|
-
if (status >= 500 && status < 600) {
|
|
58
|
-
return ErrServerError;
|
|
59
|
-
}
|
|
60
104
|
return ErrServerError;
|
|
61
105
|
}
|
|
62
106
|
async function readErrorBody(res) {
|
|
@@ -68,70 +112,155 @@ async function readErrorBody(res) {
|
|
|
68
112
|
if (!parsed.ok) {
|
|
69
113
|
return {};
|
|
70
114
|
}
|
|
71
|
-
return parsed.data;
|
|
115
|
+
return toExchangeErrorBody(parsed.data);
|
|
72
116
|
}
|
|
73
|
-
async function
|
|
74
|
-
const
|
|
117
|
+
async function sendExchangeRequest(config, path, body, logContext) {
|
|
118
|
+
const logger = config.logger;
|
|
75
119
|
const fetchFn = config.fetch ? config.fetch : globalThis.fetch;
|
|
76
120
|
const signal = config.abort ? config.abort.signal : undefined;
|
|
77
|
-
|
|
78
|
-
const url = `${config.origin}${EXCHANGE_PATH}`;
|
|
121
|
+
const url = `${config.origin}${path}`;
|
|
79
122
|
const fetchResult = await fetchFn(url, {
|
|
80
123
|
method: "POST",
|
|
81
124
|
headers: {
|
|
82
125
|
"Content-Type": "application/json",
|
|
83
126
|
Authorization: `Bearer ${config.secretKey}`
|
|
84
127
|
},
|
|
85
|
-
body: JSON.stringify(
|
|
128
|
+
body: JSON.stringify(body),
|
|
86
129
|
signal
|
|
87
|
-
}).then(function ok(
|
|
88
|
-
return { ok: true, response
|
|
130
|
+
}).then(function ok(response) {
|
|
131
|
+
return { ok: true, response };
|
|
89
132
|
}, function fail(err) {
|
|
90
133
|
return { ok: false, error: err };
|
|
91
134
|
});
|
|
92
135
|
if (!fetchResult.ok) {
|
|
93
136
|
if (isAbortError(fetchResult.error)) {
|
|
94
|
-
|
|
137
|
+
logger.error("exchange timeout", { path, ...logContext });
|
|
95
138
|
throw errors2.wrap(ErrTimeout, fetchResult.error.message);
|
|
96
139
|
}
|
|
97
|
-
|
|
140
|
+
logger.error("exchange network error", { path, ...logContext, error: fetchResult.error });
|
|
98
141
|
throw errors2.wrap(ErrNetwork, fetchResult.error.message);
|
|
99
142
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
143
|
+
return fetchResult.response;
|
|
144
|
+
}
|
|
145
|
+
async function parseSessionTokenSuccess(config, res, logContext) {
|
|
146
|
+
const logger = config.logger;
|
|
147
|
+
const jsonResult = await res.json().then(function ok(data) {
|
|
148
|
+
return { ok: true, data };
|
|
149
|
+
}, function fail(err) {
|
|
150
|
+
return { ok: false, error: err };
|
|
151
|
+
});
|
|
152
|
+
if (!jsonResult.ok) {
|
|
153
|
+
logger.error("exchange json parse failed", { ...logContext, error: jsonResult.error });
|
|
154
|
+
throw errors2.wrap(ErrJsonParse, jsonResult.error.message);
|
|
107
155
|
}
|
|
156
|
+
const body = parseSessionTokenSuccessBody(jsonResult.data);
|
|
157
|
+
if (!body) {
|
|
158
|
+
logger.error("exchange success body had invalid shape", {
|
|
159
|
+
...logContext,
|
|
160
|
+
body: jsonResult.data
|
|
161
|
+
});
|
|
162
|
+
throw errors2.wrap(ErrJsonParse, "exchange success body had invalid shape");
|
|
163
|
+
}
|
|
164
|
+
logger.debug("exchange success", {
|
|
165
|
+
...logContext,
|
|
166
|
+
expiresIn: body.expires_in
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
accessToken: body.access_token,
|
|
170
|
+
expiresInSeconds: body.expires_in
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function parseTimebackSessionSuccess(config, res, sourcedId) {
|
|
174
|
+
const logger = config.logger;
|
|
108
175
|
const jsonResult = await res.json().then(function ok(data) {
|
|
109
176
|
return { ok: true, data };
|
|
110
177
|
}, function fail(err) {
|
|
111
178
|
return { ok: false, error: err };
|
|
112
179
|
});
|
|
113
180
|
if (!jsonResult.ok) {
|
|
114
|
-
|
|
181
|
+
logger.error("timeback exchange json parse failed", { sourcedId, error: jsonResult.error });
|
|
115
182
|
throw errors2.wrap(ErrJsonParse, jsonResult.error.message);
|
|
116
183
|
}
|
|
117
|
-
|
|
184
|
+
const body = parseTimebackExchangeSuccessBody(jsonResult.data);
|
|
185
|
+
if (!body) {
|
|
186
|
+
logger.error("timeback exchange success body had invalid shape", {
|
|
187
|
+
sourcedId,
|
|
188
|
+
body: jsonResult.data
|
|
189
|
+
});
|
|
190
|
+
throw errors2.wrap(ErrJsonParse, "timeback exchange success body had invalid shape");
|
|
191
|
+
}
|
|
192
|
+
logger.debug("timeback exchange success", {
|
|
193
|
+
sourcedId,
|
|
194
|
+
studentId: body.student_id,
|
|
195
|
+
expiresIn: body.expires_in
|
|
196
|
+
});
|
|
118
197
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
198
|
+
studentId: body.student_id,
|
|
199
|
+
accessToken: body.access_token,
|
|
200
|
+
expiresInSeconds: body.expires_in
|
|
121
201
|
};
|
|
122
202
|
}
|
|
203
|
+
async function exchangeStudent(config, studentId) {
|
|
204
|
+
const logger = config.logger;
|
|
205
|
+
logger.debug("exchange student request", { studentId });
|
|
206
|
+
const res = await sendExchangeRequest(config, STUDENT_EXCHANGE_PATH, { student_id: studentId }, { studentId, exchangeKind: "student_id" });
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
const body = await readErrorBody(res);
|
|
209
|
+
const sentinel = httpSentinel(res.status, body);
|
|
210
|
+
const detail = body.error ? `${res.status} ${body.error}` : `${res.status}`;
|
|
211
|
+
logger.error("exchange student http error", { status: res.status, body, studentId });
|
|
212
|
+
throw errors2.wrap(sentinel, detail);
|
|
213
|
+
}
|
|
214
|
+
return parseSessionTokenSuccess(config, res, { studentId, exchangeKind: "student_id" });
|
|
215
|
+
}
|
|
216
|
+
async function exchangeTimebackStudent(config, sourcedId) {
|
|
217
|
+
const logger = config.logger;
|
|
218
|
+
logger.debug("exchange timeback student request", { sourcedId });
|
|
219
|
+
const res = await sendExchangeRequest(config, TIMEBACK_EXCHANGE_PATH, { sourced_id: sourcedId }, { sourcedId, exchangeKind: "timeback" });
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
const body = await readErrorBody(res);
|
|
222
|
+
const sentinel = httpSentinel(res.status, body);
|
|
223
|
+
const detail = body.error ? `${res.status} ${body.error}` : `${res.status}`;
|
|
224
|
+
logger.error("exchange timeback student http error", { status: res.status, body, sourcedId });
|
|
225
|
+
throw errors2.wrap(sentinel, detail);
|
|
226
|
+
}
|
|
227
|
+
return parseTimebackSessionSuccess(config, res, sourcedId);
|
|
228
|
+
}
|
|
123
229
|
|
|
124
|
-
// src/server/
|
|
230
|
+
// src/server/hints.ts
|
|
125
231
|
import * as errors3 from "@superbuilders/errors";
|
|
232
|
+
|
|
233
|
+
// src/grade-level.ts
|
|
234
|
+
var GRADE_LEVELS = ["K", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
|
|
235
|
+
|
|
236
|
+
// src/server/hints.ts
|
|
126
237
|
var STUDENTS_PATH = "/api/v0/students";
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
|
|
238
|
+
function isObjectRecord2(value) {
|
|
239
|
+
return typeof value === "object" && value !== null;
|
|
240
|
+
}
|
|
241
|
+
function toHintsErrorBody(data) {
|
|
242
|
+
if (!isObjectRecord2(data)) {
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
return typeof data.error === "string" ? { error: data.error } : {};
|
|
246
|
+
}
|
|
247
|
+
function parseHintsSuccess(data) {
|
|
248
|
+
if (!isObjectRecord2(data)) {
|
|
249
|
+
return;
|
|
130
250
|
}
|
|
131
|
-
if (
|
|
132
|
-
return
|
|
251
|
+
if (typeof data.student_id !== "string") {
|
|
252
|
+
return;
|
|
133
253
|
}
|
|
134
|
-
|
|
254
|
+
if (data.grade_level !== null && typeof data.grade_level !== "string") {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
student_id: data.student_id,
|
|
259
|
+
grade_level: data.grade_level
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function isAbortError2(err) {
|
|
263
|
+
return err.name === "AbortError" || err.name === "TimeoutError";
|
|
135
264
|
}
|
|
136
265
|
function httpSentinel2(status) {
|
|
137
266
|
if (status === 400) {
|
|
@@ -154,94 +283,235 @@ async function readErrorBody2(res) {
|
|
|
154
283
|
if (!parsed.ok) {
|
|
155
284
|
return {};
|
|
156
285
|
}
|
|
157
|
-
return parsed.data;
|
|
286
|
+
return toHintsErrorBody(parsed.data);
|
|
287
|
+
}
|
|
288
|
+
function buildRequestBody(hints) {
|
|
289
|
+
const body = {};
|
|
290
|
+
if (hints.gradeLevel !== undefined) {
|
|
291
|
+
body.grade_level = hints.gradeLevel;
|
|
292
|
+
}
|
|
293
|
+
return JSON.stringify(body);
|
|
158
294
|
}
|
|
159
|
-
async function
|
|
160
|
-
const
|
|
295
|
+
async function sendSetHintsRequest(config, studentId, hints) {
|
|
296
|
+
const logger = config.logger;
|
|
161
297
|
const fetchFn = config.fetch ? config.fetch : globalThis.fetch;
|
|
162
298
|
const signal = config.abort ? config.abort.signal : undefined;
|
|
163
|
-
const url = `${config.origin}${
|
|
299
|
+
const url = `${config.origin}${STUDENTS_PATH}/${encodeURIComponent(studentId)}/hints`;
|
|
164
300
|
const fetchResult = await fetchFn(url, {
|
|
165
|
-
method,
|
|
301
|
+
method: "PATCH",
|
|
166
302
|
headers: {
|
|
167
|
-
|
|
168
|
-
|
|
303
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
304
|
+
"Content-Type": "application/json"
|
|
169
305
|
},
|
|
170
|
-
body:
|
|
306
|
+
body: buildRequestBody(hints),
|
|
171
307
|
signal
|
|
172
|
-
}).then(function ok(
|
|
173
|
-
return { ok: true, response
|
|
308
|
+
}).then(function ok(response) {
|
|
309
|
+
return { ok: true, response };
|
|
174
310
|
}, function fail(err) {
|
|
175
311
|
return { ok: false, error: err };
|
|
176
312
|
});
|
|
177
313
|
if (!fetchResult.ok) {
|
|
178
314
|
if (isAbortError2(fetchResult.error)) {
|
|
179
|
-
|
|
315
|
+
logger.error("set student hints timeout", { studentId });
|
|
180
316
|
throw errors3.wrap(ErrTimeout, fetchResult.error.message);
|
|
181
317
|
}
|
|
182
|
-
|
|
318
|
+
logger.error("set student hints network error", {
|
|
319
|
+
studentId,
|
|
320
|
+
error: fetchResult.error
|
|
321
|
+
});
|
|
183
322
|
throw errors3.wrap(ErrNetwork, fetchResult.error.message);
|
|
184
323
|
}
|
|
185
324
|
return fetchResult.response;
|
|
186
325
|
}
|
|
187
|
-
async function
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
326
|
+
async function parseHintsSuccessResponse(config, res) {
|
|
327
|
+
const logger = config.logger;
|
|
328
|
+
const jsonResult = await res.json().then(function ok(data) {
|
|
329
|
+
return { ok: true, data };
|
|
330
|
+
}, function fail(err) {
|
|
331
|
+
return { ok: false, error: err };
|
|
192
332
|
});
|
|
333
|
+
if (!jsonResult.ok) {
|
|
334
|
+
logger.error("set hints response parse failed", { error: jsonResult.error });
|
|
335
|
+
throw errors3.wrap(ErrJsonParse, jsonResult.error.message);
|
|
336
|
+
}
|
|
337
|
+
const body = parseHintsSuccess(jsonResult.data);
|
|
338
|
+
if (!body) {
|
|
339
|
+
logger.error("set hints response had invalid shape", { body: jsonResult.data });
|
|
340
|
+
throw errors3.wrap(ErrJsonParse, "set hints success body had invalid shape");
|
|
341
|
+
}
|
|
342
|
+
logger.debug("set hints response success", {
|
|
343
|
+
studentId: body.student_id,
|
|
344
|
+
gradeLevel: body.grade_level
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
studentId: body.student_id,
|
|
348
|
+
gradeLevel: coerceGradeLevel(body.grade_level)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function coerceGradeLevel(value) {
|
|
352
|
+
if (value === null) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
for (const g of GRADE_LEVELS) {
|
|
356
|
+
if (g === value) {
|
|
357
|
+
return g;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
async function setStudentHints(config, studentId, hints) {
|
|
363
|
+
const logger = config.logger;
|
|
364
|
+
logger.debug("set student hints request", { studentId, hints });
|
|
365
|
+
const res = await sendSetHintsRequest(config, studentId, hints);
|
|
193
366
|
if (!res.ok) {
|
|
194
367
|
const body = await readErrorBody2(res);
|
|
195
368
|
const sentinel = httpSentinel2(res.status);
|
|
196
369
|
const detail = body.error ? `${res.status} ${body.error}` : `${res.status}`;
|
|
197
|
-
|
|
370
|
+
logger.error("set student hints http error", {
|
|
371
|
+
studentId,
|
|
372
|
+
status: res.status,
|
|
373
|
+
body
|
|
374
|
+
});
|
|
198
375
|
throw errors3.wrap(sentinel, detail);
|
|
199
376
|
}
|
|
377
|
+
return parseHintsSuccessResponse(config, res);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/server/students.ts
|
|
381
|
+
import * as errors4 from "@superbuilders/errors";
|
|
382
|
+
var STUDENTS_PATH2 = "/api/v0/students";
|
|
383
|
+
function isObjectRecord3(value) {
|
|
384
|
+
return typeof value === "object" && value !== null;
|
|
385
|
+
}
|
|
386
|
+
function toStudentsErrorBody(data) {
|
|
387
|
+
if (!isObjectRecord3(data)) {
|
|
388
|
+
return {};
|
|
389
|
+
}
|
|
390
|
+
return typeof data.error === "string" ? { error: data.error } : {};
|
|
391
|
+
}
|
|
392
|
+
function parseStudentIdSuccessBody(data) {
|
|
393
|
+
if (!isObjectRecord3(data)) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (typeof data.student_id !== "string") {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
return { student_id: data.student_id };
|
|
400
|
+
}
|
|
401
|
+
function isAbortError3(err) {
|
|
402
|
+
return err.name === "AbortError" || err.name === "TimeoutError";
|
|
403
|
+
}
|
|
404
|
+
function httpSentinel3(status) {
|
|
405
|
+
if (status === 400) {
|
|
406
|
+
return ErrBadRequest;
|
|
407
|
+
}
|
|
408
|
+
if (status === 401) {
|
|
409
|
+
return ErrInvalidSecretKey;
|
|
410
|
+
}
|
|
411
|
+
if (status === 409) {
|
|
412
|
+
return ErrConflict;
|
|
413
|
+
}
|
|
414
|
+
return ErrServerError;
|
|
415
|
+
}
|
|
416
|
+
async function readErrorBody3(res) {
|
|
417
|
+
const parsed = await res.json().then(function ok(data) {
|
|
418
|
+
return { ok: true, data };
|
|
419
|
+
}, function fail() {
|
|
420
|
+
return { ok: false };
|
|
421
|
+
});
|
|
422
|
+
if (!parsed.ok) {
|
|
423
|
+
return {};
|
|
424
|
+
}
|
|
425
|
+
return toStudentsErrorBody(parsed.data);
|
|
426
|
+
}
|
|
427
|
+
async function sendCreateStudentRequest(config) {
|
|
428
|
+
const logger = config.logger;
|
|
429
|
+
const fetchFn = config.fetch ? config.fetch : globalThis.fetch;
|
|
430
|
+
const signal = config.abort ? config.abort.signal : undefined;
|
|
431
|
+
const url = `${config.origin}${STUDENTS_PATH2}`;
|
|
432
|
+
const fetchResult = await fetchFn(url, {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: {
|
|
435
|
+
Authorization: `Bearer ${config.secretKey}`
|
|
436
|
+
},
|
|
437
|
+
signal
|
|
438
|
+
}).then(function ok(response) {
|
|
439
|
+
return { ok: true, response };
|
|
440
|
+
}, function fail(err) {
|
|
441
|
+
return { ok: false, error: err };
|
|
442
|
+
});
|
|
443
|
+
if (!fetchResult.ok) {
|
|
444
|
+
if (isAbortError3(fetchResult.error)) {
|
|
445
|
+
logger.error("students timeout", { path: STUDENTS_PATH2 });
|
|
446
|
+
throw errors4.wrap(ErrTimeout, fetchResult.error.message);
|
|
447
|
+
}
|
|
448
|
+
logger.error("students network error", {
|
|
449
|
+
path: STUDENTS_PATH2,
|
|
450
|
+
error: fetchResult.error
|
|
451
|
+
});
|
|
452
|
+
throw errors4.wrap(ErrNetwork, fetchResult.error.message);
|
|
453
|
+
}
|
|
454
|
+
return fetchResult.response;
|
|
455
|
+
}
|
|
456
|
+
async function parseStudentIdSuccess(config, res) {
|
|
457
|
+
const logger = config.logger;
|
|
200
458
|
const jsonResult = await res.json().then(function ok(data) {
|
|
201
459
|
return { ok: true, data };
|
|
202
460
|
}, function fail(err) {
|
|
203
461
|
return { ok: false, error: err };
|
|
204
462
|
});
|
|
205
463
|
if (!jsonResult.ok) {
|
|
206
|
-
|
|
207
|
-
|
|
464
|
+
logger.error("student id response parse failed", {
|
|
465
|
+
operation: "create student",
|
|
466
|
+
error: jsonResult.error
|
|
467
|
+
});
|
|
468
|
+
throw errors4.wrap(ErrJsonParse, jsonResult.error.message);
|
|
469
|
+
}
|
|
470
|
+
const body = parseStudentIdSuccessBody(jsonResult.data);
|
|
471
|
+
if (!body) {
|
|
472
|
+
logger.error("student id response had invalid shape", {
|
|
473
|
+
operation: "create student",
|
|
474
|
+
body: jsonResult.data
|
|
475
|
+
});
|
|
476
|
+
throw errors4.wrap(ErrJsonParse, "create student success body had invalid shape");
|
|
208
477
|
}
|
|
209
|
-
|
|
210
|
-
|
|
478
|
+
logger.debug("student id response success", {
|
|
479
|
+
operation: "create student",
|
|
480
|
+
studentId: body.student_id
|
|
481
|
+
});
|
|
482
|
+
return body.student_id;
|
|
211
483
|
}
|
|
212
|
-
async function
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
const res = await
|
|
484
|
+
async function createStudent(config) {
|
|
485
|
+
const logger = config.logger;
|
|
486
|
+
logger.debug("create student request");
|
|
487
|
+
const res = await sendCreateStudentRequest(config);
|
|
216
488
|
if (!res.ok) {
|
|
217
|
-
const body = await
|
|
218
|
-
const sentinel =
|
|
489
|
+
const body = await readErrorBody3(res);
|
|
490
|
+
const sentinel = httpSentinel3(res.status);
|
|
219
491
|
const detail = body.error ? `${res.status} ${body.error}` : `${res.status}`;
|
|
220
|
-
|
|
221
|
-
throw
|
|
492
|
+
logger.error("create student http error", { status: res.status, body });
|
|
493
|
+
throw errors4.wrap(sentinel, detail);
|
|
222
494
|
}
|
|
223
|
-
|
|
495
|
+
return parseStudentIdSuccess(config, res);
|
|
224
496
|
}
|
|
225
497
|
|
|
226
498
|
// src/server/create-server.ts
|
|
227
499
|
function createPrimerServer(config) {
|
|
228
500
|
return {
|
|
229
|
-
|
|
230
|
-
return createStudent(config
|
|
501
|
+
createStudent() {
|
|
502
|
+
return createStudent(config);
|
|
231
503
|
},
|
|
232
|
-
|
|
233
|
-
return
|
|
504
|
+
setStudentHints(studentId, hints) {
|
|
505
|
+
return setStudentHints(config, studentId, hints);
|
|
234
506
|
},
|
|
235
|
-
|
|
236
|
-
return
|
|
507
|
+
exchangeStudentForAccessToken(studentId) {
|
|
508
|
+
return exchangeStudent(config, studentId);
|
|
237
509
|
},
|
|
238
510
|
exchangeTimebackStudentForAccessToken(sourcedId) {
|
|
239
|
-
return
|
|
511
|
+
return exchangeTimebackStudent(config, sourcedId);
|
|
240
512
|
}
|
|
241
513
|
};
|
|
242
514
|
}
|
|
243
|
-
// src/grade-level.ts
|
|
244
|
-
var GRADE_LEVELS = ["K", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
|
|
245
515
|
export {
|
|
246
516
|
createPrimerServer,
|
|
247
517
|
GRADE_LEVELS,
|
|
@@ -253,7 +523,9 @@ export {
|
|
|
253
523
|
ErrNetwork,
|
|
254
524
|
ErrJsonParse,
|
|
255
525
|
ErrInvalidSecretKey,
|
|
526
|
+
ErrExternalAuthorityRequired,
|
|
527
|
+
ErrConflict,
|
|
256
528
|
ErrBadRequest
|
|
257
529
|
};
|
|
258
530
|
|
|
259
|
-
//# debugId=
|
|
531
|
+
//# debugId=36162738F428CAE464756E2164756E21
|