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