@superbuilders/primer-tives 2.0.0 → 2.2.1
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 +116 -54
- 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 +4 -33
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/exchange.d.ts +8 -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 +70 -415
- 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,OAAO,CAAC,EAAE,WAAW,CAAA;IAC9B,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,16 @@
|
|
|
1
1
|
import type { PrimerLogger } from "../logger";
|
|
2
|
-
interface
|
|
2
|
+
interface TokenConfig {
|
|
3
3
|
readonly origin: string;
|
|
4
4
|
readonly secretKey: string;
|
|
5
|
+
readonly headers?: HeadersInit;
|
|
5
6
|
readonly fetch?: typeof globalThis.fetch;
|
|
6
7
|
readonly abort?: AbortController;
|
|
7
8
|
readonly logger: PrimerLogger;
|
|
8
9
|
}
|
|
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 };
|
|
10
|
+
type GetTokenInput = {
|
|
11
|
+
readonly verifiedEmail: string;
|
|
12
|
+
};
|
|
13
|
+
declare function getToken(config: TokenConfig, input: GetTokenInput): Promise<string>;
|
|
14
|
+
export type { GetTokenInput, TokenConfig };
|
|
15
|
+
export { getToken };
|
|
22
16
|
//# 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,OAAO,CAAC,EAAE,WAAW,CAAA;IAC9B,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;AAsJD,iBAAe,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBlF;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,124 +13,93 @@ 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
|
|
24
|
+
var TOKEN_PATH = "/api/v0/sessions";
|
|
25
|
+
var MAX_LOGGED_BODY_CHARS = 2000;
|
|
32
26
|
function isObjectRecord(value) {
|
|
33
27
|
return typeof value === "object" && value !== null;
|
|
34
28
|
}
|
|
35
|
-
function
|
|
36
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
37
|
-
}
|
|
38
|
-
function toExchangeErrorBody(data) {
|
|
29
|
+
function toTokenErrorBody(data) {
|
|
39
30
|
if (!isObjectRecord(data)) {
|
|
40
31
|
return {};
|
|
41
32
|
}
|
|
42
33
|
return typeof data.error === "string" ? { error: data.error } : {};
|
|
43
34
|
}
|
|
44
|
-
function
|
|
45
|
-
if (
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (typeof data.access_token !== "string") {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (data.token_type !== "Bearer") {
|
|
52
|
-
return;
|
|
35
|
+
function truncateBody(text) {
|
|
36
|
+
if (text.length <= MAX_LOGGED_BODY_CHARS) {
|
|
37
|
+
return text;
|
|
53
38
|
}
|
|
54
|
-
|
|
39
|
+
return text.slice(0, MAX_LOGGED_BODY_CHARS);
|
|
40
|
+
}
|
|
41
|
+
function parseTokenSuccessBody(data) {
|
|
42
|
+
if (!isObjectRecord(data)) {
|
|
55
43
|
return;
|
|
56
44
|
}
|
|
57
|
-
if (typeof data.
|
|
45
|
+
if (typeof data.access_token !== "string" || data.token_type !== "Bearer") {
|
|
58
46
|
return;
|
|
59
47
|
}
|
|
60
48
|
return {
|
|
61
49
|
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
|
|
50
|
+
token_type: "Bearer"
|
|
78
51
|
};
|
|
79
52
|
}
|
|
80
53
|
function isAbortError(err) {
|
|
81
54
|
return err.name === "AbortError" || err.name === "TimeoutError";
|
|
82
55
|
}
|
|
83
|
-
function
|
|
84
|
-
if (body.error === "unsupported_grade") {
|
|
85
|
-
return ErrUnsupportedGrade;
|
|
86
|
-
}
|
|
87
|
-
return ErrBadRequest;
|
|
88
|
-
}
|
|
89
|
-
function httpSentinel(status, body) {
|
|
56
|
+
function httpSentinel(status) {
|
|
90
57
|
if (status === 400) {
|
|
91
|
-
return
|
|
58
|
+
return ErrBadRequest;
|
|
92
59
|
}
|
|
93
60
|
if (status === 401) {
|
|
94
61
|
return ErrInvalidSecretKey;
|
|
95
62
|
}
|
|
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
63
|
return ErrServerError;
|
|
109
64
|
}
|
|
110
65
|
async function readErrorBody(res) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
66
|
+
const contentType = res.headers.get("content-type");
|
|
67
|
+
const textResult = await res.text().then(function ok(text) {
|
|
68
|
+
return { ok: true, text };
|
|
69
|
+
}, function fail(err) {
|
|
70
|
+
return { ok: false, error: err };
|
|
115
71
|
});
|
|
116
|
-
if (!
|
|
117
|
-
return {};
|
|
72
|
+
if (!textResult.ok) {
|
|
73
|
+
return { contentType: contentType === null ? undefined : contentType };
|
|
74
|
+
}
|
|
75
|
+
const rawText = truncateBody(textResult.text);
|
|
76
|
+
const parsed = errors2.trySync(function parseJson() {
|
|
77
|
+
return JSON.parse(textResult.text);
|
|
78
|
+
});
|
|
79
|
+
if (parsed.error) {
|
|
80
|
+
return { rawText, contentType: contentType === null ? undefined : contentType };
|
|
118
81
|
}
|
|
119
|
-
|
|
82
|
+
const body = toTokenErrorBody(parsed.data);
|
|
83
|
+
return { ...body, rawText, contentType: contentType === null ? undefined : contentType };
|
|
120
84
|
}
|
|
121
|
-
async function
|
|
85
|
+
async function sendTokenRequest(config, input) {
|
|
122
86
|
const logger = config.logger;
|
|
123
87
|
const fetchFn = config.fetch ? config.fetch : globalThis.fetch;
|
|
124
88
|
const signal = config.abort ? config.abort.signal : undefined;
|
|
125
|
-
const url = `${config.origin}${
|
|
89
|
+
const url = `${config.origin}${TOKEN_PATH}`;
|
|
90
|
+
const headers = new Headers(config.headers);
|
|
91
|
+
headers.set("Content-Type", "application/json");
|
|
92
|
+
headers.set("Authorization", `Bearer ${config.secretKey}`);
|
|
93
|
+
const hasProtectionBypass = headers.has("x-vercel-protection-bypass");
|
|
94
|
+
logger.debug("token request dispatch", {
|
|
95
|
+
url,
|
|
96
|
+
hasProtectionBypass,
|
|
97
|
+
hasAbortSignal: signal !== undefined
|
|
98
|
+
});
|
|
126
99
|
const fetchResult = await fetchFn(url, {
|
|
127
100
|
method: "POST",
|
|
128
|
-
headers
|
|
129
|
-
|
|
130
|
-
Authorization: `Bearer ${config.secretKey}`
|
|
131
|
-
},
|
|
132
|
-
body: JSON.stringify(body),
|
|
101
|
+
headers,
|
|
102
|
+
body: JSON.stringify({ verified_email: input.verifiedEmail }),
|
|
133
103
|
signal
|
|
134
104
|
}).then(function ok(response) {
|
|
135
105
|
return { ok: true, response };
|
|
@@ -138,15 +108,15 @@ async function sendExchangeRequest(config, path, body, logContext) {
|
|
|
138
108
|
});
|
|
139
109
|
if (!fetchResult.ok) {
|
|
140
110
|
if (isAbortError(fetchResult.error)) {
|
|
141
|
-
logger.error("
|
|
111
|
+
logger.error("token request timeout");
|
|
142
112
|
throw errors2.wrap(ErrTimeout, fetchResult.error.message);
|
|
143
113
|
}
|
|
144
|
-
logger.error("
|
|
114
|
+
logger.error("token request network error", { error: fetchResult.error });
|
|
145
115
|
throw errors2.wrap(ErrNetwork, fetchResult.error.message);
|
|
146
116
|
}
|
|
147
117
|
return fetchResult.response;
|
|
148
118
|
}
|
|
149
|
-
async function
|
|
119
|
+
async function parseToken(config, res) {
|
|
150
120
|
const logger = config.logger;
|
|
151
121
|
const jsonResult = await res.json().then(function ok(data) {
|
|
152
122
|
return { ok: true, data };
|
|
@@ -154,360 +124,45 @@ async function parseSessionTokenSuccess(config, res, logContext) {
|
|
|
154
124
|
return { ok: false, error: err };
|
|
155
125
|
});
|
|
156
126
|
if (!jsonResult.ok) {
|
|
157
|
-
logger.error("
|
|
127
|
+
logger.error("token response json parse failed", { error: jsonResult.error });
|
|
158
128
|
throw errors2.wrap(ErrJsonParse, jsonResult.error.message);
|
|
159
129
|
}
|
|
160
|
-
const body =
|
|
161
|
-
if (
|
|
162
|
-
logger.error("
|
|
163
|
-
|
|
164
|
-
body: jsonResult.data
|
|
165
|
-
});
|
|
166
|
-
throw errors2.wrap(ErrJsonParse, "exchange success body had invalid shape");
|
|
130
|
+
const body = parseTokenSuccessBody(jsonResult.data);
|
|
131
|
+
if (body === undefined) {
|
|
132
|
+
logger.error("token response had invalid shape", { body: jsonResult.data });
|
|
133
|
+
throw errors2.wrap(ErrJsonParse, "token response had invalid shape");
|
|
167
134
|
}
|
|
168
|
-
logger.debug("
|
|
169
|
-
|
|
170
|
-
expiresIn: body.expires_in
|
|
171
|
-
});
|
|
172
|
-
return {
|
|
173
|
-
accessToken: body.access_token,
|
|
174
|
-
expiresInSeconds: body.expires_in
|
|
175
|
-
};
|
|
135
|
+
logger.debug("token request success");
|
|
136
|
+
return body.access_token;
|
|
176
137
|
}
|
|
177
|
-
async function
|
|
138
|
+
async function getToken(config, input) {
|
|
178
139
|
const logger = config.logger;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
140
|
+
logger.debug("token request", {
|
|
141
|
+
origin: config.origin,
|
|
142
|
+
verifiedEmail: input.verifiedEmail,
|
|
143
|
+
hasCustomHeaders: config.headers !== undefined
|
|
183
144
|
});
|
|
184
|
-
|
|
185
|
-
logger.error("timeback exchange json parse failed", { sourcedId, error: jsonResult.error });
|
|
186
|
-
throw errors2.wrap(ErrJsonParse, jsonResult.error.message);
|
|
187
|
-
}
|
|
188
|
-
const body = parseTimebackExchangeSuccessBody(jsonResult.data);
|
|
189
|
-
if (!body) {
|
|
190
|
-
logger.error("timeback exchange success body had invalid shape", {
|
|
191
|
-
sourcedId,
|
|
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" });
|
|
145
|
+
const res = await sendTokenRequest(config, input);
|
|
211
146
|
if (!res.ok) {
|
|
212
147
|
const body = await readErrorBody(res);
|
|
213
|
-
const sentinel = httpSentinel(res.status
|
|
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);
|
|
217
|
-
}
|
|
218
|
-
return parseSessionTokenSuccess(config, res, { studentId, exchangeKind: "student_id" });
|
|
219
|
-
}
|
|
220
|
-
async function exchangeTimebackStudent(config, sourcedId) {
|
|
221
|
-
const logger = config.logger;
|
|
222
|
-
logger.debug("exchange timeback student request", { sourcedId });
|
|
223
|
-
const res = await sendExchangeRequest(config, TIMEBACK_EXCHANGE_PATH, { sourced_id: sourcedId }, { sourcedId, exchangeKind: "timeback" });
|
|
224
|
-
if (!res.ok) {
|
|
225
|
-
const body = await readErrorBody(res);
|
|
226
|
-
const sentinel = httpSentinel(res.status, body);
|
|
227
|
-
const detail = body.error ? `${res.status} ${body.error}` : `${res.status}`;
|
|
228
|
-
logger.error("exchange timeback student http error", { status: res.status, body, sourcedId });
|
|
229
|
-
throw errors2.wrap(sentinel, detail);
|
|
230
|
-
}
|
|
231
|
-
return parseTimebackSessionSuccess(config, res, sourcedId);
|
|
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);
|
|
148
|
+
const sentinel = httpSentinel(res.status);
|
|
368
149
|
const detail = body.error ? `${res.status} ${body.error}` : `${res.status}`;
|
|
369
|
-
logger.error("
|
|
370
|
-
studentId,
|
|
150
|
+
logger.error("token request http error", {
|
|
371
151
|
status: res.status,
|
|
372
|
-
body
|
|
152
|
+
body,
|
|
153
|
+
origin: config.origin,
|
|
154
|
+
verifiedEmail: input.verifiedEmail
|
|
373
155
|
});
|
|
374
|
-
throw
|
|
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);
|
|
156
|
+
throw errors2.wrap(sentinel, detail);
|
|
493
157
|
}
|
|
494
|
-
return
|
|
158
|
+
return parseToken(config, res);
|
|
495
159
|
}
|
|
496
160
|
|
|
497
161
|
// src/server/create-server.ts
|
|
498
162
|
function createPrimerServer(config) {
|
|
499
163
|
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);
|
|
164
|
+
getToken(input) {
|
|
165
|
+
return getToken(config, input);
|
|
511
166
|
}
|
|
512
167
|
};
|
|
513
168
|
}
|
|
@@ -515,4 +170,4 @@ export {
|
|
|
515
170
|
createPrimerServer
|
|
516
171
|
};
|
|
517
172
|
|
|
518
|
-
//# debugId=
|
|
173
|
+
//# debugId=33FD60F3187C394264756E2164756E21
|