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