@superbuilders/primer-tives 4.0.4 → 4.1.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 +42 -6
- package/dist/client/auth/access-token.d.ts +1 -1
- package/dist/client/auth/access-token.d.ts.map +1 -1
- package/dist/client/auth/hosted-popup.d.ts +1 -1
- package/dist/client/auth/hosted-popup.d.ts.map +1 -1
- package/dist/client/auth/provider.d.ts +8 -3
- package/dist/client/auth/provider.d.ts.map +1 -1
- package/dist/client/auth/storage.d.ts +8 -0
- package/dist/client/auth/storage.d.ts.map +1 -0
- package/dist/client/auth-state.d.ts +2 -2
- package/dist/client/auth-state.d.ts.map +1 -1
- package/dist/client/choice-state.d.ts +2 -2
- package/dist/client/choice-state.d.ts.map +1 -1
- package/dist/client/extended-text-state.d.ts +2 -2
- package/dist/client/extended-text-state.d.ts.map +1 -1
- package/dist/client/feedback-state.d.ts +4 -4
- package/dist/client/feedback-state.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +636 -556
- package/dist/client/index.js.map +25 -24
- package/dist/client/match-state.d.ts +2 -2
- package/dist/client/match-state.d.ts.map +1 -1
- package/dist/client/observation-state.d.ts +1 -1
- package/dist/client/observation-state.d.ts.map +1 -1
- package/dist/client/order-state.d.ts +2 -2
- package/dist/client/order-state.d.ts.map +1 -1
- package/dist/client/pci-state.d.ts +2 -2
- package/dist/client/pci-state.d.ts.map +1 -1
- package/dist/client/session-context.d.ts +2 -2
- package/dist/client/session-context.d.ts.map +1 -1
- package/dist/client/session.d.ts +5 -5
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/start.d.ts +3 -3
- package/dist/client/start.d.ts.map +1 -1
- package/dist/client/text-entry-state.d.ts +2 -2
- package/dist/client/text-entry-state.d.ts.map +1 -1
- package/dist/client/transport.d.ts +7 -6
- package/dist/client/transport.d.ts.map +1 -1
- package/dist/client/types.d.ts +8 -2
- package/dist/client/types.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +4 -4
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +12 -12
- package/dist/contracts/index.js.map +6 -6
- package/dist/contracts/pci-schemas.d.ts +4 -0
- package/dist/contracts/pci-schemas.d.ts.map +1 -1
- package/dist/contracts/pci.d.ts +1 -1
- package/dist/contracts/pci.d.ts.map +1 -1
- package/dist/contracts/review.d.ts +1 -1
- package/dist/contracts/review.d.ts.map +1 -1
- package/dist/contracts/validation.d.ts +24 -2
- package/dist/contracts/validation.d.ts.map +1 -1
- package/dist/grade-level.d.ts +1 -1
- package/dist/grade-level.d.ts.map +1 -1
- package/dist/grade-level.js.map +1 -1
- package/dist/subject-pcis.d.ts +1 -1
- package/dist/subject-pcis.d.ts.map +1 -1
- package/dist/subject-pcis.js.map +2 -2
- package/dist/subject.d.ts +1 -1
- package/dist/subject.d.ts.map +1 -1
- package/dist/subject.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/client/index.js
CHANGED
|
@@ -6461,14 +6461,14 @@ var FractionInputSubmissionDraft07Schema = {
|
|
|
6461
6461
|
type: "object",
|
|
6462
6462
|
additionalProperties: false,
|
|
6463
6463
|
required: ["form", "whole"],
|
|
6464
|
-
properties: { form: { const: "whole" }, whole: { type: "string" } }
|
|
6464
|
+
properties: { form: { type: "string", const: "whole" }, whole: { type: "string" } }
|
|
6465
6465
|
},
|
|
6466
6466
|
{
|
|
6467
6467
|
type: "object",
|
|
6468
6468
|
additionalProperties: false,
|
|
6469
6469
|
required: ["form", "numerator", "denominator"],
|
|
6470
6470
|
properties: {
|
|
6471
|
-
form: { const: "proper" },
|
|
6471
|
+
form: { type: "string", const: "proper" },
|
|
6472
6472
|
numerator: { type: "string" },
|
|
6473
6473
|
denominator: { type: "string" }
|
|
6474
6474
|
}
|
|
@@ -6478,7 +6478,7 @@ var FractionInputSubmissionDraft07Schema = {
|
|
|
6478
6478
|
additionalProperties: false,
|
|
6479
6479
|
required: ["form", "numerator", "denominator"],
|
|
6480
6480
|
properties: {
|
|
6481
|
-
form: { const: "improper" },
|
|
6481
|
+
form: { type: "string", const: "improper" },
|
|
6482
6482
|
numerator: { type: "string" },
|
|
6483
6483
|
denominator: { type: "string" }
|
|
6484
6484
|
}
|
|
@@ -6488,7 +6488,7 @@ var FractionInputSubmissionDraft07Schema = {
|
|
|
6488
6488
|
additionalProperties: false,
|
|
6489
6489
|
required: ["form", "whole", "numerator", "denominator"],
|
|
6490
6490
|
properties: {
|
|
6491
|
-
form: { const: "mixed" },
|
|
6491
|
+
form: { type: "string", const: "mixed" },
|
|
6492
6492
|
whole: { type: "string" },
|
|
6493
6493
|
numerator: { type: "string" },
|
|
6494
6494
|
denominator: { type: "string" }
|
|
@@ -6510,7 +6510,7 @@ var ChoiceSubmissionSchema = {
|
|
|
6510
6510
|
additionalProperties: false,
|
|
6511
6511
|
required: ["type", "selectedKeys"],
|
|
6512
6512
|
properties: {
|
|
6513
|
-
type: { const: "choice" },
|
|
6513
|
+
type: { type: "string", const: "choice" },
|
|
6514
6514
|
selectedKeys: { type: "array", items: { type: "string" } }
|
|
6515
6515
|
}
|
|
6516
6516
|
};
|
|
@@ -6518,14 +6518,14 @@ var TextEntrySubmissionSchema = {
|
|
|
6518
6518
|
type: "object",
|
|
6519
6519
|
additionalProperties: false,
|
|
6520
6520
|
required: ["type", "value"],
|
|
6521
|
-
properties: { type: { const: "text-entry" }, value: { type: "string" } }
|
|
6521
|
+
properties: { type: { type: "string", const: "text-entry" }, value: { type: "string" } }
|
|
6522
6522
|
};
|
|
6523
6523
|
var ExtendedTextSubmissionSchema = {
|
|
6524
6524
|
type: "object",
|
|
6525
6525
|
additionalProperties: false,
|
|
6526
6526
|
required: ["type", "values"],
|
|
6527
6527
|
properties: {
|
|
6528
|
-
type: { const: "extended-text" },
|
|
6528
|
+
type: { type: "string", const: "extended-text" },
|
|
6529
6529
|
values: { type: "array", minItems: 1, items: { type: "string" } }
|
|
6530
6530
|
}
|
|
6531
6531
|
};
|
|
@@ -6534,7 +6534,7 @@ var OrderSubmissionSchema = {
|
|
|
6534
6534
|
additionalProperties: false,
|
|
6535
6535
|
required: ["type", "orderedKeys"],
|
|
6536
6536
|
properties: {
|
|
6537
|
-
type: { const: "order" },
|
|
6537
|
+
type: { type: "string", const: "order" },
|
|
6538
6538
|
orderedKeys: { type: "array", items: { type: "string" } }
|
|
6539
6539
|
}
|
|
6540
6540
|
};
|
|
@@ -6543,7 +6543,7 @@ var MatchSubmissionSchema = {
|
|
|
6543
6543
|
additionalProperties: false,
|
|
6544
6544
|
required: ["type", "pairs"],
|
|
6545
6545
|
properties: {
|
|
6546
|
-
type: { const: "match" },
|
|
6546
|
+
type: { type: "string", const: "match" },
|
|
6547
6547
|
pairs: { type: "array", items: MatchPairSchema }
|
|
6548
6548
|
}
|
|
6549
6549
|
};
|
|
@@ -6552,8 +6552,8 @@ var FractionInputPciSubmissionSchema = {
|
|
|
6552
6552
|
additionalProperties: false,
|
|
6553
6553
|
required: ["type", "pciId", "value"],
|
|
6554
6554
|
properties: {
|
|
6555
|
-
type: { const: "portable-custom" },
|
|
6556
|
-
pciId: { const: "urn:primer:pci:fraction-input" },
|
|
6555
|
+
type: { type: "string", const: "portable-custom" },
|
|
6556
|
+
pciId: { type: "string", const: "urn:primer:pci:fraction-input" },
|
|
6557
6557
|
value: FractionInputSubmissionDraft07Schema
|
|
6558
6558
|
}
|
|
6559
6559
|
};
|
|
@@ -6779,216 +6779,434 @@ function validateSubmissionForInteraction(interaction, submission) {
|
|
|
6779
6779
|
function submissionValidationMessage(result) {
|
|
6780
6780
|
return result.issues.join("; ");
|
|
6781
6781
|
}
|
|
6782
|
-
// src/client/
|
|
6783
|
-
import * as
|
|
6784
|
-
|
|
6785
|
-
// src/version.ts
|
|
6786
|
-
var SDK_VERSION = "4.0.4";
|
|
6787
|
-
var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
|
|
6782
|
+
// src/client/auth/provider.ts
|
|
6783
|
+
import * as errors4 from "@superbuilders/errors";
|
|
6788
6784
|
|
|
6789
|
-
// src/client/
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
return;
|
|
6785
|
+
// src/client/auth/access-token.ts
|
|
6786
|
+
import * as errors3 from "@superbuilders/errors";
|
|
6787
|
+
var ACCESS_TOKEN_PREFIX = "eyJ";
|
|
6788
|
+
var TOKEN_EXPIRY_SKEW_MS = 60000;
|
|
6789
|
+
var resolvedAccessTokenBrand = Symbol("primer resolved access token");
|
|
6790
|
+
function isMalformedJws(token) {
|
|
6791
|
+
if (!token.startsWith(ACCESS_TOKEN_PREFIX)) {
|
|
6792
|
+
return true;
|
|
6798
6793
|
}
|
|
6799
|
-
|
|
6794
|
+
const dotCount = token.split(".").length - 1;
|
|
6795
|
+
return dotCount !== 2;
|
|
6800
6796
|
}
|
|
6801
|
-
function
|
|
6802
|
-
|
|
6803
|
-
|
|
6797
|
+
function paddedBase64(base64Url) {
|
|
6798
|
+
const normalized = base64Url.replaceAll("-", "+").replaceAll("_", "/");
|
|
6799
|
+
const remainder = normalized.length % 4;
|
|
6800
|
+
if (remainder === 0) {
|
|
6801
|
+
return normalized;
|
|
6804
6802
|
}
|
|
6805
|
-
|
|
6806
|
-
return
|
|
6803
|
+
if (remainder === 2) {
|
|
6804
|
+
return `${normalized}==`;
|
|
6805
|
+
}
|
|
6806
|
+
if (remainder === 3) {
|
|
6807
|
+
return `${normalized}=`;
|
|
6808
|
+
}
|
|
6809
|
+
throw errors3.wrap(ErrMalformedAccessToken, "payload base64");
|
|
6810
|
+
}
|
|
6811
|
+
function decodeBase64UrlJson(segment) {
|
|
6812
|
+
const decoded = errors3.trySync(function decodePayload() {
|
|
6813
|
+
const json = globalThis.atob(paddedBase64(segment));
|
|
6814
|
+
return JSON.parse(json);
|
|
6807
6815
|
});
|
|
6808
|
-
if (
|
|
6816
|
+
if (decoded.error) {
|
|
6817
|
+
throw errors3.wrap(ErrMalformedAccessToken, "payload decode");
|
|
6818
|
+
}
|
|
6819
|
+
return decoded.data;
|
|
6820
|
+
}
|
|
6821
|
+
function numericClaim(payload, claim) {
|
|
6822
|
+
if (typeof payload !== "object" || payload === null) {
|
|
6809
6823
|
return null;
|
|
6810
6824
|
}
|
|
6811
|
-
const
|
|
6812
|
-
if (typeof
|
|
6825
|
+
const value = Reflect.get(payload, claim);
|
|
6826
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
6813
6827
|
return null;
|
|
6814
6828
|
}
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6829
|
+
return value;
|
|
6830
|
+
}
|
|
6831
|
+
function validateAccessTokenTimestamp(token, logger) {
|
|
6832
|
+
const segments = token.split(".");
|
|
6833
|
+
const payloadSegment = segments[1];
|
|
6834
|
+
if (payloadSegment === undefined || payloadSegment.length === 0) {
|
|
6835
|
+
logger.error("access token payload missing");
|
|
6836
|
+
throw errors3.wrap(ErrMalformedAccessToken, "payload missing");
|
|
6819
6837
|
}
|
|
6820
|
-
const
|
|
6821
|
-
|
|
6822
|
-
|
|
6838
|
+
const payload = decodeBase64UrlJson(payloadSegment);
|
|
6839
|
+
const exp = numericClaim(payload, "exp");
|
|
6840
|
+
if (exp === null) {
|
|
6841
|
+
logger.error("access token exp missing");
|
|
6842
|
+
throw errors3.wrap(ErrMalformedAccessToken, "exp missing");
|
|
6823
6843
|
}
|
|
6824
|
-
const
|
|
6825
|
-
if (
|
|
6826
|
-
|
|
6844
|
+
const expiresAtMs = exp * 1000;
|
|
6845
|
+
if (expiresAtMs <= Date.now() + TOKEN_EXPIRY_SKEW_MS) {
|
|
6846
|
+
logger.error("access token expired");
|
|
6847
|
+
throw ErrTokenExpired;
|
|
6827
6848
|
}
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6849
|
+
}
|
|
6850
|
+
function resolveAccessToken(token, logger) {
|
|
6851
|
+
if (isMalformedJws(token)) {
|
|
6852
|
+
logger.error({ prefix: ACCESS_TOKEN_PREFIX }, "malformed access token");
|
|
6853
|
+
throw errors3.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
|
|
6831
6854
|
}
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6855
|
+
validateAccessTokenTimestamp(token, logger);
|
|
6856
|
+
return { value: token, [resolvedAccessTokenBrand]: true };
|
|
6857
|
+
}
|
|
6858
|
+
|
|
6859
|
+
// src/client/auth/browser.ts
|
|
6860
|
+
var POPUP_TARGET = "primer-auth";
|
|
6861
|
+
var POPUP_FEATURES = "popup,width=480,height=720";
|
|
6862
|
+
function currentUrl(logger) {
|
|
6863
|
+
if (typeof globalThis.location === "undefined") {
|
|
6864
|
+
logger.error("auth location unavailable");
|
|
6865
|
+
throw ErrAuthUnavailable;
|
|
6866
|
+
}
|
|
6867
|
+
return new URL(globalThis.location.href);
|
|
6868
|
+
}
|
|
6869
|
+
function redirectUri(url) {
|
|
6870
|
+
return `${url.origin}${url.pathname}${url.search}`;
|
|
6871
|
+
}
|
|
6872
|
+
function randomClientState(logger) {
|
|
6873
|
+
if (typeof globalThis.crypto === "undefined") {
|
|
6874
|
+
logger.error("auth crypto unavailable");
|
|
6875
|
+
throw ErrAuthUnavailable;
|
|
6876
|
+
}
|
|
6877
|
+
const bytes = new Uint8Array(24);
|
|
6878
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
6879
|
+
let result = "";
|
|
6880
|
+
for (const byte of bytes) {
|
|
6881
|
+
result += byte.toString(16).padStart(2, "0");
|
|
6835
6882
|
}
|
|
6836
6883
|
return result;
|
|
6837
6884
|
}
|
|
6838
|
-
function
|
|
6839
|
-
if (
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
return ErrSdkUpgradeRequired;
|
|
6843
|
-
}
|
|
6844
|
-
return ErrBadRequest;
|
|
6885
|
+
function openAuthPopup(url, logger) {
|
|
6886
|
+
if (typeof globalThis.open === "undefined") {
|
|
6887
|
+
logger.error("auth popup api unavailable");
|
|
6888
|
+
throw ErrAuthUnavailable;
|
|
6845
6889
|
}
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
}
|
|
6851
|
-
return ErrInvalidAccessToken;
|
|
6890
|
+
const popup = globalThis.open(url, POPUP_TARGET, POPUP_FEATURES);
|
|
6891
|
+
if (popup === null) {
|
|
6892
|
+
logger.error("auth popup blocked");
|
|
6893
|
+
throw ErrAuthPopupBlocked;
|
|
6852
6894
|
}
|
|
6853
|
-
|
|
6854
|
-
|
|
6895
|
+
return popup;
|
|
6896
|
+
}
|
|
6897
|
+
|
|
6898
|
+
// src/client/auth/hosted-popup.ts
|
|
6899
|
+
var DEFAULT_POPUP_TIMEOUT_MS = 10 * 60 * 1000;
|
|
6900
|
+
var POPUP_POLL_MS = 250;
|
|
6901
|
+
var AUTH_MESSAGE_TYPE = "primer-tives.auth.result.v1";
|
|
6902
|
+
function hostedAuthUrl(config) {
|
|
6903
|
+
const logger = config.logger;
|
|
6904
|
+
if (!URL.canParse(config.origin)) {
|
|
6905
|
+
logger.error({ origin: config.origin }, "hosted auth origin invalid");
|
|
6906
|
+
throw ErrAuthConfigInvalid;
|
|
6855
6907
|
}
|
|
6856
|
-
|
|
6857
|
-
|
|
6908
|
+
const authUrl = new URL("/api/auth/timeback/start", config.origin);
|
|
6909
|
+
authUrl.searchParams.set("publishableKey", config.publishableKey);
|
|
6910
|
+
authUrl.searchParams.set("redirectUri", redirectUri(config.currentUrl));
|
|
6911
|
+
authUrl.searchParams.set("state", config.clientState);
|
|
6912
|
+
return authUrl.toString();
|
|
6913
|
+
}
|
|
6914
|
+
function isRecord(value) {
|
|
6915
|
+
return typeof value === "object" && value !== null;
|
|
6916
|
+
}
|
|
6917
|
+
function stringField(value, key) {
|
|
6918
|
+
const field = value[key];
|
|
6919
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
6920
|
+
return null;
|
|
6858
6921
|
}
|
|
6859
|
-
|
|
6860
|
-
|
|
6922
|
+
return field;
|
|
6923
|
+
}
|
|
6924
|
+
function readPopupMessage(event, popup, config, expectedOrigin) {
|
|
6925
|
+
if (event.source !== popup) {
|
|
6926
|
+
return { kind: "ignore" };
|
|
6861
6927
|
}
|
|
6862
|
-
if (
|
|
6863
|
-
return
|
|
6928
|
+
if (event.origin !== expectedOrigin) {
|
|
6929
|
+
return { kind: "ignore" };
|
|
6864
6930
|
}
|
|
6865
|
-
|
|
6866
|
-
|
|
6931
|
+
const data = event.data;
|
|
6932
|
+
if (!isRecord(data)) {
|
|
6933
|
+
return { kind: "ignore" };
|
|
6867
6934
|
}
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
const parsed = parseAdvanceErrorBody(text);
|
|
6872
|
-
if (parsed === null) {
|
|
6873
|
-
logger.error({
|
|
6874
|
-
receivedSdkVersion: null,
|
|
6875
|
-
minimumSdkVersion: "<unknown>",
|
|
6876
|
-
upgradeUrl: NPM_PACKAGE_URL
|
|
6877
|
-
}, "sdk upgrade required");
|
|
6878
|
-
const message2 = `<missing> < <unknown>; bump @superbuilders/primer-tives at ${NPM_PACKAGE_URL}`;
|
|
6879
|
-
return errors3.wrap(sentinel, message2);
|
|
6935
|
+
const messageType = stringField(data, "type");
|
|
6936
|
+
if (messageType !== AUTH_MESSAGE_TYPE) {
|
|
6937
|
+
return { kind: "ignore" };
|
|
6880
6938
|
}
|
|
6881
|
-
const
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
const upgradeUrl = parsed.upgradeUrl === undefined ? NPM_PACKAGE_URL : parsed.upgradeUrl;
|
|
6885
|
-
logger.error({
|
|
6886
|
-
receivedSdkVersion,
|
|
6887
|
-
minimumSdkVersion,
|
|
6888
|
-
upgradeUrl
|
|
6889
|
-
}, "sdk upgrade required");
|
|
6890
|
-
const message = `${receivedDisplay} < ${minimumSdkVersion}; bump @superbuilders/primer-tives at ${upgradeUrl}`;
|
|
6891
|
-
return errors3.wrap(sentinel, message);
|
|
6892
|
-
}
|
|
6893
|
-
function isAbortError(err) {
|
|
6894
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
6895
|
-
return true;
|
|
6939
|
+
const state = stringField(data, "state");
|
|
6940
|
+
if (state === null) {
|
|
6941
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
6896
6942
|
}
|
|
6897
|
-
if (
|
|
6898
|
-
return
|
|
6943
|
+
if (state !== config.clientState) {
|
|
6944
|
+
return { kind: "error", error: ErrAuthStateMismatch };
|
|
6899
6945
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
const fetchFn = tc.fetch ? tc.fetch : globalThis.fetch;
|
|
6904
|
-
const logger = tc.logger;
|
|
6905
|
-
const advanceUrl = new URL(ADVANCE_PATH, tc.origin).toString();
|
|
6906
|
-
function transportSignal() {
|
|
6907
|
-
if (tc.abort) {
|
|
6908
|
-
return tc.abort.signal;
|
|
6909
|
-
}
|
|
6910
|
-
return;
|
|
6946
|
+
const status = stringField(data, "status");
|
|
6947
|
+
if (status === "error") {
|
|
6948
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
6911
6949
|
}
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
signal: transportSignal()
|
|
6929
|
-
}).then(function ok(r) {
|
|
6930
|
-
return { ok: true, response: r };
|
|
6931
|
-
}, function fail(err) {
|
|
6932
|
-
return { ok: false, error: err };
|
|
6950
|
+
if (status !== "success") {
|
|
6951
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
6952
|
+
}
|
|
6953
|
+
const accessToken = stringField(data, "accessToken");
|
|
6954
|
+
if (accessToken === null) {
|
|
6955
|
+
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
6956
|
+
}
|
|
6957
|
+
return { kind: "success", accessToken };
|
|
6958
|
+
}
|
|
6959
|
+
async function waitForPopupMessage(popup, config, expectedOrigin) {
|
|
6960
|
+
let __stack = [];
|
|
6961
|
+
try {
|
|
6962
|
+
const logger = config.logger;
|
|
6963
|
+
const stack = __using(__stack, new AsyncDisposableStack, 1);
|
|
6964
|
+
stack.defer(function closePopup() {
|
|
6965
|
+
popup.close();
|
|
6933
6966
|
});
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6967
|
+
const result = await new Promise(function waitForMessage(resolve, reject) {
|
|
6968
|
+
let settled = false;
|
|
6969
|
+
function finishWithError(error) {
|
|
6970
|
+
if (settled) {
|
|
6971
|
+
return;
|
|
6972
|
+
}
|
|
6973
|
+
settled = true;
|
|
6974
|
+
reject(error);
|
|
6940
6975
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6976
|
+
function finishWithToken(token) {
|
|
6977
|
+
if (settled) {
|
|
6978
|
+
return;
|
|
6979
|
+
}
|
|
6980
|
+
settled = true;
|
|
6981
|
+
logger.debug("hosted auth popup completed");
|
|
6982
|
+
resolve(token);
|
|
6983
|
+
}
|
|
6984
|
+
const timeoutId = globalThis.setTimeout(function timeout() {
|
|
6985
|
+
logger.error("hosted auth popup timed out");
|
|
6986
|
+
finishWithError(ErrAuthCancelled);
|
|
6987
|
+
}, DEFAULT_POPUP_TIMEOUT_MS);
|
|
6988
|
+
stack.defer(function clearPopupTimeout() {
|
|
6989
|
+
globalThis.clearTimeout(timeoutId);
|
|
6950
6990
|
});
|
|
6951
|
-
const
|
|
6952
|
-
|
|
6953
|
-
|
|
6991
|
+
const closedPollId = globalThis.setInterval(function checkClosed() {
|
|
6992
|
+
if (!popup.closed) {
|
|
6993
|
+
return;
|
|
6994
|
+
}
|
|
6995
|
+
logger.error("hosted auth popup closed");
|
|
6996
|
+
finishWithError(ErrAuthCancelled);
|
|
6997
|
+
}, POPUP_POLL_MS);
|
|
6998
|
+
stack.defer(function clearClosedPoll() {
|
|
6999
|
+
globalThis.clearInterval(closedPollId);
|
|
7000
|
+
});
|
|
7001
|
+
function handleMessage(event) {
|
|
7002
|
+
const result2 = readPopupMessage(event, popup, config, expectedOrigin);
|
|
7003
|
+
if (result2.kind === "ignore") {
|
|
7004
|
+
return;
|
|
7005
|
+
}
|
|
7006
|
+
if (result2.kind === "error") {
|
|
7007
|
+
logger.error({ error: result2.error }, "hosted auth popup failed");
|
|
7008
|
+
finishWithError(result2.error);
|
|
7009
|
+
return;
|
|
7010
|
+
}
|
|
7011
|
+
finishWithToken(result2.accessToken);
|
|
6954
7012
|
}
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
subject: body.subject
|
|
6960
|
-
}, "transport http error");
|
|
6961
|
-
return { ok: false, error: errors3.wrap(sentinel, text) };
|
|
6962
|
-
}
|
|
6963
|
-
const jsonResult = await res.json().then(function ok(data) {
|
|
6964
|
-
return { ok: true, data };
|
|
6965
|
-
}, function fail(err) {
|
|
6966
|
-
return { ok: false, error: err };
|
|
7013
|
+
globalThis.addEventListener("message", handleMessage);
|
|
7014
|
+
stack.defer(function removeMessageListener() {
|
|
7015
|
+
globalThis.removeEventListener("message", handleMessage);
|
|
7016
|
+
});
|
|
6967
7017
|
});
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
logger.debug({
|
|
6975
|
-
intentKind: body.intent.kind
|
|
6976
|
-
}, "transport success");
|
|
6977
|
-
return { ok: true, data: jsonResult.data };
|
|
7018
|
+
return result;
|
|
7019
|
+
} catch (_catch) {
|
|
7020
|
+
var _err = _catch, _hasErr = 1;
|
|
7021
|
+
} finally {
|
|
7022
|
+
var _promise = __callDispose(__stack, _err, _hasErr);
|
|
7023
|
+
_promise && await _promise;
|
|
6978
7024
|
}
|
|
6979
|
-
|
|
7025
|
+
}
|
|
7026
|
+
async function beginHostedPopup(config) {
|
|
7027
|
+
const logger = config.logger;
|
|
7028
|
+
const url = hostedAuthUrl(config);
|
|
7029
|
+
if (!URL.canParse(config.origin)) {
|
|
7030
|
+
logger.error({ origin: config.origin }, "hosted auth origin invalid");
|
|
7031
|
+
throw ErrAuthConfigInvalid;
|
|
7032
|
+
}
|
|
7033
|
+
const expectedOrigin = new URL(config.origin).origin;
|
|
7034
|
+
const popup = openAuthPopup(url, logger);
|
|
7035
|
+
return waitForPopupMessage(popup, config, expectedOrigin);
|
|
6980
7036
|
}
|
|
6981
7037
|
|
|
6982
|
-
// src/client/
|
|
6983
|
-
|
|
7038
|
+
// src/client/auth/storage.ts
|
|
7039
|
+
var MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX = "primer:access-token";
|
|
7040
|
+
function managedAuthAccessTokenStorageKey(publishableKey) {
|
|
7041
|
+
return `${MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX}:${publishableKey}`;
|
|
7042
|
+
}
|
|
7043
|
+
function managedAuthStorage(logger) {
|
|
7044
|
+
if (typeof globalThis.sessionStorage === "undefined") {
|
|
7045
|
+
logger.error("managed auth storage unavailable");
|
|
7046
|
+
throw ErrAuthUnavailable;
|
|
7047
|
+
}
|
|
7048
|
+
return globalThis.sessionStorage;
|
|
7049
|
+
}
|
|
7050
|
+
function loadManagedAuthAccessToken(storage, publishableKey) {
|
|
7051
|
+
const token = storage.getItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7052
|
+
if (token === null || token.length === 0) {
|
|
7053
|
+
return null;
|
|
7054
|
+
}
|
|
7055
|
+
return token;
|
|
7056
|
+
}
|
|
7057
|
+
function storeManagedAuthAccessToken(storage, publishableKey, accessToken) {
|
|
7058
|
+
storage.setItem(managedAuthAccessTokenStorageKey(publishableKey), accessToken);
|
|
7059
|
+
}
|
|
7060
|
+
function clearManagedAuthAccessToken(storage, publishableKey) {
|
|
7061
|
+
storage.removeItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7062
|
+
}
|
|
7063
|
+
|
|
7064
|
+
// src/client/auth/provider.ts
|
|
7065
|
+
function resolveProvidedAccessToken(token, logger) {
|
|
7066
|
+
const result = errors4.trySync(function resolveProvidedToken() {
|
|
7067
|
+
return resolveAccessToken(token, logger);
|
|
7068
|
+
});
|
|
7069
|
+
if (result.error) {
|
|
7070
|
+
return { kind: "fatal", error: result.error };
|
|
7071
|
+
}
|
|
7072
|
+
return { kind: "resolved", accessToken: result.data };
|
|
7073
|
+
}
|
|
7074
|
+
function resolveHostedAccessToken(token, storage, options) {
|
|
7075
|
+
const result = errors4.trySync(function resolveHostedToken() {
|
|
7076
|
+
return resolveAccessToken(token, options.logger);
|
|
7077
|
+
});
|
|
7078
|
+
if (result.error) {
|
|
7079
|
+
return { kind: "sign-in-failed", error: result.error };
|
|
7080
|
+
}
|
|
7081
|
+
storeManagedAuthAccessToken(storage, options.publishableKey, token);
|
|
7082
|
+
return {
|
|
7083
|
+
kind: "resolved",
|
|
7084
|
+
accessToken: result.data,
|
|
7085
|
+
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7086
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7087
|
+
}
|
|
7088
|
+
};
|
|
7089
|
+
}
|
|
7090
|
+
function resolveStoredAccessToken(storage, options) {
|
|
7091
|
+
const stored = loadManagedAuthAccessToken(storage, options.publishableKey);
|
|
7092
|
+
if (stored === null) {
|
|
7093
|
+
return { kind: "missing" };
|
|
7094
|
+
}
|
|
7095
|
+
const result = errors4.trySync(function resolveStoredToken() {
|
|
7096
|
+
return resolveAccessToken(stored, options.logger);
|
|
7097
|
+
});
|
|
7098
|
+
if (result.error) {
|
|
7099
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7100
|
+
return { kind: "missing" };
|
|
7101
|
+
}
|
|
7102
|
+
return {
|
|
7103
|
+
kind: "resolved",
|
|
7104
|
+
accessToken: result.data,
|
|
7105
|
+
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7106
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7107
|
+
}
|
|
7108
|
+
};
|
|
7109
|
+
}
|
|
7110
|
+
function resolveExistingAccessToken(options) {
|
|
7111
|
+
if (options.accessToken !== undefined) {
|
|
7112
|
+
return resolveProvidedAccessToken(options.accessToken, options.logger);
|
|
7113
|
+
}
|
|
7114
|
+
const storageResult = errors4.trySync(function readManagedAuthStorage() {
|
|
7115
|
+
return managedAuthStorage(options.logger);
|
|
7116
|
+
});
|
|
7117
|
+
if (storageResult.error) {
|
|
7118
|
+
return { kind: "auth-unavailable", error: storageResult.error };
|
|
7119
|
+
}
|
|
7120
|
+
return resolveStoredAccessToken(storageResult.data, options);
|
|
7121
|
+
}
|
|
7122
|
+
function authFailureResult(error) {
|
|
7123
|
+
if (errors4.is(error, ErrAuthConfigInvalid)) {
|
|
7124
|
+
return { kind: "auth-config-invalid", error };
|
|
7125
|
+
}
|
|
7126
|
+
if (errors4.is(error, ErrAuthUnavailable)) {
|
|
7127
|
+
return { kind: "auth-unavailable", error };
|
|
7128
|
+
}
|
|
7129
|
+
return { kind: "sign-in-failed", error };
|
|
7130
|
+
}
|
|
7131
|
+
async function beginHostedLogin(options) {
|
|
7132
|
+
const logger = options.logger;
|
|
7133
|
+
const contextResult = errors4.trySync(function readContext() {
|
|
7134
|
+
const storage = managedAuthStorage(logger);
|
|
7135
|
+
const url = currentUrl(logger);
|
|
7136
|
+
const clientState = randomClientState(logger);
|
|
7137
|
+
return { storage, url, clientState };
|
|
7138
|
+
});
|
|
7139
|
+
if (contextResult.error) {
|
|
7140
|
+
return authFailureResult(contextResult.error);
|
|
7141
|
+
}
|
|
7142
|
+
const context = contextResult.data;
|
|
7143
|
+
const accessTokenResult = await errors4.try(beginHostedPopup({
|
|
7144
|
+
origin: options.origin,
|
|
7145
|
+
publishableKey: options.publishableKey,
|
|
7146
|
+
currentUrl: context.url,
|
|
7147
|
+
clientState: context.clientState,
|
|
7148
|
+
logger
|
|
7149
|
+
}));
|
|
7150
|
+
if (accessTokenResult.error) {
|
|
7151
|
+
return authFailureResult(accessTokenResult.error);
|
|
7152
|
+
}
|
|
7153
|
+
return resolveHostedAccessToken(accessTokenResult.data, context.storage, options);
|
|
7154
|
+
}
|
|
6984
7155
|
|
|
6985
7156
|
// src/client/consumed.ts
|
|
6986
7157
|
function poisonToJSON() {
|
|
6987
7158
|
throw ErrNotSerializable;
|
|
6988
7159
|
}
|
|
6989
7160
|
|
|
7161
|
+
// src/client/auth-state.ts
|
|
7162
|
+
function pendingLogin(config) {
|
|
7163
|
+
let pending;
|
|
7164
|
+
function login() {
|
|
7165
|
+
if (pending !== undefined) {
|
|
7166
|
+
return pending;
|
|
7167
|
+
}
|
|
7168
|
+
pending = config.login().finally(function clearPending() {
|
|
7169
|
+
pending = undefined;
|
|
7170
|
+
});
|
|
7171
|
+
return pending;
|
|
7172
|
+
}
|
|
7173
|
+
return login;
|
|
7174
|
+
}
|
|
7175
|
+
function signInRequiredState(config) {
|
|
7176
|
+
return {
|
|
7177
|
+
phase: "sign-in-required",
|
|
7178
|
+
login: pendingLogin(config),
|
|
7179
|
+
toJSON: poisonToJSON
|
|
7180
|
+
};
|
|
7181
|
+
}
|
|
7182
|
+
function signInFailedState(config) {
|
|
7183
|
+
return {
|
|
7184
|
+
phase: "sign-in-failed",
|
|
7185
|
+
error: config.error,
|
|
7186
|
+
login: pendingLogin(config),
|
|
7187
|
+
toJSON: poisonToJSON
|
|
7188
|
+
};
|
|
7189
|
+
}
|
|
7190
|
+
function authUnavailableState(error) {
|
|
7191
|
+
return {
|
|
7192
|
+
phase: "auth-unavailable",
|
|
7193
|
+
error,
|
|
7194
|
+
toJSON: poisonToJSON
|
|
7195
|
+
};
|
|
7196
|
+
}
|
|
7197
|
+
function authConfigInvalidState(error) {
|
|
7198
|
+
return {
|
|
7199
|
+
phase: "auth-config-invalid",
|
|
7200
|
+
error,
|
|
7201
|
+
toJSON: poisonToJSON
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7205
|
+
// src/client/session.ts
|
|
7206
|
+
import * as errors11 from "@superbuilders/errors";
|
|
7207
|
+
|
|
6990
7208
|
// src/client/choice-state.ts
|
|
6991
|
-
import * as
|
|
7209
|
+
import * as errors5 from "@superbuilders/errors";
|
|
6992
7210
|
function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minChoices) {
|
|
6993
7211
|
let submitPending;
|
|
6994
7212
|
let submitKey;
|
|
@@ -6997,18 +7215,18 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
6997
7215
|
const submission = { type: "choice", selectedKeys };
|
|
6998
7216
|
const key = JSON.stringify(submission);
|
|
6999
7217
|
if (timeoutPending) {
|
|
7000
|
-
return Promise.resolve(ctx.errored(
|
|
7218
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7001
7219
|
}
|
|
7002
7220
|
if (submitPending) {
|
|
7003
7221
|
if (submitKey === key) {
|
|
7004
7222
|
return submitPending;
|
|
7005
7223
|
}
|
|
7006
|
-
return Promise.resolve(ctx.errored(
|
|
7224
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit a different choice payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7007
7225
|
}
|
|
7008
7226
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7009
7227
|
if (!validation.ok) {
|
|
7010
7228
|
ctx.logger.error({ selectedKeys, issues: validation.issues }, "choice submit invalid");
|
|
7011
|
-
return Promise.resolve(ctx.errored(
|
|
7229
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7012
7230
|
}
|
|
7013
7231
|
submitKey = key;
|
|
7014
7232
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7020,7 +7238,7 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
7020
7238
|
function beginTimeout() {
|
|
7021
7239
|
const intent = { kind: "timeout" };
|
|
7022
7240
|
if (submitPending) {
|
|
7023
|
-
return Promise.resolve(ctx.errored(
|
|
7241
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7024
7242
|
}
|
|
7025
7243
|
if (timeoutPending) {
|
|
7026
7244
|
return timeoutPending;
|
|
@@ -7046,25 +7264,25 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
7046
7264
|
}
|
|
7047
7265
|
|
|
7048
7266
|
// src/client/extended-text-state.ts
|
|
7049
|
-
import * as
|
|
7267
|
+
import * as errors6 from "@superbuilders/errors";
|
|
7050
7268
|
function extendedTextState(ctx, body, stimulus, interaction) {
|
|
7051
7269
|
if (interaction.cardinality === "single") {
|
|
7052
7270
|
let submitText = function(value) {
|
|
7053
7271
|
const submission = { type: "extended-text", values: [value] };
|
|
7054
7272
|
const key = JSON.stringify(submission);
|
|
7055
7273
|
if (timeoutPending2) {
|
|
7056
|
-
return Promise.resolve(ctx.errored(
|
|
7274
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7057
7275
|
}
|
|
7058
7276
|
if (submitPending2) {
|
|
7059
7277
|
if (submitKey2 === key) {
|
|
7060
7278
|
return submitPending2;
|
|
7061
7279
|
}
|
|
7062
|
-
return Promise.resolve(ctx.errored(
|
|
7280
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7063
7281
|
}
|
|
7064
7282
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7065
7283
|
if (!validation.ok) {
|
|
7066
7284
|
ctx.logger.error({ value, issues: validation.issues }, "extended-text submit invalid");
|
|
7067
|
-
return Promise.resolve(ctx.errored(
|
|
7285
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7068
7286
|
}
|
|
7069
7287
|
submitKey2 = key;
|
|
7070
7288
|
submitPending2 = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7075,7 +7293,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7075
7293
|
}, timeout2 = function() {
|
|
7076
7294
|
const intent = { kind: "timeout" };
|
|
7077
7295
|
if (submitPending2) {
|
|
7078
|
-
return Promise.resolve(ctx.errored(
|
|
7296
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7079
7297
|
}
|
|
7080
7298
|
if (timeoutPending2) {
|
|
7081
7299
|
return timeoutPending2;
|
|
@@ -7108,18 +7326,18 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7108
7326
|
const submission = { type: "extended-text", values };
|
|
7109
7327
|
const key = JSON.stringify(submission);
|
|
7110
7328
|
if (timeoutPending) {
|
|
7111
|
-
return Promise.resolve(ctx.errored(
|
|
7329
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7112
7330
|
}
|
|
7113
7331
|
if (submitPending) {
|
|
7114
7332
|
if (submitKey === key) {
|
|
7115
7333
|
return submitPending;
|
|
7116
7334
|
}
|
|
7117
|
-
return Promise.resolve(ctx.errored(
|
|
7335
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7118
7336
|
}
|
|
7119
7337
|
const validation = validateSubmissionForInteraction(multi, submission);
|
|
7120
7338
|
if (!validation.ok) {
|
|
7121
7339
|
ctx.logger.error({ values, issues: validation.issues }, "extended-text submit invalid");
|
|
7122
|
-
return Promise.resolve(ctx.errored(
|
|
7340
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7123
7341
|
}
|
|
7124
7342
|
submitKey = key;
|
|
7125
7343
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7131,7 +7349,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7131
7349
|
function timeout() {
|
|
7132
7350
|
const intent = { kind: "timeout" };
|
|
7133
7351
|
if (submitPending) {
|
|
7134
|
-
return Promise.resolve(ctx.errored(
|
|
7352
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7135
7353
|
}
|
|
7136
7354
|
if (timeoutPending) {
|
|
7137
7355
|
return timeoutPending;
|
|
@@ -7157,7 +7375,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7157
7375
|
}
|
|
7158
7376
|
|
|
7159
7377
|
// src/client/feedback-state.ts
|
|
7160
|
-
function feedbackState(ctx, body, stimulus, interaction, submission,
|
|
7378
|
+
function feedbackState(ctx, body, stimulus, interaction, submission, assessmentOutcome, feedbackContent, review) {
|
|
7161
7379
|
let pending;
|
|
7162
7380
|
return {
|
|
7163
7381
|
phase: "feedback",
|
|
@@ -7165,7 +7383,7 @@ function feedbackState(ctx, body, stimulus, interaction, submission, isCorrect,
|
|
|
7165
7383
|
stimulus,
|
|
7166
7384
|
interaction,
|
|
7167
7385
|
submission,
|
|
7168
|
-
|
|
7386
|
+
assessmentOutcome,
|
|
7169
7387
|
feedbackContent,
|
|
7170
7388
|
review,
|
|
7171
7389
|
advance: function advance() {
|
|
@@ -7180,7 +7398,7 @@ function feedbackState(ctx, body, stimulus, interaction, submission, isCorrect,
|
|
|
7180
7398
|
}
|
|
7181
7399
|
|
|
7182
7400
|
// src/client/match-state.ts
|
|
7183
|
-
import * as
|
|
7401
|
+
import * as errors7 from "@superbuilders/errors";
|
|
7184
7402
|
function matchState(ctx, body, stimulus, interaction) {
|
|
7185
7403
|
let submitPending;
|
|
7186
7404
|
let submitKey;
|
|
@@ -7189,18 +7407,18 @@ function matchState(ctx, body, stimulus, interaction) {
|
|
|
7189
7407
|
const submission = { type: "match", pairs };
|
|
7190
7408
|
const key = JSON.stringify(submission);
|
|
7191
7409
|
if (timeoutPending) {
|
|
7192
|
-
return Promise.resolve(ctx.errored(
|
|
7410
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7193
7411
|
}
|
|
7194
7412
|
if (submitPending) {
|
|
7195
7413
|
if (submitKey === key) {
|
|
7196
7414
|
return submitPending;
|
|
7197
7415
|
}
|
|
7198
|
-
return Promise.resolve(ctx.errored(
|
|
7416
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different match payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7199
7417
|
}
|
|
7200
7418
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7201
7419
|
if (!validation.ok) {
|
|
7202
7420
|
ctx.logger.error({ pairs, issues: validation.issues }, "match submit invalid");
|
|
7203
|
-
return Promise.resolve(ctx.errored(
|
|
7421
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7204
7422
|
}
|
|
7205
7423
|
submitKey = key;
|
|
7206
7424
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7212,7 +7430,7 @@ function matchState(ctx, body, stimulus, interaction) {
|
|
|
7212
7430
|
function timeout() {
|
|
7213
7431
|
const intent = { kind: "timeout" };
|
|
7214
7432
|
if (submitPending) {
|
|
7215
|
-
return Promise.resolve(ctx.errored(
|
|
7433
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7216
7434
|
}
|
|
7217
7435
|
if (timeoutPending) {
|
|
7218
7436
|
return timeoutPending;
|
|
@@ -7257,7 +7475,7 @@ function observationState(ctx, body, stimulus) {
|
|
|
7257
7475
|
}
|
|
7258
7476
|
|
|
7259
7477
|
// src/client/order-state.ts
|
|
7260
|
-
import * as
|
|
7478
|
+
import * as errors8 from "@superbuilders/errors";
|
|
7261
7479
|
function orderState(ctx, body, stimulus, interaction) {
|
|
7262
7480
|
let submitPending;
|
|
7263
7481
|
let submitKey;
|
|
@@ -7266,18 +7484,18 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7266
7484
|
const submission = { type: "order", orderedKeys };
|
|
7267
7485
|
const key = JSON.stringify(submission);
|
|
7268
7486
|
if (timeoutPending) {
|
|
7269
|
-
return Promise.resolve(ctx.errored(
|
|
7487
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7270
7488
|
}
|
|
7271
7489
|
if (submitPending) {
|
|
7272
7490
|
if (submitKey === key) {
|
|
7273
7491
|
return submitPending;
|
|
7274
7492
|
}
|
|
7275
|
-
return Promise.resolve(ctx.errored(
|
|
7493
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit a different order payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7276
7494
|
}
|
|
7277
7495
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7278
7496
|
if (!validation.ok) {
|
|
7279
7497
|
ctx.logger.error({ orderedKeys, issues: validation.issues }, "order submit invalid");
|
|
7280
|
-
return Promise.resolve(ctx.errored(
|
|
7498
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7281
7499
|
}
|
|
7282
7500
|
submitKey = key;
|
|
7283
7501
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7289,7 +7507,7 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7289
7507
|
function timeout() {
|
|
7290
7508
|
const intent = { kind: "timeout" };
|
|
7291
7509
|
if (submitPending) {
|
|
7292
|
-
return Promise.resolve(ctx.errored(
|
|
7510
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7293
7511
|
}
|
|
7294
7512
|
if (timeoutPending) {
|
|
7295
7513
|
return timeoutPending;
|
|
@@ -7315,7 +7533,7 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7315
7533
|
}
|
|
7316
7534
|
|
|
7317
7535
|
// src/client/pci-state.ts
|
|
7318
|
-
import * as
|
|
7536
|
+
import * as errors9 from "@superbuilders/errors";
|
|
7319
7537
|
function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
7320
7538
|
let submitPending;
|
|
7321
7539
|
let submitKey;
|
|
@@ -7325,13 +7543,13 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7325
7543
|
const submission = { type: "portable-custom", pciId, value };
|
|
7326
7544
|
const key = JSON.stringify(submission);
|
|
7327
7545
|
if (timeoutPending) {
|
|
7328
|
-
return Promise.resolve(ctx.errored(
|
|
7546
|
+
return Promise.resolve(ctx.errored(errors9.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7329
7547
|
}
|
|
7330
7548
|
if (submitPending) {
|
|
7331
7549
|
if (submitKey === key) {
|
|
7332
7550
|
return submitPending;
|
|
7333
7551
|
}
|
|
7334
|
-
return Promise.resolve(ctx.errored(
|
|
7552
|
+
return Promise.resolve(ctx.errored(errors9.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7335
7553
|
}
|
|
7336
7554
|
ctx.logger.debug({ pciId }, "pci submit");
|
|
7337
7555
|
submitKey = key;
|
|
@@ -7344,7 +7562,7 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7344
7562
|
function timeout() {
|
|
7345
7563
|
const intent = { kind: "timeout" };
|
|
7346
7564
|
if (submitPending) {
|
|
7347
|
-
return Promise.resolve(ctx.errored(
|
|
7565
|
+
return Promise.resolve(ctx.errored(errors9.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7348
7566
|
}
|
|
7349
7567
|
if (timeoutPending) {
|
|
7350
7568
|
return timeoutPending;
|
|
@@ -7370,7 +7588,7 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7370
7588
|
}
|
|
7371
7589
|
|
|
7372
7590
|
// src/client/text-entry-state.ts
|
|
7373
|
-
import * as
|
|
7591
|
+
import * as errors10 from "@superbuilders/errors";
|
|
7374
7592
|
function textEntryState(ctx, body, stimulus, interaction) {
|
|
7375
7593
|
let submitPending;
|
|
7376
7594
|
let submitKey;
|
|
@@ -7379,18 +7597,18 @@ function textEntryState(ctx, body, stimulus, interaction) {
|
|
|
7379
7597
|
const submission = { type: "text-entry", value };
|
|
7380
7598
|
const key = JSON.stringify(submission);
|
|
7381
7599
|
if (timeoutPending) {
|
|
7382
|
-
return Promise.resolve(ctx.errored(
|
|
7600
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7383
7601
|
}
|
|
7384
7602
|
if (submitPending) {
|
|
7385
7603
|
if (submitKey === key) {
|
|
7386
7604
|
return submitPending;
|
|
7387
7605
|
}
|
|
7388
|
-
return Promise.resolve(ctx.errored(
|
|
7606
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrConflict, "cannot submit a different text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7389
7607
|
}
|
|
7390
7608
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7391
7609
|
if (!validation.ok) {
|
|
7392
7610
|
ctx.logger.error({ value, issues: validation.issues }, "text-entry submit invalid");
|
|
7393
|
-
return Promise.resolve(ctx.errored(
|
|
7611
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7394
7612
|
}
|
|
7395
7613
|
submitKey = key;
|
|
7396
7614
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7402,7 +7620,7 @@ function textEntryState(ctx, body, stimulus, interaction) {
|
|
|
7402
7620
|
function timeout() {
|
|
7403
7621
|
const intent = { kind: "timeout" };
|
|
7404
7622
|
if (submitPending) {
|
|
7405
|
-
return Promise.resolve(ctx.errored(
|
|
7623
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7406
7624
|
}
|
|
7407
7625
|
if (timeoutPending) {
|
|
7408
7626
|
return timeoutPending;
|
|
@@ -7436,14 +7654,14 @@ var FATAL_SENTINELS = [
|
|
|
7436
7654
|
];
|
|
7437
7655
|
function isFatalError(err) {
|
|
7438
7656
|
for (const sentinel of FATAL_SENTINELS) {
|
|
7439
|
-
if (
|
|
7657
|
+
if (errors11.is(err, sentinel)) {
|
|
7440
7658
|
return true;
|
|
7441
7659
|
}
|
|
7442
7660
|
}
|
|
7443
7661
|
return false;
|
|
7444
7662
|
}
|
|
7445
7663
|
function isRetriableError(err) {
|
|
7446
|
-
return !
|
|
7664
|
+
return !errors11.is(err, ErrInvalidSubmission);
|
|
7447
7665
|
}
|
|
7448
7666
|
function makeSession(sc) {
|
|
7449
7667
|
const logger = sc.logger;
|
|
@@ -7457,12 +7675,15 @@ function makeSession(sc) {
|
|
|
7457
7675
|
logger.error("submitted result without interaction");
|
|
7458
7676
|
return {
|
|
7459
7677
|
phase: "fatal",
|
|
7460
|
-
error:
|
|
7678
|
+
error: errors11.wrap(ErrBadRequest, "submitted result missing interaction"),
|
|
7461
7679
|
retriable: false,
|
|
7462
7680
|
toJSON: poisonToJSON
|
|
7463
7681
|
};
|
|
7464
7682
|
}
|
|
7465
|
-
|
|
7683
|
+
if (result.assessmentOutcome.value === "revisionRequested") {
|
|
7684
|
+
return fromAdvanced(result.frame.body, result.frame.stimulus, interaction);
|
|
7685
|
+
}
|
|
7686
|
+
return feedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.submission, result.assessmentOutcome, result.feedbackContent, result.review);
|
|
7466
7687
|
}
|
|
7467
7688
|
case "completed":
|
|
7468
7689
|
return { phase: "completed", toJSON: poisonToJSON };
|
|
@@ -7507,9 +7728,9 @@ function makeSession(sc) {
|
|
|
7507
7728
|
};
|
|
7508
7729
|
const result = await sc.transport(body);
|
|
7509
7730
|
if (!result.ok) {
|
|
7510
|
-
if (
|
|
7511
|
-
logger.warn({ phase, intentKind: intent.kind }, "session token
|
|
7512
|
-
return sc.reauthenticate();
|
|
7731
|
+
if ((errors11.is(result.error, ErrTokenExpired) || errors11.is(result.error, ErrInvalidAccessToken)) && sc.reauthenticate !== null) {
|
|
7732
|
+
logger.warn({ phase, intentKind: intent.kind }, "session token rejected");
|
|
7733
|
+
return sc.reauthenticate(result.error);
|
|
7513
7734
|
}
|
|
7514
7735
|
if (isFatalError(result.error)) {
|
|
7515
7736
|
logger.error({
|
|
@@ -7557,7 +7778,7 @@ function makeSession(sc) {
|
|
|
7557
7778
|
logger.error({ pciId: interaction.pciId }, "unsupported pci in frame");
|
|
7558
7779
|
return {
|
|
7559
7780
|
phase: "fatal",
|
|
7560
|
-
error:
|
|
7781
|
+
error: errors11.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
|
|
7561
7782
|
retriable: false,
|
|
7562
7783
|
toJSON: poisonToJSON
|
|
7563
7784
|
};
|
|
@@ -7565,6 +7786,19 @@ function makeSession(sc) {
|
|
|
7565
7786
|
}
|
|
7566
7787
|
return pendingInteractionState(body, stimulus, interaction);
|
|
7567
7788
|
}
|
|
7789
|
+
function extendedTextInteractionState(body, stimulus, interaction) {
|
|
7790
|
+
if (!("cardinality" in interaction)) {
|
|
7791
|
+
logger.error("extended-text interaction is unsupported");
|
|
7792
|
+
return {
|
|
7793
|
+
phase: "fatal",
|
|
7794
|
+
error: errors11.wrap(ErrBadRequest, "extended-text interaction unsupported"),
|
|
7795
|
+
retriable: false,
|
|
7796
|
+
toJSON: poisonToJSON
|
|
7797
|
+
};
|
|
7798
|
+
}
|
|
7799
|
+
const extendedInteraction = interaction;
|
|
7800
|
+
return extendedTextState(ctx, body, stimulus, extendedInteraction);
|
|
7801
|
+
}
|
|
7568
7802
|
function pendingInteractionState(body, stimulus, interaction) {
|
|
7569
7803
|
switch (interaction.type) {
|
|
7570
7804
|
case "choice":
|
|
@@ -7572,7 +7806,7 @@ function makeSession(sc) {
|
|
|
7572
7806
|
case "text-entry":
|
|
7573
7807
|
return textEntryState(ctx, body, stimulus, interaction);
|
|
7574
7808
|
case "extended-text":
|
|
7575
|
-
return
|
|
7809
|
+
return extendedTextInteractionState(body, stimulus, interaction);
|
|
7576
7810
|
case "order":
|
|
7577
7811
|
return orderState(ctx, body, stimulus, interaction);
|
|
7578
7812
|
case "match":
|
|
@@ -7585,362 +7819,204 @@ function makeSession(sc) {
|
|
|
7585
7819
|
return { execute };
|
|
7586
7820
|
}
|
|
7587
7821
|
|
|
7588
|
-
// src/client/
|
|
7822
|
+
// src/client/transport.ts
|
|
7589
7823
|
import * as errors12 from "@superbuilders/errors";
|
|
7590
7824
|
|
|
7591
|
-
// src/
|
|
7592
|
-
var
|
|
7593
|
-
var
|
|
7594
|
-
function currentUrl(logger) {
|
|
7595
|
-
if (typeof globalThis.location === "undefined") {
|
|
7596
|
-
logger.error("auth location unavailable");
|
|
7597
|
-
throw ErrAuthUnavailable;
|
|
7598
|
-
}
|
|
7599
|
-
return new URL(globalThis.location.href);
|
|
7600
|
-
}
|
|
7601
|
-
function redirectUri(url) {
|
|
7602
|
-
return `${url.origin}${url.pathname}${url.search}`;
|
|
7603
|
-
}
|
|
7604
|
-
function randomClientState(logger) {
|
|
7605
|
-
if (typeof globalThis.crypto === "undefined") {
|
|
7606
|
-
logger.error("auth crypto unavailable");
|
|
7607
|
-
throw ErrAuthUnavailable;
|
|
7608
|
-
}
|
|
7609
|
-
const bytes = new Uint8Array(24);
|
|
7610
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
7611
|
-
let result = "";
|
|
7612
|
-
for (const byte of bytes) {
|
|
7613
|
-
result += byte.toString(16).padStart(2, "0");
|
|
7614
|
-
}
|
|
7615
|
-
return result;
|
|
7616
|
-
}
|
|
7617
|
-
function openAuthPopup(url, logger) {
|
|
7618
|
-
if (typeof globalThis.open === "undefined") {
|
|
7619
|
-
logger.error("auth popup api unavailable");
|
|
7620
|
-
throw ErrAuthUnavailable;
|
|
7621
|
-
}
|
|
7622
|
-
const popup = globalThis.open(url, POPUP_TARGET, POPUP_FEATURES);
|
|
7623
|
-
if (popup === null) {
|
|
7624
|
-
logger.error("auth popup blocked");
|
|
7625
|
-
throw ErrAuthPopupBlocked;
|
|
7626
|
-
}
|
|
7627
|
-
return popup;
|
|
7628
|
-
}
|
|
7629
|
-
|
|
7630
|
-
// src/client/auth/hosted-popup.ts
|
|
7631
|
-
var DEFAULT_POPUP_TIMEOUT_MS = 10 * 60 * 1000;
|
|
7632
|
-
var POPUP_POLL_MS = 250;
|
|
7633
|
-
var AUTH_MESSAGE_TYPE = "primer-tives.auth.result.v1";
|
|
7634
|
-
function hostedAuthUrl(config) {
|
|
7635
|
-
const logger = config.logger;
|
|
7636
|
-
if (!URL.canParse(config.origin)) {
|
|
7637
|
-
logger.error({ origin: config.origin }, "hosted auth origin invalid");
|
|
7638
|
-
throw ErrAuthConfigInvalid;
|
|
7639
|
-
}
|
|
7640
|
-
const authUrl = new URL("/api/auth/timeback/start", config.origin);
|
|
7641
|
-
authUrl.searchParams.set("publishableKey", config.publishableKey);
|
|
7642
|
-
authUrl.searchParams.set("redirectUri", redirectUri(config.currentUrl));
|
|
7643
|
-
authUrl.searchParams.set("state", config.clientState);
|
|
7644
|
-
return authUrl.toString();
|
|
7645
|
-
}
|
|
7646
|
-
function isRecord(value) {
|
|
7647
|
-
return typeof value === "object" && value !== null;
|
|
7648
|
-
}
|
|
7649
|
-
function stringField(value, key) {
|
|
7650
|
-
const field = value[key];
|
|
7651
|
-
if (typeof field !== "string" || field.length === 0) {
|
|
7652
|
-
return null;
|
|
7653
|
-
}
|
|
7654
|
-
return field;
|
|
7655
|
-
}
|
|
7656
|
-
function readPopupMessage(event, popup, config, expectedOrigin) {
|
|
7657
|
-
if (event.source !== popup) {
|
|
7658
|
-
return { kind: "ignore" };
|
|
7659
|
-
}
|
|
7660
|
-
if (event.origin !== expectedOrigin) {
|
|
7661
|
-
return { kind: "ignore" };
|
|
7662
|
-
}
|
|
7663
|
-
const data = event.data;
|
|
7664
|
-
if (!isRecord(data)) {
|
|
7665
|
-
return { kind: "ignore" };
|
|
7666
|
-
}
|
|
7667
|
-
const messageType = stringField(data, "type");
|
|
7668
|
-
if (messageType !== AUTH_MESSAGE_TYPE) {
|
|
7669
|
-
return { kind: "ignore" };
|
|
7670
|
-
}
|
|
7671
|
-
const state = stringField(data, "state");
|
|
7672
|
-
if (state === null) {
|
|
7673
|
-
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
7674
|
-
}
|
|
7675
|
-
if (state !== config.clientState) {
|
|
7676
|
-
return { kind: "error", error: ErrAuthStateMismatch };
|
|
7677
|
-
}
|
|
7678
|
-
const status = stringField(data, "status");
|
|
7679
|
-
if (status === "error") {
|
|
7680
|
-
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
7681
|
-
}
|
|
7682
|
-
if (status !== "success") {
|
|
7683
|
-
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
7684
|
-
}
|
|
7685
|
-
const accessToken = stringField(data, "accessToken");
|
|
7686
|
-
if (accessToken === null) {
|
|
7687
|
-
return { kind: "error", error: ErrAuthCallbackInvalid };
|
|
7688
|
-
}
|
|
7689
|
-
return { kind: "success", accessToken };
|
|
7690
|
-
}
|
|
7691
|
-
async function waitForPopupMessage(popup, config, expectedOrigin) {
|
|
7692
|
-
let __stack = [];
|
|
7693
|
-
try {
|
|
7694
|
-
const logger = config.logger;
|
|
7695
|
-
const stack = __using(__stack, new AsyncDisposableStack, 1);
|
|
7696
|
-
stack.defer(function closePopup() {
|
|
7697
|
-
popup.close();
|
|
7698
|
-
});
|
|
7699
|
-
const result = await new Promise(function waitForMessage(resolve, reject) {
|
|
7700
|
-
let settled = false;
|
|
7701
|
-
function finishWithError(error) {
|
|
7702
|
-
if (settled) {
|
|
7703
|
-
return;
|
|
7704
|
-
}
|
|
7705
|
-
settled = true;
|
|
7706
|
-
reject(error);
|
|
7707
|
-
}
|
|
7708
|
-
function finishWithToken(token) {
|
|
7709
|
-
if (settled) {
|
|
7710
|
-
return;
|
|
7711
|
-
}
|
|
7712
|
-
settled = true;
|
|
7713
|
-
logger.debug("hosted auth popup completed");
|
|
7714
|
-
resolve(token);
|
|
7715
|
-
}
|
|
7716
|
-
const timeoutId = globalThis.setTimeout(function timeout() {
|
|
7717
|
-
logger.error("hosted auth popup timed out");
|
|
7718
|
-
finishWithError(ErrAuthCancelled);
|
|
7719
|
-
}, DEFAULT_POPUP_TIMEOUT_MS);
|
|
7720
|
-
stack.defer(function clearPopupTimeout() {
|
|
7721
|
-
globalThis.clearTimeout(timeoutId);
|
|
7722
|
-
});
|
|
7723
|
-
const closedPollId = globalThis.setInterval(function checkClosed() {
|
|
7724
|
-
if (!popup.closed) {
|
|
7725
|
-
return;
|
|
7726
|
-
}
|
|
7727
|
-
logger.error("hosted auth popup closed");
|
|
7728
|
-
finishWithError(ErrAuthCancelled);
|
|
7729
|
-
}, POPUP_POLL_MS);
|
|
7730
|
-
stack.defer(function clearClosedPoll() {
|
|
7731
|
-
globalThis.clearInterval(closedPollId);
|
|
7732
|
-
});
|
|
7733
|
-
function handleMessage(event) {
|
|
7734
|
-
const result2 = readPopupMessage(event, popup, config, expectedOrigin);
|
|
7735
|
-
if (result2.kind === "ignore") {
|
|
7736
|
-
return;
|
|
7737
|
-
}
|
|
7738
|
-
if (result2.kind === "error") {
|
|
7739
|
-
logger.error({ error: result2.error }, "hosted auth popup failed");
|
|
7740
|
-
finishWithError(result2.error);
|
|
7741
|
-
return;
|
|
7742
|
-
}
|
|
7743
|
-
finishWithToken(result2.accessToken);
|
|
7744
|
-
}
|
|
7745
|
-
globalThis.addEventListener("message", handleMessage);
|
|
7746
|
-
stack.defer(function removeMessageListener() {
|
|
7747
|
-
globalThis.removeEventListener("message", handleMessage);
|
|
7748
|
-
});
|
|
7749
|
-
});
|
|
7750
|
-
return result;
|
|
7751
|
-
} catch (_catch) {
|
|
7752
|
-
var _err = _catch, _hasErr = 1;
|
|
7753
|
-
} finally {
|
|
7754
|
-
var _promise = __callDispose(__stack, _err, _hasErr);
|
|
7755
|
-
_promise && await _promise;
|
|
7756
|
-
}
|
|
7757
|
-
}
|
|
7758
|
-
async function beginHostedPopup(config) {
|
|
7759
|
-
const logger = config.logger;
|
|
7760
|
-
const url = hostedAuthUrl(config);
|
|
7761
|
-
if (!URL.canParse(config.origin)) {
|
|
7762
|
-
logger.error({ origin: config.origin }, "hosted auth origin invalid");
|
|
7763
|
-
throw ErrAuthConfigInvalid;
|
|
7764
|
-
}
|
|
7765
|
-
const expectedOrigin = new URL(config.origin).origin;
|
|
7766
|
-
const popup = openAuthPopup(url, logger);
|
|
7767
|
-
return waitForPopupMessage(popup, config, expectedOrigin);
|
|
7768
|
-
}
|
|
7825
|
+
// src/version.ts
|
|
7826
|
+
var SDK_VERSION = "4.1.0";
|
|
7827
|
+
var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
|
|
7769
7828
|
|
|
7770
|
-
// src/client/
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
function isMalformedJws(token) {
|
|
7776
|
-
if (!token.startsWith(ACCESS_TOKEN_PREFIX)) {
|
|
7777
|
-
return true;
|
|
7778
|
-
}
|
|
7779
|
-
const dotCount = token.split(".").length - 1;
|
|
7780
|
-
return dotCount !== 2;
|
|
7781
|
-
}
|
|
7782
|
-
function paddedBase64(base64Url) {
|
|
7783
|
-
const normalized = base64Url.replaceAll("-", "+").replaceAll("_", "/");
|
|
7784
|
-
const remainder = normalized.length % 4;
|
|
7785
|
-
if (remainder === 0) {
|
|
7786
|
-
return normalized;
|
|
7787
|
-
}
|
|
7788
|
-
if (remainder === 2) {
|
|
7789
|
-
return `${normalized}==`;
|
|
7829
|
+
// src/client/transport.ts
|
|
7830
|
+
var ADVANCE_PATH = "/api/v0/advance";
|
|
7831
|
+
function readStringField(value, key) {
|
|
7832
|
+
if (!(key in value)) {
|
|
7833
|
+
return;
|
|
7790
7834
|
}
|
|
7791
|
-
|
|
7792
|
-
|
|
7835
|
+
const v = Reflect.get(value, key);
|
|
7836
|
+
if (typeof v !== "string") {
|
|
7837
|
+
return;
|
|
7793
7838
|
}
|
|
7794
|
-
|
|
7839
|
+
return v;
|
|
7795
7840
|
}
|
|
7796
|
-
function
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
return JSON.parse(json);
|
|
7800
|
-
});
|
|
7801
|
-
if (decoded.error) {
|
|
7802
|
-
throw errors11.wrap(ErrMalformedAccessToken, "payload decode");
|
|
7841
|
+
function parseAdvanceErrorBody(body) {
|
|
7842
|
+
if (body.length === 0) {
|
|
7843
|
+
return null;
|
|
7803
7844
|
}
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
if (
|
|
7845
|
+
const parsed = errors12.trySync(function parseJson() {
|
|
7846
|
+
return JSON.parse(body);
|
|
7847
|
+
});
|
|
7848
|
+
if (parsed.error) {
|
|
7808
7849
|
return null;
|
|
7809
7850
|
}
|
|
7810
|
-
const
|
|
7811
|
-
if (typeof
|
|
7851
|
+
const raw = parsed.data;
|
|
7852
|
+
if (typeof raw !== "object" || raw === null) {
|
|
7812
7853
|
return null;
|
|
7813
7854
|
}
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
const payloadSegment = segments[1];
|
|
7819
|
-
if (payloadSegment === undefined || payloadSegment.length === 0) {
|
|
7820
|
-
logger.error("access token payload missing");
|
|
7821
|
-
throw errors11.wrap(ErrMalformedAccessToken, "payload missing");
|
|
7855
|
+
const result = {};
|
|
7856
|
+
const error = readStringField(raw, "error");
|
|
7857
|
+
if (error !== undefined) {
|
|
7858
|
+
result.error = error;
|
|
7822
7859
|
}
|
|
7823
|
-
const
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
logger.error("access token exp missing");
|
|
7827
|
-
throw errors11.wrap(ErrMalformedAccessToken, "exp missing");
|
|
7860
|
+
const detail = readStringField(raw, "detail");
|
|
7861
|
+
if (detail !== undefined) {
|
|
7862
|
+
result.detail = detail;
|
|
7828
7863
|
}
|
|
7829
|
-
const
|
|
7830
|
-
if (
|
|
7831
|
-
|
|
7832
|
-
throw ErrTokenExpired;
|
|
7864
|
+
const minimumSdkVersion = readStringField(raw, "minimumSdkVersion");
|
|
7865
|
+
if (minimumSdkVersion !== undefined) {
|
|
7866
|
+
result.minimumSdkVersion = minimumSdkVersion;
|
|
7833
7867
|
}
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
logger.error({ prefix: ACCESS_TOKEN_PREFIX }, "malformed access token");
|
|
7838
|
-
throw errors11.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
|
|
7868
|
+
const receivedSdkVersion = readStringField(raw, "receivedSdkVersion");
|
|
7869
|
+
if (receivedSdkVersion !== undefined) {
|
|
7870
|
+
result.receivedSdkVersion = receivedSdkVersion;
|
|
7839
7871
|
}
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
// src/client/auth/provider.ts
|
|
7845
|
-
function resolveProvidedAccessToken(token, logger) {
|
|
7846
|
-
const result = errors12.trySync(function resolveProvidedToken() {
|
|
7847
|
-
return resolveAccessToken(token, logger);
|
|
7848
|
-
});
|
|
7849
|
-
if (result.error) {
|
|
7850
|
-
return { kind: "fatal", error: result.error };
|
|
7872
|
+
const upgradeUrl = readStringField(raw, "upgradeUrl");
|
|
7873
|
+
if (upgradeUrl !== undefined) {
|
|
7874
|
+
result.upgradeUrl = upgradeUrl;
|
|
7851
7875
|
}
|
|
7852
|
-
return
|
|
7876
|
+
return result;
|
|
7853
7877
|
}
|
|
7854
|
-
function
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7878
|
+
function httpSentinel(status, body) {
|
|
7879
|
+
if (status === 400) {
|
|
7880
|
+
const parsed = parseAdvanceErrorBody(body);
|
|
7881
|
+
if (parsed?.error === "sdk_upgrade_required") {
|
|
7882
|
+
return ErrSdkUpgradeRequired;
|
|
7883
|
+
}
|
|
7884
|
+
return ErrBadRequest;
|
|
7860
7885
|
}
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7886
|
+
if (status === 401) {
|
|
7887
|
+
const parsed = parseAdvanceErrorBody(body);
|
|
7888
|
+
if (parsed?.detail === "token_expired") {
|
|
7889
|
+
return ErrTokenExpired;
|
|
7890
|
+
}
|
|
7891
|
+
return ErrInvalidAccessToken;
|
|
7866
7892
|
}
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
function authFailureResult(error) {
|
|
7870
|
-
if (errors12.is(error, ErrAuthConfigInvalid)) {
|
|
7871
|
-
return { kind: "auth-config-invalid", error };
|
|
7893
|
+
if (status === 403) {
|
|
7894
|
+
return ErrForbidden;
|
|
7872
7895
|
}
|
|
7873
|
-
if (
|
|
7874
|
-
return
|
|
7896
|
+
if (status === 404) {
|
|
7897
|
+
return ErrNotFound;
|
|
7875
7898
|
}
|
|
7876
|
-
|
|
7899
|
+
if (status === 409) {
|
|
7900
|
+
return ErrConflict;
|
|
7901
|
+
}
|
|
7902
|
+
if (status === 429) {
|
|
7903
|
+
return ErrRateLimited;
|
|
7904
|
+
}
|
|
7905
|
+
if (status === 502 || status === 503 || status === 504) {
|
|
7906
|
+
return ErrServiceUnavailable;
|
|
7907
|
+
}
|
|
7908
|
+
return ErrServerError;
|
|
7877
7909
|
}
|
|
7878
|
-
|
|
7879
|
-
const
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7910
|
+
function buildSdkUpgradeRequiredError(sentinel, text, logger) {
|
|
7911
|
+
const parsed = parseAdvanceErrorBody(text);
|
|
7912
|
+
if (parsed === null) {
|
|
7913
|
+
logger.error({
|
|
7914
|
+
receivedSdkVersion: null,
|
|
7915
|
+
minimumSdkVersion: "<unknown>",
|
|
7916
|
+
upgradeUrl: NPM_PACKAGE_URL
|
|
7917
|
+
}, "sdk upgrade required");
|
|
7918
|
+
const message2 = `<missing> < <unknown>; bump @superbuilders/primer-tives at ${NPM_PACKAGE_URL}`;
|
|
7919
|
+
return errors12.wrap(sentinel, message2);
|
|
7887
7920
|
}
|
|
7888
|
-
const
|
|
7889
|
-
const
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7921
|
+
const minimumSdkVersion = parsed.minimumSdkVersion === undefined ? "<unknown>" : parsed.minimumSdkVersion;
|
|
7922
|
+
const receivedSdkVersion = parsed.receivedSdkVersion === undefined ? null : parsed.receivedSdkVersion;
|
|
7923
|
+
const receivedDisplay = receivedSdkVersion === null ? "<missing>" : receivedSdkVersion;
|
|
7924
|
+
const upgradeUrl = parsed.upgradeUrl === undefined ? NPM_PACKAGE_URL : parsed.upgradeUrl;
|
|
7925
|
+
logger.error({
|
|
7926
|
+
receivedSdkVersion,
|
|
7927
|
+
minimumSdkVersion,
|
|
7928
|
+
upgradeUrl
|
|
7929
|
+
}, "sdk upgrade required");
|
|
7930
|
+
const message = `${receivedDisplay} < ${minimumSdkVersion}; bump @superbuilders/primer-tives at ${upgradeUrl}`;
|
|
7931
|
+
return errors12.wrap(sentinel, message);
|
|
7932
|
+
}
|
|
7933
|
+
function isAbortError(err) {
|
|
7934
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
7935
|
+
return true;
|
|
7936
|
+
}
|
|
7937
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
7938
|
+
return true;
|
|
7898
7939
|
}
|
|
7899
|
-
return
|
|
7940
|
+
return false;
|
|
7900
7941
|
}
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
function
|
|
7906
|
-
if (
|
|
7907
|
-
return
|
|
7942
|
+
function createTransport(tc) {
|
|
7943
|
+
const fetchFn = tc.fetch ? tc.fetch : globalThis.fetch;
|
|
7944
|
+
const logger = tc.logger;
|
|
7945
|
+
const advanceUrl = new URL(ADVANCE_PATH, tc.origin).toString();
|
|
7946
|
+
function transportSignal() {
|
|
7947
|
+
if (tc.abort) {
|
|
7948
|
+
return tc.abort.signal;
|
|
7908
7949
|
}
|
|
7909
|
-
|
|
7910
|
-
|
|
7950
|
+
return;
|
|
7951
|
+
}
|
|
7952
|
+
async function transport(body) {
|
|
7953
|
+
logger.debug({
|
|
7954
|
+
intentKind: body.intent.kind,
|
|
7955
|
+
subject: body.subject
|
|
7956
|
+
}, "transport request");
|
|
7957
|
+
const fetchResult = await fetchFn(advanceUrl, {
|
|
7958
|
+
method: "POST",
|
|
7959
|
+
mode: "cors",
|
|
7960
|
+
credentials: "omit",
|
|
7961
|
+
headers: {
|
|
7962
|
+
"Content-Type": "application/json",
|
|
7963
|
+
Authorization: `Bearer ${tc.accessToken.value}`,
|
|
7964
|
+
"X-Primer-Publishable-Key": tc.publishableKey,
|
|
7965
|
+
"X-Primer-SDK-Version": SDK_VERSION
|
|
7966
|
+
},
|
|
7967
|
+
body: JSON.stringify(body),
|
|
7968
|
+
signal: transportSignal()
|
|
7969
|
+
}).then(function ok(r) {
|
|
7970
|
+
return { ok: true, response: r };
|
|
7971
|
+
}, function fail(err) {
|
|
7972
|
+
return { ok: false, error: err };
|
|
7911
7973
|
});
|
|
7912
|
-
|
|
7974
|
+
if (!fetchResult.ok) {
|
|
7975
|
+
if (isAbortError(fetchResult.error)) {
|
|
7976
|
+
logger.error({
|
|
7977
|
+
intentKind: body.intent.kind
|
|
7978
|
+
}, "transport timeout");
|
|
7979
|
+
return { ok: false, error: errors12.wrap(ErrTimeout, fetchResult.error.message) };
|
|
7980
|
+
}
|
|
7981
|
+
logger.error({
|
|
7982
|
+
error: fetchResult.error
|
|
7983
|
+
}, "transport network error");
|
|
7984
|
+
return { ok: false, error: errors12.wrap(ErrNetwork, fetchResult.error.message) };
|
|
7985
|
+
}
|
|
7986
|
+
const res = fetchResult.response;
|
|
7987
|
+
if (!res.ok) {
|
|
7988
|
+
const text = await res.text().catch(function fallback() {
|
|
7989
|
+
return "";
|
|
7990
|
+
});
|
|
7991
|
+
const sentinel = httpSentinel(res.status, text);
|
|
7992
|
+
if (errors12.is(sentinel, ErrSdkUpgradeRequired)) {
|
|
7993
|
+
return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
|
|
7994
|
+
}
|
|
7995
|
+
logger.error({
|
|
7996
|
+
status: res.status,
|
|
7997
|
+
body: text,
|
|
7998
|
+
intentKind: body.intent.kind,
|
|
7999
|
+
subject: body.subject
|
|
8000
|
+
}, "transport http error");
|
|
8001
|
+
return { ok: false, error: errors12.wrap(sentinel, text) };
|
|
8002
|
+
}
|
|
8003
|
+
const jsonResult = await res.json().then(function ok(data) {
|
|
8004
|
+
return { ok: true, data };
|
|
8005
|
+
}, function fail(err) {
|
|
8006
|
+
return { ok: false, error: err };
|
|
8007
|
+
});
|
|
8008
|
+
if (!jsonResult.ok) {
|
|
8009
|
+
logger.error({
|
|
8010
|
+
error: jsonResult.error
|
|
8011
|
+
}, "transport json parse failed");
|
|
8012
|
+
return { ok: false, error: errors12.wrap(ErrJsonParse, jsonResult.error.message) };
|
|
8013
|
+
}
|
|
8014
|
+
logger.debug({
|
|
8015
|
+
intentKind: body.intent.kind
|
|
8016
|
+
}, "transport success");
|
|
8017
|
+
return { ok: true, data: jsonResult.data };
|
|
7913
8018
|
}
|
|
7914
|
-
return
|
|
7915
|
-
}
|
|
7916
|
-
function signInRequiredState(config) {
|
|
7917
|
-
return {
|
|
7918
|
-
phase: "sign-in-required",
|
|
7919
|
-
login: pendingLogin(config),
|
|
7920
|
-
toJSON: poisonToJSON
|
|
7921
|
-
};
|
|
7922
|
-
}
|
|
7923
|
-
function signInFailedState(config) {
|
|
7924
|
-
return {
|
|
7925
|
-
phase: "sign-in-failed",
|
|
7926
|
-
error: config.error,
|
|
7927
|
-
login: pendingLogin(config),
|
|
7928
|
-
toJSON: poisonToJSON
|
|
7929
|
-
};
|
|
7930
|
-
}
|
|
7931
|
-
function authUnavailableState(error) {
|
|
7932
|
-
return {
|
|
7933
|
-
phase: "auth-unavailable",
|
|
7934
|
-
error,
|
|
7935
|
-
toJSON: poisonToJSON
|
|
7936
|
-
};
|
|
7937
|
-
}
|
|
7938
|
-
function authConfigInvalidState(error) {
|
|
7939
|
-
return {
|
|
7940
|
-
phase: "auth-config-invalid",
|
|
7941
|
-
error,
|
|
7942
|
-
toJSON: poisonToJSON
|
|
7943
|
-
};
|
|
8019
|
+
return transport;
|
|
7944
8020
|
}
|
|
7945
8021
|
|
|
7946
8022
|
// src/client/start.ts
|
|
@@ -7965,8 +8041,11 @@ function primerOrigin(origin) {
|
|
|
7965
8041
|
}
|
|
7966
8042
|
async function startRuntime(config, resolved) {
|
|
7967
8043
|
let reauthenticate = null;
|
|
7968
|
-
if (resolved.
|
|
7969
|
-
reauthenticate =
|
|
8044
|
+
if (resolved.clearCachedAccessToken !== undefined) {
|
|
8045
|
+
reauthenticate = function reauthenticateManagedAuth(error) {
|
|
8046
|
+
resolved.clearCachedAccessToken?.();
|
|
8047
|
+
return Promise.resolve(makeSignInFailedState(config, error));
|
|
8048
|
+
};
|
|
7970
8049
|
}
|
|
7971
8050
|
const transport = createTransport({
|
|
7972
8051
|
accessToken: resolved.accessToken,
|
|
@@ -8018,9 +8097,7 @@ async function loginAndStart(config) {
|
|
|
8018
8097
|
}
|
|
8019
8098
|
return startRuntime(config, {
|
|
8020
8099
|
accessToken: result.accessToken,
|
|
8021
|
-
|
|
8022
|
-
return Promise.resolve(makeSignInFailedState(config, ErrTokenExpired));
|
|
8023
|
-
}
|
|
8100
|
+
clearCachedAccessToken: result.clearCachedAccessToken
|
|
8024
8101
|
});
|
|
8025
8102
|
}
|
|
8026
8103
|
async function start(options) {
|
|
@@ -8050,6 +8127,9 @@ async function start(options) {
|
|
|
8050
8127
|
toJSON: poisonToJSON
|
|
8051
8128
|
};
|
|
8052
8129
|
}
|
|
8130
|
+
if (accessToken.kind === "auth-unavailable") {
|
|
8131
|
+
return authUnavailableState(accessToken.error);
|
|
8132
|
+
}
|
|
8053
8133
|
if (accessToken.kind === "missing") {
|
|
8054
8134
|
return makeSignInRequiredState(config);
|
|
8055
8135
|
}
|
|
@@ -8059,4 +8139,4 @@ export {
|
|
|
8059
8139
|
start
|
|
8060
8140
|
};
|
|
8061
8141
|
|
|
8062
|
-
//# debugId=
|
|
8142
|
+
//# debugId=8BB175F09E303E4A64756E2164756E21
|