@superbuilders/primer-tives 3.6.0 → 3.7.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 +4 -13
- package/dist/client/auth/browser.d.ts +1 -4
- package/dist/client/auth/browser.d.ts.map +1 -1
- package/dist/client/auth/hosted-popup.d.ts.map +1 -1
- package/dist/client/auth/provider.d.ts.map +1 -1
- package/dist/client/index.js +175 -163
- package/dist/client/index.js.map +9 -10
- package/dist/client/start.d.ts +1 -1
- package/dist/client/start.d.ts.map +1 -1
- package/dist/client/transport.d.ts.map +1 -1
- package/dist/contracts/index.js +39 -1
- package/dist/contracts/index.js.map +2 -2
- package/dist/errors.js +39 -1
- package/dist/errors.js.map +2 -2
- package/dist/grade-level.js +39 -1
- package/dist/grade-level.js.map +2 -2
- package/dist/subject-pcis.js +39 -1
- package/dist/subject-pcis.js.map +2 -2
- package/dist/subject.js +39 -1
- package/dist/subject.js.map +2 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ bun add @superbuilders/primer-tives
|
|
|
17
17
|
|
|
18
18
|
## Version
|
|
19
19
|
|
|
20
|
-
The current SDK version is `3.
|
|
20
|
+
The current SDK version is `3.7.1`.
|
|
21
21
|
|
|
22
22
|
## Entrypoints
|
|
23
23
|
|
|
@@ -42,7 +42,6 @@ import * as logger from "@superbuilders/slog"
|
|
|
42
42
|
import { start, type PrimerOptions } from "@superbuilders/primer-tives/client"
|
|
43
43
|
|
|
44
44
|
const options = {
|
|
45
|
-
origin: "https://primerlearn.dev",
|
|
46
45
|
publishableKey: "pk_...",
|
|
47
46
|
subject: "math",
|
|
48
47
|
supportedPcis: ["urn:primer:pci:fraction-input"],
|
|
@@ -56,7 +55,6 @@ If your application already has a learner access token, pass it directly. `start
|
|
|
56
55
|
|
|
57
56
|
```ts
|
|
58
57
|
const options = {
|
|
59
|
-
origin: "https://primerlearn.dev",
|
|
60
58
|
publishableKey: "pk_...",
|
|
61
59
|
accessToken,
|
|
62
60
|
subject: "math",
|
|
@@ -92,7 +90,6 @@ Vocabulary and science currently have no required PCI capabilities, so `supporte
|
|
|
92
90
|
|
|
93
91
|
```ts
|
|
94
92
|
const options = {
|
|
95
|
-
origin: "https://primerlearn.dev",
|
|
96
93
|
publishableKey: "pk_...",
|
|
97
94
|
subject: "vocabulary",
|
|
98
95
|
logger
|
|
@@ -105,7 +102,6 @@ Omitting `subject` means the SDK asks Primer for the all-subject runtime scope.
|
|
|
105
102
|
|
|
106
103
|
```ts
|
|
107
104
|
const options = {
|
|
108
|
-
origin: "https://primerlearn.dev",
|
|
109
105
|
publishableKey: "pk_...",
|
|
110
106
|
supportedPcis: ["urn:primer:pci:fraction-input"],
|
|
111
107
|
logger
|
|
@@ -142,7 +138,6 @@ import { start, type PrimerOptions } from "@superbuilders/primer-tives/client"
|
|
|
142
138
|
import { ErrAuthUnavailable, ErrMalformedAccessToken } from "@superbuilders/primer-tives/errors"
|
|
143
139
|
|
|
144
140
|
const options = {
|
|
145
|
-
origin,
|
|
146
141
|
publishableKey,
|
|
147
142
|
accessToken,
|
|
148
143
|
subject: "math",
|
|
@@ -174,7 +169,6 @@ if (state.phase === "fatal") {
|
|
|
174
169
|
|
|
175
170
|
```ts
|
|
176
171
|
type PrimerOptions<S extends Subject | undefined = undefined, Supported extends readonly PciId[] = []> = {
|
|
177
|
-
readonly origin: string
|
|
178
172
|
readonly publishableKey: string
|
|
179
173
|
readonly accessToken?: string
|
|
180
174
|
readonly subject?: S
|
|
@@ -187,7 +181,6 @@ type PrimerOptions<S extends Subject | undefined = undefined, Supported extends
|
|
|
187
181
|
|
|
188
182
|
| Field | Required | Meaning |
|
|
189
183
|
| --- | --- | --- |
|
|
190
|
-
| `origin` | Yes | Primer deployment origin. |
|
|
191
184
|
| `publishableKey` | Yes | Public key identifying the Primer frontend your runtime belongs to. |
|
|
192
185
|
| `accessToken` | No | Learner access token. When present, `start` uses it for the learning runtime. |
|
|
193
186
|
| `subject` | No | Public content scope: `"math"`, `"vocabulary"`, or `"science"`. Omitted means all-subject scope. |
|
|
@@ -198,6 +191,8 @@ type PrimerOptions<S extends Subject | undefined = undefined, Supported extends
|
|
|
198
191
|
|
|
199
192
|
The presence or absence of `accessToken` selects startup auth semantics.
|
|
200
193
|
|
|
194
|
+
The SDK uses Primer's production runtime by default.
|
|
195
|
+
|
|
201
196
|
| Shape | Semantics |
|
|
202
197
|
| --- | --- |
|
|
203
198
|
| `accessToken` present | `start` validates the token shape locally and uses it for learning runtime state. |
|
|
@@ -288,7 +283,6 @@ This fails at compile time because math can emit a required PCI:
|
|
|
288
283
|
|
|
289
284
|
```ts
|
|
290
285
|
await start({
|
|
291
|
-
origin,
|
|
292
286
|
publishableKey,
|
|
293
287
|
subject: "math",
|
|
294
288
|
logger
|
|
@@ -299,7 +293,6 @@ This passes:
|
|
|
299
293
|
|
|
300
294
|
```ts
|
|
301
295
|
const options = {
|
|
302
|
-
origin,
|
|
303
296
|
publishableKey,
|
|
304
297
|
subject: "math",
|
|
305
298
|
supportedPcis: ["urn:primer:pci:fraction-input"],
|
|
@@ -1288,7 +1281,6 @@ import * as logger from "@superbuilders/slog"
|
|
|
1288
1281
|
import { start, type PrimerOptions } from "@superbuilders/primer-tives/client"
|
|
1289
1282
|
|
|
1290
1283
|
const options = {
|
|
1291
|
-
origin,
|
|
1292
1284
|
publishableKey,
|
|
1293
1285
|
subject: "vocabulary",
|
|
1294
1286
|
logger
|
|
@@ -1341,7 +1333,6 @@ import { ErrUnsupportedPci } from "@superbuilders/primer-tives/errors"
|
|
|
1341
1333
|
declare const fetchMock: typeof globalThis.fetch
|
|
1342
1334
|
|
|
1343
1335
|
const options = {
|
|
1344
|
-
origin: "https://primer.test",
|
|
1345
1336
|
publishableKey: "pk_test",
|
|
1346
1337
|
accessToken: "eyJ.test.token",
|
|
1347
1338
|
subject: "vocabulary",
|
|
@@ -1388,7 +1379,7 @@ grade level
|
|
|
1388
1379
|
1. Import `start` and `PrimerOptions` from `@superbuilders/primer-tives/client`.
|
|
1389
1380
|
2. Import shared renderer contracts from `@superbuilders/primer-tives/contracts`.
|
|
1390
1381
|
3. Define options with `satisfies PrimerOptions<...>` so subject and PCI requirements stay visible at the declaration site.
|
|
1391
|
-
4. Pass `
|
|
1382
|
+
4. Pass `publishableKey` and `logger` to `start`.
|
|
1392
1383
|
5. Either pass `accessToken` or handle `UnauthenticatedState`.
|
|
1393
1384
|
6. Choose a public `subject`, or omit `subject` for all-subject runtime scope.
|
|
1394
1385
|
7. Declare every required renderer PCI in `supportedPcis`.
|
|
@@ -11,10 +11,7 @@ declare function browserStorage(options: HostedAuthOptions | undefined, logger:
|
|
|
11
11
|
declare function currentUrl(options: HostedAuthOptions | undefined, logger: PrimerLogger): URL;
|
|
12
12
|
declare function redirectUri(options: HostedAuthOptions | undefined, url: URL, logger: PrimerLogger): string;
|
|
13
13
|
declare function randomClientState(logger: PrimerLogger): string;
|
|
14
|
-
declare function clearCallbackHash(options: HostedAuthOptions | undefined, url: URL): void;
|
|
15
14
|
declare function openAuthPopup(url: string, options: HostedAuthOptions | undefined, logger: PrimerLogger): Window;
|
|
16
|
-
|
|
17
|
-
declare function sleep(ms: number): Promise<void>;
|
|
18
|
-
export { browserStorage, clearCallbackHash, currentUrl, openAuthPopup, randomClientState, readablePopupUrl, redirectUri, sleep };
|
|
15
|
+
export { browserStorage, currentUrl, openAuthPopup, randomClientState, redirectUri };
|
|
19
16
|
export type { HostedAuthOptions };
|
|
20
17
|
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/client/auth/browser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/client/auth/browser.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,KAAK,iBAAiB,GAAG;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAChC,CAAA;AAED,iBAAS,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAS7F;AAED,iBAAS,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,GAAG,CAarF;AAED,iBAAS,WAAW,CACnB,OAAO,EAAE,iBAAiB,GAAG,SAAS,EACtC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,YAAY,GAClB,MAAM,CASR;AAED,iBAAS,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAYvD;AAED,iBAAS,aAAa,CACrB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,iBAAiB,GAAG,SAAS,EACtC,MAAM,EAAE,YAAY,GAClB,MAAM,CAmBR;AAED,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAA;AACpF,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-popup.d.ts","sourceRoot":"","sources":["../../../src/client/auth/hosted-popup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hosted-popup.d.ts","sourceRoot":"","sources":["../../../src/client/auth/hosted-popup.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,EAGN,KAAK,iBAAiB,EACtB,MAAM,iDAAiD,CAAA;AAOxD,KAAK,iBAAiB,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B,CAAA;AAwJD,iBAAe,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAU1E;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/client/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/client/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,EAIN,KAAK,iBAAiB,EACtB,MAAM,iDAAiD,CAAA;AAExD,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,sDAAsD,CAAA;AAS7D,KAAK,0BAA0B,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B,CAAA;AAED,KAAK,yBAAyB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;CACzC,CAAA;AAED,KAAK,gCAAgC,GAAG;IACvC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAC5B,CAAA;AAED,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACrB,CAAA;AAED,KAAK,yBAAyB,GAC3B,yBAAyB,GACzB,gCAAgC,GAChC,sBAAsB,CAAA;AAEzB,KAAK,iBAAiB,GAAG,yBAAyB,GAAG,gCAAgC,CAAA;AA4DrF,iBAAS,0BAA0B,CAClC,OAAO,EAAE,0BAA0B,GACjC,yBAAyB,CAe3B;AAED,iBAAe,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqC/F;AAED,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,CAAA"}
|
package/dist/client/index.js
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
|
2
|
+
var __asyncDispose = Symbol.asyncDispose || /* @__PURE__ */ Symbol.for("Symbol.asyncDispose");
|
|
3
|
+
var __using = (stack, value, async) => {
|
|
4
|
+
if (value != null) {
|
|
5
|
+
if (typeof value !== "object" && typeof value !== "function")
|
|
6
|
+
throw TypeError('Object expected to be assigned to "using" declaration');
|
|
7
|
+
var dispose;
|
|
8
|
+
if (async)
|
|
9
|
+
dispose = value[__asyncDispose];
|
|
10
|
+
if (dispose === undefined)
|
|
11
|
+
dispose = value[__dispose];
|
|
12
|
+
if (typeof dispose !== "function")
|
|
13
|
+
throw TypeError("Object not disposable");
|
|
14
|
+
stack.push([async, dispose, value]);
|
|
15
|
+
} else if (async) {
|
|
16
|
+
stack.push([async]);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
var __callDispose = (stack, error, hasError) => {
|
|
21
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
22
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
23
|
+
}, fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e), next = (it) => {
|
|
24
|
+
while (it = stack.pop()) {
|
|
25
|
+
try {
|
|
26
|
+
var result = it[1] && it[1].call(it[2]);
|
|
27
|
+
if (it[0])
|
|
28
|
+
return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
fail(e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (hasError)
|
|
34
|
+
throw error;
|
|
35
|
+
};
|
|
36
|
+
return next();
|
|
37
|
+
};
|
|
38
|
+
|
|
1
39
|
// src/errors.ts
|
|
2
40
|
import * as errors from "@superbuilders/errors";
|
|
3
41
|
var ErrNetwork = errors.new("network");
|
|
@@ -341,7 +379,7 @@ function submissionValidationMessage(result) {
|
|
|
341
379
|
import * as errors2 from "@superbuilders/errors";
|
|
342
380
|
|
|
343
381
|
// src/version.ts
|
|
344
|
-
var SDK_VERSION = "3.
|
|
382
|
+
var SDK_VERSION = "3.7.1";
|
|
345
383
|
var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
|
|
346
384
|
|
|
347
385
|
// src/client/transport.ts
|
|
@@ -474,6 +512,8 @@ function createTransport(tc) {
|
|
|
474
512
|
const url = `${tc.origin}${ADVANCE_PATH}`;
|
|
475
513
|
const fetchResult = await fetchFn(url, {
|
|
476
514
|
method: "POST",
|
|
515
|
+
mode: "cors",
|
|
516
|
+
credentials: "omit",
|
|
477
517
|
headers: {
|
|
478
518
|
"Content-Type": "application/json",
|
|
479
519
|
Authorization: `Bearer ${tc.accessToken.value}`,
|
|
@@ -1134,10 +1174,9 @@ function makeSession(sc) {
|
|
|
1134
1174
|
}
|
|
1135
1175
|
|
|
1136
1176
|
// src/client/auth/provider.ts
|
|
1137
|
-
import * as
|
|
1177
|
+
import * as errors11 from "@superbuilders/errors";
|
|
1138
1178
|
|
|
1139
1179
|
// src/client/auth/browser.ts
|
|
1140
|
-
import * as errors10 from "@superbuilders/errors";
|
|
1141
1180
|
function browserStorage(options, logger) {
|
|
1142
1181
|
if (options !== undefined && options.storage !== undefined) {
|
|
1143
1182
|
return options.storage;
|
|
@@ -1185,16 +1224,6 @@ function randomClientState(logger) {
|
|
|
1185
1224
|
}
|
|
1186
1225
|
return result;
|
|
1187
1226
|
}
|
|
1188
|
-
function clearCallbackHash(options, url) {
|
|
1189
|
-
if (options !== undefined && options.currentUrl !== undefined) {
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
if (typeof globalThis.history === "undefined") {
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
const cleanUrl = `${url.pathname}${url.search}`;
|
|
1196
|
-
globalThis.history.replaceState(globalThis.history.state, "", cleanUrl);
|
|
1197
|
-
}
|
|
1198
1227
|
function openAuthPopup(url, options, logger) {
|
|
1199
1228
|
if (typeof globalThis.open === "undefined") {
|
|
1200
1229
|
logger.error("auth popup api unavailable");
|
|
@@ -1215,67 +1244,12 @@ function openAuthPopup(url, options, logger) {
|
|
|
1215
1244
|
}
|
|
1216
1245
|
return popup;
|
|
1217
1246
|
}
|
|
1218
|
-
function readablePopupUrl(popup) {
|
|
1219
|
-
const result = errors10.trySync(function readLocation() {
|
|
1220
|
-
return popup.location.href;
|
|
1221
|
-
});
|
|
1222
|
-
if (result.error) {
|
|
1223
|
-
return null;
|
|
1224
|
-
}
|
|
1225
|
-
return result.data;
|
|
1226
|
-
}
|
|
1227
|
-
function sleep(ms) {
|
|
1228
|
-
return new Promise(function resolveLater(resolve) {
|
|
1229
|
-
setTimeout(resolve, ms);
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// src/client/auth/callback.ts
|
|
1234
|
-
var ACCESS_TOKEN_HASH_PARAM = "primer_access_token";
|
|
1235
|
-
var AUTH_STATUS_HASH_PARAM = "primer_auth";
|
|
1236
|
-
var AUTH_STATE_HASH_PARAM = "primer_state";
|
|
1237
|
-
var AUTH_SUCCESS = "success";
|
|
1238
|
-
var AUTH_ERROR = "error";
|
|
1239
|
-
function readAuthCallback(url, logger) {
|
|
1240
|
-
if (url.hash.length === 0) {
|
|
1241
|
-
return null;
|
|
1242
|
-
}
|
|
1243
|
-
const hash = new URLSearchParams(url.hash.slice(1));
|
|
1244
|
-
const authStatus = hash.get(AUTH_STATUS_HASH_PARAM);
|
|
1245
|
-
if (authStatus === null) {
|
|
1246
|
-
return null;
|
|
1247
|
-
}
|
|
1248
|
-
if (authStatus === AUTH_ERROR) {
|
|
1249
|
-
logger.error("auth callback returned error");
|
|
1250
|
-
throw ErrAuthCallbackInvalid;
|
|
1251
|
-
}
|
|
1252
|
-
if (authStatus !== AUTH_SUCCESS) {
|
|
1253
|
-
logger.error("auth callback status invalid", { authStatus });
|
|
1254
|
-
throw ErrAuthCallbackInvalid;
|
|
1255
|
-
}
|
|
1256
|
-
const accessToken = hash.get(ACCESS_TOKEN_HASH_PARAM);
|
|
1257
|
-
const state = hash.get(AUTH_STATE_HASH_PARAM);
|
|
1258
|
-
if (accessToken === null || accessToken.length === 0 || state === null || state.length === 0) {
|
|
1259
|
-
logger.error("auth callback missing token or state");
|
|
1260
|
-
throw ErrAuthCallbackInvalid;
|
|
1261
|
-
}
|
|
1262
|
-
return { accessToken, state };
|
|
1263
|
-
}
|
|
1264
|
-
function requireMatchingCallbackState(callback, expectedState, logger) {
|
|
1265
|
-
if (expectedState === null) {
|
|
1266
|
-
logger.error("auth callback expected state missing");
|
|
1267
|
-
throw ErrAuthCallbackInvalid;
|
|
1268
|
-
}
|
|
1269
|
-
if (callback.state !== expectedState) {
|
|
1270
|
-
logger.error("auth callback state mismatch");
|
|
1271
|
-
throw ErrAuthStateMismatch;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
1247
|
|
|
1275
1248
|
// src/client/auth/hosted-popup.ts
|
|
1276
|
-
import * as errors11 from "@superbuilders/errors";
|
|
1277
1249
|
var DEFAULT_POPUP_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1278
1250
|
var POPUP_POLL_MS = 250;
|
|
1251
|
+
var AUTH_MESSAGE_TYPE = "primer-tives.auth.result.v1";
|
|
1252
|
+
var AUTH_RESPONSE_MODE = "web_message";
|
|
1279
1253
|
function hostedAuthUrl(config) {
|
|
1280
1254
|
const logger = config.logger;
|
|
1281
1255
|
if (!URL.canParse(config.origin)) {
|
|
@@ -1286,6 +1260,7 @@ function hostedAuthUrl(config) {
|
|
|
1286
1260
|
authUrl.searchParams.set("publishableKey", config.publishableKey);
|
|
1287
1261
|
authUrl.searchParams.set("redirectUri", redirectUri(config.options, config.currentUrl, logger));
|
|
1288
1262
|
authUrl.searchParams.set("state", config.clientState);
|
|
1263
|
+
authUrl.searchParams.set("responseMode", AUTH_RESPONSE_MODE);
|
|
1289
1264
|
return authUrl.toString();
|
|
1290
1265
|
}
|
|
1291
1266
|
function popupTimeoutMs(options) {
|
|
@@ -1294,62 +1269,132 @@ function popupTimeoutMs(options) {
|
|
|
1294
1269
|
}
|
|
1295
1270
|
return DEFAULT_POPUP_TIMEOUT_MS;
|
|
1296
1271
|
}
|
|
1297
|
-
function
|
|
1298
|
-
|
|
1299
|
-
|
|
1272
|
+
function isRecord(value) {
|
|
1273
|
+
return typeof value === "object" && value !== null;
|
|
1274
|
+
}
|
|
1275
|
+
function stringField(value, key) {
|
|
1276
|
+
const field = value[key];
|
|
1277
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
1300
1278
|
return null;
|
|
1301
1279
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
if (
|
|
1306
|
-
|
|
1307
|
-
throw callbackResult.error;
|
|
1280
|
+
return field;
|
|
1281
|
+
}
|
|
1282
|
+
function readPopupMessage(event, popup, config, expectedOrigin) {
|
|
1283
|
+
if (event.source !== popup) {
|
|
1284
|
+
return { kind: "ignore" };
|
|
1308
1285
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1286
|
+
if (event.origin !== expectedOrigin) {
|
|
1287
|
+
return { kind: "ignore" };
|
|
1288
|
+
}
|
|
1289
|
+
const data = event.data;
|
|
1290
|
+
if (!isRecord(data)) {
|
|
1291
|
+
return { kind: "ignore" };
|
|
1292
|
+
}
|
|
1293
|
+
const messageType = stringField(data, "type");
|
|
1294
|
+
if (messageType !== AUTH_MESSAGE_TYPE) {
|
|
1295
|
+
return { kind: "ignore" };
|
|
1296
|
+
}
|
|
1297
|
+
const state = stringField(data, "state");
|
|
1298
|
+
if (state === null) {
|
|
1299
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
1300
|
+
}
|
|
1301
|
+
if (state !== config.clientState) {
|
|
1302
|
+
return { kind: "error", error: ErrAuthStateMismatch };
|
|
1303
|
+
}
|
|
1304
|
+
const status = stringField(data, "status");
|
|
1305
|
+
if (status === "error") {
|
|
1306
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
1307
|
+
}
|
|
1308
|
+
if (status !== "success") {
|
|
1309
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
1312
1310
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1311
|
+
const accessToken = stringField(data, "accessToken");
|
|
1312
|
+
if (accessToken === null) {
|
|
1313
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
1316
1314
|
}
|
|
1317
|
-
return
|
|
1315
|
+
return { kind: "success", accessToken };
|
|
1318
1316
|
}
|
|
1319
|
-
async function
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
logger.error("hosted auth popup failed", { error: accessTokenResult.error });
|
|
1336
|
-
throw accessTokenResult.error;
|
|
1317
|
+
async function waitForPopupMessage(popup, config, expectedOrigin) {
|
|
1318
|
+
let __stack = [];
|
|
1319
|
+
try {
|
|
1320
|
+
const logger = config.logger;
|
|
1321
|
+
const stack = __using(__stack, new AsyncDisposableStack, 1);
|
|
1322
|
+
stack.defer(function closePopup() {
|
|
1323
|
+
popup.close();
|
|
1324
|
+
});
|
|
1325
|
+
const result = await new Promise(function waitForMessage(resolve, reject) {
|
|
1326
|
+
let settled = false;
|
|
1327
|
+
function finishWithError(error) {
|
|
1328
|
+
if (settled) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
settled = true;
|
|
1332
|
+
reject(error);
|
|
1337
1333
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1334
|
+
function finishWithToken(token) {
|
|
1335
|
+
if (settled) {
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
settled = true;
|
|
1340
1339
|
logger.debug("hosted auth popup completed");
|
|
1341
|
-
|
|
1340
|
+
resolve(token);
|
|
1342
1341
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1342
|
+
const timeoutId = globalThis.setTimeout(function timeout() {
|
|
1343
|
+
logger.error("hosted auth popup timed out");
|
|
1344
|
+
finishWithError(ErrAuthCancelled);
|
|
1345
|
+
}, popupTimeoutMs(config.options));
|
|
1346
|
+
stack.defer(function clearPopupTimeout() {
|
|
1347
|
+
globalThis.clearTimeout(timeoutId);
|
|
1348
|
+
});
|
|
1349
|
+
const closedPollId = globalThis.setInterval(function checkClosed() {
|
|
1350
|
+
if (!popup.closed) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
logger.error("hosted auth popup closed");
|
|
1354
|
+
finishWithError(ErrAuthCancelled);
|
|
1355
|
+
}, POPUP_POLL_MS);
|
|
1356
|
+
stack.defer(function clearClosedPoll() {
|
|
1357
|
+
globalThis.clearInterval(closedPollId);
|
|
1358
|
+
});
|
|
1359
|
+
function handleMessage(event) {
|
|
1360
|
+
const result2 = readPopupMessage(event, popup, config, expectedOrigin);
|
|
1361
|
+
if (result2.kind === "ignore") {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (result2.kind === "error") {
|
|
1365
|
+
logger.error("hosted auth popup failed", { error: result2.error });
|
|
1366
|
+
finishWithError(result2.error);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
finishWithToken(result2.accessToken);
|
|
1370
|
+
}
|
|
1371
|
+
globalThis.addEventListener("message", handleMessage);
|
|
1372
|
+
stack.defer(function removeMessageListener() {
|
|
1373
|
+
globalThis.removeEventListener("message", handleMessage);
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
return result;
|
|
1377
|
+
} catch (_catch) {
|
|
1378
|
+
var _err = _catch, _hasErr = 1;
|
|
1379
|
+
} finally {
|
|
1380
|
+
var _promise = __callDispose(__stack, _err, _hasErr);
|
|
1381
|
+
_promise && await _promise;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function beginHostedPopup(config) {
|
|
1385
|
+
const logger = config.logger;
|
|
1386
|
+
const url = hostedAuthUrl(config);
|
|
1387
|
+
if (!URL.canParse(config.origin)) {
|
|
1388
|
+
logger.error("hosted auth origin invalid", { origin: config.origin });
|
|
1389
|
+
throw ErrAuthCallbackInvalid;
|
|
1345
1390
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1391
|
+
const expectedOrigin = new URL(config.origin).origin;
|
|
1392
|
+
const popup = openAuthPopup(url, config.options, logger);
|
|
1393
|
+
return waitForPopupMessage(popup, config, expectedOrigin);
|
|
1349
1394
|
}
|
|
1350
1395
|
|
|
1351
1396
|
// src/client/auth/access-token.ts
|
|
1352
|
-
import * as
|
|
1397
|
+
import * as errors10 from "@superbuilders/errors";
|
|
1353
1398
|
var ACCESS_TOKEN_PREFIX = "eyJ";
|
|
1354
1399
|
var resolvedAccessTokenBrand = Symbol("primer resolved access token");
|
|
1355
1400
|
function isMalformedJws(token) {
|
|
@@ -1362,7 +1407,7 @@ function isMalformedJws(token) {
|
|
|
1362
1407
|
function resolveAccessToken(token, logger) {
|
|
1363
1408
|
if (isMalformedJws(token)) {
|
|
1364
1409
|
logger.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
|
|
1365
|
-
throw
|
|
1410
|
+
throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
|
|
1366
1411
|
}
|
|
1367
1412
|
return { value: token, [resolvedAccessTokenBrand]: true };
|
|
1368
1413
|
}
|
|
@@ -1389,13 +1434,6 @@ function storeAccessToken(storage, publishableKey, accessToken) {
|
|
|
1389
1434
|
function clearStoredAccessToken(storage, publishableKey) {
|
|
1390
1435
|
storage.removeItem(accessTokenStorageKey(publishableKey));
|
|
1391
1436
|
}
|
|
1392
|
-
function loadAuthState(storage, publishableKey) {
|
|
1393
|
-
const state = storage.getItem(authStateStorageKey(publishableKey));
|
|
1394
|
-
if (state === null || state.length === 0) {
|
|
1395
|
-
return null;
|
|
1396
|
-
}
|
|
1397
|
-
return state;
|
|
1398
|
-
}
|
|
1399
1437
|
function storeAuthState(storage, publishableKey, state) {
|
|
1400
1438
|
storage.setItem(authStateStorageKey(publishableKey), state);
|
|
1401
1439
|
}
|
|
@@ -1405,7 +1443,7 @@ function clearAuthState(storage, publishableKey) {
|
|
|
1405
1443
|
|
|
1406
1444
|
// src/client/auth/provider.ts
|
|
1407
1445
|
function resolveProvidedAccessToken(token, logger) {
|
|
1408
|
-
const result =
|
|
1446
|
+
const result = errors11.trySync(function resolveProvidedToken() {
|
|
1409
1447
|
return resolveAccessToken(token, logger);
|
|
1410
1448
|
});
|
|
1411
1449
|
if (result.error) {
|
|
@@ -1414,7 +1452,7 @@ function resolveProvidedAccessToken(token, logger) {
|
|
|
1414
1452
|
return { kind: "resolved", accessToken: result.data };
|
|
1415
1453
|
}
|
|
1416
1454
|
function resolveManagedAccessToken(token, logger) {
|
|
1417
|
-
const result =
|
|
1455
|
+
const result = errors11.trySync(function resolveManagedToken() {
|
|
1418
1456
|
return resolveAccessToken(token, logger);
|
|
1419
1457
|
});
|
|
1420
1458
|
if (result.error) {
|
|
@@ -1424,7 +1462,7 @@ function resolveManagedAccessToken(token, logger) {
|
|
|
1424
1462
|
}
|
|
1425
1463
|
function readBrowserAuthContext(options) {
|
|
1426
1464
|
const logger = options.logger;
|
|
1427
|
-
const contextResult =
|
|
1465
|
+
const contextResult = errors11.trySync(function readContext() {
|
|
1428
1466
|
const storage = browserStorage(options.hostedAuth, logger);
|
|
1429
1467
|
const url = currentUrl(options.hostedAuth, logger);
|
|
1430
1468
|
return { storage, url };
|
|
@@ -1434,36 +1472,6 @@ function readBrowserAuthContext(options) {
|
|
|
1434
1472
|
}
|
|
1435
1473
|
return contextResult.data;
|
|
1436
1474
|
}
|
|
1437
|
-
function resolveCallbackAccessToken(context, options) {
|
|
1438
|
-
const logger = options.logger;
|
|
1439
|
-
const callbackResult = errors13.trySync(function readCallback() {
|
|
1440
|
-
return readAuthCallback(context.url, logger);
|
|
1441
|
-
});
|
|
1442
|
-
if (callbackResult.error) {
|
|
1443
|
-
clearAuthState(context.storage, options.publishableKey);
|
|
1444
|
-
return { kind: "unauthenticated", error: callbackResult.error };
|
|
1445
|
-
}
|
|
1446
|
-
const callback = callbackResult.data;
|
|
1447
|
-
if (callback === null) {
|
|
1448
|
-
return null;
|
|
1449
|
-
}
|
|
1450
|
-
const stateResult = errors13.trySync(function requireState() {
|
|
1451
|
-
requireMatchingCallbackState(callback, loadAuthState(context.storage, options.publishableKey), logger);
|
|
1452
|
-
});
|
|
1453
|
-
if (stateResult.error) {
|
|
1454
|
-
clearAuthState(context.storage, options.publishableKey);
|
|
1455
|
-
return { kind: "unauthenticated", error: stateResult.error };
|
|
1456
|
-
}
|
|
1457
|
-
const resolved = resolveManagedAccessToken(callback.accessToken, logger);
|
|
1458
|
-
if (resolved.kind === "unauthenticated") {
|
|
1459
|
-
clearAuthState(context.storage, options.publishableKey);
|
|
1460
|
-
return resolved;
|
|
1461
|
-
}
|
|
1462
|
-
storeAccessToken(context.storage, options.publishableKey, callback.accessToken);
|
|
1463
|
-
clearAuthState(context.storage, options.publishableKey);
|
|
1464
|
-
clearCallbackHash(options.hostedAuth, context.url);
|
|
1465
|
-
return resolved;
|
|
1466
|
-
}
|
|
1467
1475
|
function resolveStoredAccessToken(context, options) {
|
|
1468
1476
|
const stored = loadStoredAccessToken(context.storage, options.publishableKey);
|
|
1469
1477
|
if (stored === null) {
|
|
@@ -1483,10 +1491,6 @@ function resolveExistingAccessToken(options) {
|
|
|
1483
1491
|
if ("kind" in context) {
|
|
1484
1492
|
return context;
|
|
1485
1493
|
}
|
|
1486
|
-
const callback = resolveCallbackAccessToken(context, options);
|
|
1487
|
-
if (callback !== null) {
|
|
1488
|
-
return callback;
|
|
1489
|
-
}
|
|
1490
1494
|
const stored = resolveStoredAccessToken(context, options);
|
|
1491
1495
|
if (stored !== null) {
|
|
1492
1496
|
return stored;
|
|
@@ -1499,7 +1503,7 @@ async function beginHostedLogin(options) {
|
|
|
1499
1503
|
if ("kind" in context) {
|
|
1500
1504
|
return context;
|
|
1501
1505
|
}
|
|
1502
|
-
const clientStateResult =
|
|
1506
|
+
const clientStateResult = errors11.trySync(function prepareHostedLogin() {
|
|
1503
1507
|
const clientState2 = randomClientState(logger);
|
|
1504
1508
|
storeAuthState(context.storage, options.publishableKey, clientState2);
|
|
1505
1509
|
return clientState2;
|
|
@@ -1508,7 +1512,7 @@ async function beginHostedLogin(options) {
|
|
|
1508
1512
|
return { kind: "unauthenticated", error: clientStateResult.error };
|
|
1509
1513
|
}
|
|
1510
1514
|
const clientState = clientStateResult.data;
|
|
1511
|
-
const accessTokenResult = await
|
|
1515
|
+
const accessTokenResult = await errors11.try(beginHostedPopup({
|
|
1512
1516
|
origin: options.origin,
|
|
1513
1517
|
publishableKey: options.publishableKey,
|
|
1514
1518
|
currentUrl: context.url,
|
|
@@ -1551,6 +1555,7 @@ function unauthenticatedState(config) {
|
|
|
1551
1555
|
}
|
|
1552
1556
|
|
|
1553
1557
|
// src/client/start.ts
|
|
1558
|
+
var DEFAULT_PRIMER_ORIGIN = "https://primerlearn.dev";
|
|
1554
1559
|
function supportedPcisOrEmpty(supportedPcis) {
|
|
1555
1560
|
if (supportedPcis !== undefined) {
|
|
1556
1561
|
return supportedPcis;
|
|
@@ -1563,6 +1568,12 @@ function runtimeSubject(subject) {
|
|
|
1563
1568
|
}
|
|
1564
1569
|
return subject;
|
|
1565
1570
|
}
|
|
1571
|
+
function primerOrigin(origin) {
|
|
1572
|
+
if (origin !== undefined) {
|
|
1573
|
+
return origin;
|
|
1574
|
+
}
|
|
1575
|
+
return DEFAULT_PRIMER_ORIGIN;
|
|
1576
|
+
}
|
|
1566
1577
|
async function startRuntime(config, accessToken) {
|
|
1567
1578
|
const transport = createTransport({
|
|
1568
1579
|
accessToken,
|
|
@@ -1603,9 +1614,10 @@ async function loginAndStart(config) {
|
|
|
1603
1614
|
async function start(options) {
|
|
1604
1615
|
const logger = options.logger;
|
|
1605
1616
|
logger.debug("start");
|
|
1617
|
+
const origin = primerOrigin(options.origin);
|
|
1606
1618
|
const config = {
|
|
1607
1619
|
publishableKey: options.publishableKey,
|
|
1608
|
-
origin
|
|
1620
|
+
origin,
|
|
1609
1621
|
fetch: options.fetch,
|
|
1610
1622
|
abort: options.abort,
|
|
1611
1623
|
logger,
|
|
@@ -1615,7 +1627,7 @@ async function start(options) {
|
|
|
1615
1627
|
const accessToken = resolveExistingAccessToken({
|
|
1616
1628
|
accessToken: options.accessToken,
|
|
1617
1629
|
publishableKey: options.publishableKey,
|
|
1618
|
-
origin
|
|
1630
|
+
origin,
|
|
1619
1631
|
logger
|
|
1620
1632
|
});
|
|
1621
1633
|
if (accessToken.kind === "fatal") {
|
|
@@ -1635,4 +1647,4 @@ export {
|
|
|
1635
1647
|
start
|
|
1636
1648
|
};
|
|
1637
1649
|
|
|
1638
|
-
//# debugId=
|
|
1650
|
+
//# debugId=F2A9711D50B754AA64756E2164756E21
|