@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.
Files changed (39) hide show
  1. package/README.md +742 -540
  2. package/dist/client/create.d.ts +22 -6
  3. package/dist/client/create.d.ts.map +1 -1
  4. package/dist/client/index.js +116 -54
  5. package/dist/client/index.js.map +14 -11
  6. package/dist/client/session.d.ts +1 -1
  7. package/dist/client/session.d.ts.map +1 -1
  8. package/dist/client/transport.d.ts +4 -5
  9. package/dist/client/transport.d.ts.map +1 -1
  10. package/dist/contracts/index.d.ts +3 -2
  11. package/dist/contracts/index.d.ts.map +1 -1
  12. package/dist/contracts/index.js +42 -36
  13. package/dist/contracts/index.js.map +6 -5
  14. package/dist/contracts/pci-schemas.d.ts +24 -20
  15. package/dist/contracts/pci-schemas.d.ts.map +1 -1
  16. package/dist/contracts/pci.d.ts +26 -27
  17. package/dist/contracts/pci.d.ts.map +1 -1
  18. package/dist/contracts/validation.d.ts +34 -23
  19. package/dist/contracts/validation.d.ts.map +1 -1
  20. package/dist/errors.d.ts +2 -6
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +3 -11
  23. package/dist/errors.js.map +3 -3
  24. package/dist/server/create-server.d.ts +4 -33
  25. package/dist/server/create-server.d.ts.map +1 -1
  26. package/dist/server/exchange.d.ts +8 -14
  27. package/dist/server/exchange.d.ts.map +1 -1
  28. package/dist/server/index.d.ts +1 -3
  29. package/dist/server/index.d.ts.map +1 -1
  30. package/dist/server/index.js +70 -415
  31. package/dist/server/index.js.map +6 -9
  32. package/dist/subject-pcis.d.ts +13 -0
  33. package/dist/subject-pcis.d.ts.map +1 -0
  34. package/dist/version.d.ts +1 -1
  35. package/package.json +5 -1
  36. package/dist/server/hints.d.ts +0 -25
  37. package/dist/server/hints.d.ts.map +0 -1
  38. package/dist/server/students.d.ts +0 -12
  39. 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,EACN,KAAK,YAAY,EACjB,KAAK,eAAe,EAGpB,MAAM,6CAA6C,CAAA;AAEpD,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAEzB,MAAM,0CAA0C,CAAA;AAGjD,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;;;;;;;;OAQG;IACH,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAEhC;;;;;;OAMG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAExF;;;;OAIG;IACH,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAEvE;;;;OAIG;IACH,qCAAqC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAClF;AAED,iBAAS,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAepE;AAED,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
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 ExchangeConfig {
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
- interface SessionToken {
10
- readonly accessToken: string;
11
- readonly expiresInSeconds: number;
12
- }
13
- interface TimebackSession {
14
- readonly studentId: string;
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":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAKtE,UAAU,cAAc;IACvB,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,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CACjC;AAED,UAAU,eAAe;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CACjC;AA8OD,iBAAe,eAAe,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAmB/F;AAED,iBAAe,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC,CAmB1B;AAED,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,CAAA"}
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"}
@@ -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;AAEzD,YAAY,EACX,YAAY,EACZ,eAAe,EACf,MAAM,6CAA6C,CAAA;AAEpD,YAAY,EACX,cAAc,EACd,oBAAoB,EACpB,MAAM,0CAA0C,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"}
@@ -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 STUDENT_EXCHANGE_PATH = "/api/v0/auth/exchange";
31
- var TIMEBACK_EXCHANGE_PATH = "/api/v0/auth/exchange/timeback";
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 isFiniteNumber(value) {
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 parseSessionTokenSuccessBody(data) {
45
- if (!isObjectRecord(data)) {
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
- if (!isFiniteNumber(data.expires_in)) {
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.scope !== "string") {
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: data.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 mapBadRequest(body) {
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 mapBadRequest(body);
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 parsed = await res.json().then(function ok(data) {
112
- return { ok: true, data };
113
- }, function fail() {
114
- return { ok: false };
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 (!parsed.ok) {
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
- return toExchangeErrorBody(parsed.data);
82
+ const body = toTokenErrorBody(parsed.data);
83
+ return { ...body, rawText, contentType: contentType === null ? undefined : contentType };
120
84
  }
121
- async function sendExchangeRequest(config, path, body, logContext) {
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}${path}`;
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
- "Content-Type": "application/json",
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("exchange timeout", { path, ...logContext });
111
+ logger.error("token request timeout");
142
112
  throw errors2.wrap(ErrTimeout, fetchResult.error.message);
143
113
  }
144
- logger.error("exchange network error", { path, ...logContext, error: fetchResult.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 parseSessionTokenSuccess(config, res, logContext) {
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("exchange json parse failed", { ...logContext, error: jsonResult.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 = 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");
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("exchange success", {
169
- ...logContext,
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 parseTimebackSessionSuccess(config, res, sourcedId) {
138
+ async function getToken(config, input) {
178
139
  const logger = config.logger;
179
- const jsonResult = await res.json().then(function ok(data) {
180
- return { ok: true, data };
181
- }, function fail(err) {
182
- return { ok: false, error: err };
140
+ logger.debug("token request", {
141
+ origin: config.origin,
142
+ verifiedEmail: input.verifiedEmail,
143
+ hasCustomHeaders: config.headers !== undefined
183
144
  });
184
- if (!jsonResult.ok) {
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, 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);
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("set student hints http 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 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);
156
+ throw errors2.wrap(sentinel, detail);
493
157
  }
494
- return parseStudentIdSuccess(config, res);
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
- createStudent() {
501
- return createStudent(config);
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=ED126AFAB7F1033A64756E2164756E21
173
+ //# debugId=33FD60F3187C394264756E2164756E21