@superbuilders/primer-tives 4.0.5 → 5.0.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 +36 -8
- 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 +2 -2
- package/dist/client/auth/provider.d.ts.map +1 -1
- 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 +3 -3
- package/dist/client/choice-state.d.ts.map +1 -1
- package/dist/client/extended-text-state.d.ts +3 -3
- package/dist/client/extended-text-state.d.ts.map +1 -1
- package/dist/client/feedback-state.d.ts +6 -5
- 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 +883 -737
- package/dist/client/index.js.map +24 -24
- package/dist/client/match-state.d.ts +3 -3
- 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 +3 -3
- package/dist/client/order-state.d.ts.map +1 -1
- package/dist/client/pci-state.d.ts +3 -3
- 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 +4 -4
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/start.d.ts +4 -3
- package/dist/client/start.d.ts.map +1 -1
- package/dist/client/text-entry-state.d.ts +3 -3
- package/dist/client/text-entry-state.d.ts.map +1 -1
- package/dist/client/transport.d.ts +12 -6
- package/dist/client/transport.d.ts.map +1 -1
- package/dist/client/types.d.ts +32 -3
- 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,217 +6779,518 @@ 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.5";
|
|
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
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
}
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
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
|
+
function supportsAsyncDisposableStack() {
|
|
6960
|
+
return typeof Reflect.get(globalThis, "AsyncDisposableStack") === "function";
|
|
6961
|
+
}
|
|
6962
|
+
async function waitForPopupMessageWithAsyncDisposableStack(popup, config, expectedOrigin) {
|
|
6963
|
+
let __stack = [];
|
|
6964
|
+
try {
|
|
6965
|
+
const logger = config.logger;
|
|
6966
|
+
const stack = __using(__stack, new AsyncDisposableStack, 1);
|
|
6967
|
+
stack.defer(function closePopup() {
|
|
6968
|
+
popup.close();
|
|
6969
|
+
});
|
|
6970
|
+
const result = await new Promise(function waitForMessage(resolve, reject) {
|
|
6971
|
+
let settled = false;
|
|
6972
|
+
function finishWithError(error) {
|
|
6973
|
+
if (settled) {
|
|
6974
|
+
return;
|
|
6975
|
+
}
|
|
6976
|
+
settled = true;
|
|
6977
|
+
reject(error);
|
|
6940
6978
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6979
|
+
function finishWithToken(token) {
|
|
6980
|
+
if (settled) {
|
|
6981
|
+
return;
|
|
6982
|
+
}
|
|
6983
|
+
settled = true;
|
|
6984
|
+
logger.debug("hosted auth popup completed");
|
|
6985
|
+
resolve(token);
|
|
6986
|
+
}
|
|
6987
|
+
const timeoutId = globalThis.setTimeout(function timeout() {
|
|
6988
|
+
logger.error("hosted auth popup timed out");
|
|
6989
|
+
finishWithError(ErrAuthCancelled);
|
|
6990
|
+
}, DEFAULT_POPUP_TIMEOUT_MS);
|
|
6991
|
+
stack.defer(function clearPopupTimeout() {
|
|
6992
|
+
globalThis.clearTimeout(timeoutId);
|
|
6950
6993
|
});
|
|
6951
|
-
const
|
|
6952
|
-
|
|
6953
|
-
|
|
6994
|
+
const closedPollId = globalThis.setInterval(function checkClosed() {
|
|
6995
|
+
if (!popup.closed) {
|
|
6996
|
+
return;
|
|
6997
|
+
}
|
|
6998
|
+
logger.error("hosted auth popup closed");
|
|
6999
|
+
finishWithError(ErrAuthCancelled);
|
|
7000
|
+
}, POPUP_POLL_MS);
|
|
7001
|
+
stack.defer(function clearClosedPoll() {
|
|
7002
|
+
globalThis.clearInterval(closedPollId);
|
|
7003
|
+
});
|
|
7004
|
+
function handleMessage(event) {
|
|
7005
|
+
const result2 = readPopupMessage(event, popup, config, expectedOrigin);
|
|
7006
|
+
if (result2.kind === "ignore") {
|
|
7007
|
+
return;
|
|
7008
|
+
}
|
|
7009
|
+
if (result2.kind === "error") {
|
|
7010
|
+
logger.error({ error: result2.error }, "hosted auth popup failed");
|
|
7011
|
+
finishWithError(result2.error);
|
|
7012
|
+
return;
|
|
7013
|
+
}
|
|
7014
|
+
finishWithToken(result2.accessToken);
|
|
6954
7015
|
}
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
7016
|
+
globalThis.addEventListener("message", handleMessage);
|
|
7017
|
+
stack.defer(function removeMessageListener() {
|
|
7018
|
+
globalThis.removeEventListener("message", handleMessage);
|
|
7019
|
+
});
|
|
7020
|
+
});
|
|
7021
|
+
return result;
|
|
7022
|
+
} catch (_catch) {
|
|
7023
|
+
var _err = _catch, _hasErr = 1;
|
|
7024
|
+
} finally {
|
|
7025
|
+
var _promise = __callDispose(__stack, _err, _hasErr);
|
|
7026
|
+
_promise && await _promise;
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
function cleanupManualPopupWait(cleanups) {
|
|
7030
|
+
for (let index = cleanups.length - 1;index >= 0; index -= 1) {
|
|
7031
|
+
const cleanup = cleanups[index];
|
|
7032
|
+
if (cleanup === undefined) {
|
|
7033
|
+
continue;
|
|
6962
7034
|
}
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
7035
|
+
cleanup();
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
async function waitForPopupMessageWithManualCleanup(popup, config, expectedOrigin) {
|
|
7039
|
+
const logger = config.logger;
|
|
7040
|
+
const cleanups = [];
|
|
7041
|
+
cleanups.push(function closePopup() {
|
|
7042
|
+
popup.close();
|
|
7043
|
+
});
|
|
7044
|
+
const pending = new Promise(function waitForMessage(resolve, reject) {
|
|
7045
|
+
let settled = false;
|
|
7046
|
+
function finishWithError(error) {
|
|
7047
|
+
if (settled) {
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
settled = true;
|
|
7051
|
+
reject(error);
|
|
7052
|
+
}
|
|
7053
|
+
function finishWithToken(token) {
|
|
7054
|
+
if (settled) {
|
|
7055
|
+
return;
|
|
7056
|
+
}
|
|
7057
|
+
settled = true;
|
|
7058
|
+
logger.debug("hosted auth popup completed");
|
|
7059
|
+
resolve(token);
|
|
7060
|
+
}
|
|
7061
|
+
const timeoutId = globalThis.setTimeout(function timeout() {
|
|
7062
|
+
logger.error("hosted auth popup timed out");
|
|
7063
|
+
finishWithError(ErrAuthCancelled);
|
|
7064
|
+
}, DEFAULT_POPUP_TIMEOUT_MS);
|
|
7065
|
+
cleanups.push(function clearPopupTimeout() {
|
|
7066
|
+
globalThis.clearTimeout(timeoutId);
|
|
6967
7067
|
});
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
}
|
|
6972
|
-
|
|
7068
|
+
const closedPollId = globalThis.setInterval(function checkClosed() {
|
|
7069
|
+
if (!popup.closed) {
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
logger.error("hosted auth popup closed");
|
|
7073
|
+
finishWithError(ErrAuthCancelled);
|
|
7074
|
+
}, POPUP_POLL_MS);
|
|
7075
|
+
cleanups.push(function clearClosedPoll() {
|
|
7076
|
+
globalThis.clearInterval(closedPollId);
|
|
7077
|
+
});
|
|
7078
|
+
function handleMessage(event) {
|
|
7079
|
+
const result = readPopupMessage(event, popup, config, expectedOrigin);
|
|
7080
|
+
if (result.kind === "ignore") {
|
|
7081
|
+
return;
|
|
7082
|
+
}
|
|
7083
|
+
if (result.kind === "error") {
|
|
7084
|
+
logger.error({ error: result.error }, "hosted auth popup failed");
|
|
7085
|
+
finishWithError(result.error);
|
|
7086
|
+
return;
|
|
7087
|
+
}
|
|
7088
|
+
finishWithToken(result.accessToken);
|
|
6973
7089
|
}
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
7090
|
+
globalThis.addEventListener("message", handleMessage);
|
|
7091
|
+
cleanups.push(function removeMessageListener() {
|
|
7092
|
+
globalThis.removeEventListener("message", handleMessage);
|
|
7093
|
+
});
|
|
7094
|
+
});
|
|
7095
|
+
return pending.then(function cleanupSuccess(token) {
|
|
7096
|
+
cleanupManualPopupWait(cleanups);
|
|
7097
|
+
return token;
|
|
7098
|
+
}, function cleanupFailure(error) {
|
|
7099
|
+
cleanupManualPopupWait(cleanups);
|
|
7100
|
+
throw error;
|
|
7101
|
+
});
|
|
7102
|
+
}
|
|
7103
|
+
async function waitForPopupMessage(popup, config, expectedOrigin) {
|
|
7104
|
+
if (supportsAsyncDisposableStack()) {
|
|
7105
|
+
return waitForPopupMessageWithAsyncDisposableStack(popup, config, expectedOrigin);
|
|
6978
7106
|
}
|
|
6979
|
-
return
|
|
7107
|
+
return waitForPopupMessageWithManualCleanup(popup, config, expectedOrigin);
|
|
7108
|
+
}
|
|
7109
|
+
async function beginHostedPopup(config) {
|
|
7110
|
+
const logger = config.logger;
|
|
7111
|
+
const url = hostedAuthUrl(config);
|
|
7112
|
+
if (!URL.canParse(config.origin)) {
|
|
7113
|
+
logger.error({ origin: config.origin }, "hosted auth origin invalid");
|
|
7114
|
+
throw ErrAuthConfigInvalid;
|
|
7115
|
+
}
|
|
7116
|
+
const expectedOrigin = new URL(config.origin).origin;
|
|
7117
|
+
const popup = openAuthPopup(url, logger);
|
|
7118
|
+
return waitForPopupMessage(popup, config, expectedOrigin);
|
|
6980
7119
|
}
|
|
6981
7120
|
|
|
6982
|
-
// src/client/
|
|
6983
|
-
|
|
7121
|
+
// src/client/auth/storage.ts
|
|
7122
|
+
var MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX = "primer:access-token";
|
|
7123
|
+
function managedAuthAccessTokenStorageKey(publishableKey) {
|
|
7124
|
+
return `${MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX}:${publishableKey}`;
|
|
7125
|
+
}
|
|
7126
|
+
function managedAuthStorage(logger) {
|
|
7127
|
+
if (typeof globalThis.sessionStorage === "undefined") {
|
|
7128
|
+
logger.error("managed auth storage unavailable");
|
|
7129
|
+
throw ErrAuthUnavailable;
|
|
7130
|
+
}
|
|
7131
|
+
return globalThis.sessionStorage;
|
|
7132
|
+
}
|
|
7133
|
+
function loadManagedAuthAccessToken(storage, publishableKey) {
|
|
7134
|
+
const token = storage.getItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7135
|
+
if (token === null || token.length === 0) {
|
|
7136
|
+
return null;
|
|
7137
|
+
}
|
|
7138
|
+
return token;
|
|
7139
|
+
}
|
|
7140
|
+
function storeManagedAuthAccessToken(storage, publishableKey, accessToken) {
|
|
7141
|
+
storage.setItem(managedAuthAccessTokenStorageKey(publishableKey), accessToken);
|
|
7142
|
+
}
|
|
7143
|
+
function clearManagedAuthAccessToken(storage, publishableKey) {
|
|
7144
|
+
storage.removeItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7145
|
+
}
|
|
7146
|
+
|
|
7147
|
+
// src/client/auth/provider.ts
|
|
7148
|
+
function resolveProvidedAccessToken(token, logger) {
|
|
7149
|
+
const result = errors4.trySync(function resolveProvidedToken() {
|
|
7150
|
+
return resolveAccessToken(token, logger);
|
|
7151
|
+
});
|
|
7152
|
+
if (result.error) {
|
|
7153
|
+
return { kind: "fatal", error: result.error };
|
|
7154
|
+
}
|
|
7155
|
+
return { kind: "resolved", accessToken: result.data };
|
|
7156
|
+
}
|
|
7157
|
+
function resolveHostedAccessToken(token, storage, options) {
|
|
7158
|
+
const result = errors4.trySync(function resolveHostedToken() {
|
|
7159
|
+
return resolveAccessToken(token, options.logger);
|
|
7160
|
+
});
|
|
7161
|
+
if (result.error) {
|
|
7162
|
+
return { kind: "sign-in-failed", error: result.error };
|
|
7163
|
+
}
|
|
7164
|
+
storeManagedAuthAccessToken(storage, options.publishableKey, token);
|
|
7165
|
+
return {
|
|
7166
|
+
kind: "resolved",
|
|
7167
|
+
accessToken: result.data,
|
|
7168
|
+
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7169
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7170
|
+
}
|
|
7171
|
+
};
|
|
7172
|
+
}
|
|
7173
|
+
function resolveStoredAccessToken(storage, options) {
|
|
7174
|
+
const stored = loadManagedAuthAccessToken(storage, options.publishableKey);
|
|
7175
|
+
if (stored === null) {
|
|
7176
|
+
return { kind: "missing" };
|
|
7177
|
+
}
|
|
7178
|
+
const result = errors4.trySync(function resolveStoredToken() {
|
|
7179
|
+
return resolveAccessToken(stored, options.logger);
|
|
7180
|
+
});
|
|
7181
|
+
if (result.error) {
|
|
7182
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7183
|
+
return { kind: "missing" };
|
|
7184
|
+
}
|
|
7185
|
+
return {
|
|
7186
|
+
kind: "resolved",
|
|
7187
|
+
accessToken: result.data,
|
|
7188
|
+
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7189
|
+
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7190
|
+
}
|
|
7191
|
+
};
|
|
7192
|
+
}
|
|
7193
|
+
function resolveExistingAccessToken(options) {
|
|
7194
|
+
if (options.accessToken !== undefined) {
|
|
7195
|
+
return resolveProvidedAccessToken(options.accessToken, options.logger);
|
|
7196
|
+
}
|
|
7197
|
+
const storageResult = errors4.trySync(function readManagedAuthStorage() {
|
|
7198
|
+
return managedAuthStorage(options.logger);
|
|
7199
|
+
});
|
|
7200
|
+
if (storageResult.error) {
|
|
7201
|
+
return { kind: "auth-unavailable", error: storageResult.error };
|
|
7202
|
+
}
|
|
7203
|
+
return resolveStoredAccessToken(storageResult.data, options);
|
|
7204
|
+
}
|
|
7205
|
+
function authFailureResult(error) {
|
|
7206
|
+
if (errors4.is(error, ErrAuthConfigInvalid)) {
|
|
7207
|
+
return { kind: "auth-config-invalid", error };
|
|
7208
|
+
}
|
|
7209
|
+
if (errors4.is(error, ErrAuthUnavailable)) {
|
|
7210
|
+
return { kind: "auth-unavailable", error };
|
|
7211
|
+
}
|
|
7212
|
+
return { kind: "sign-in-failed", error };
|
|
7213
|
+
}
|
|
7214
|
+
async function beginHostedLogin(options) {
|
|
7215
|
+
const logger = options.logger;
|
|
7216
|
+
const contextResult = errors4.trySync(function readContext() {
|
|
7217
|
+
const storage = managedAuthStorage(logger);
|
|
7218
|
+
const url = currentUrl(logger);
|
|
7219
|
+
const clientState = randomClientState(logger);
|
|
7220
|
+
return { storage, url, clientState };
|
|
7221
|
+
});
|
|
7222
|
+
if (contextResult.error) {
|
|
7223
|
+
return authFailureResult(contextResult.error);
|
|
7224
|
+
}
|
|
7225
|
+
const context = contextResult.data;
|
|
7226
|
+
const accessTokenResult = await errors4.try(beginHostedPopup({
|
|
7227
|
+
origin: options.origin,
|
|
7228
|
+
publishableKey: options.publishableKey,
|
|
7229
|
+
currentUrl: context.url,
|
|
7230
|
+
clientState: context.clientState,
|
|
7231
|
+
logger
|
|
7232
|
+
}));
|
|
7233
|
+
if (accessTokenResult.error) {
|
|
7234
|
+
return authFailureResult(accessTokenResult.error);
|
|
7235
|
+
}
|
|
7236
|
+
return resolveHostedAccessToken(accessTokenResult.data, context.storage, options);
|
|
7237
|
+
}
|
|
6984
7238
|
|
|
6985
7239
|
// src/client/consumed.ts
|
|
6986
7240
|
function poisonToJSON() {
|
|
6987
7241
|
throw ErrNotSerializable;
|
|
6988
7242
|
}
|
|
6989
7243
|
|
|
7244
|
+
// src/client/auth-state.ts
|
|
7245
|
+
function pendingLogin(config) {
|
|
7246
|
+
let pending;
|
|
7247
|
+
function login() {
|
|
7248
|
+
if (pending !== undefined) {
|
|
7249
|
+
return pending;
|
|
7250
|
+
}
|
|
7251
|
+
pending = config.login().finally(function clearPending() {
|
|
7252
|
+
pending = undefined;
|
|
7253
|
+
});
|
|
7254
|
+
return pending;
|
|
7255
|
+
}
|
|
7256
|
+
return login;
|
|
7257
|
+
}
|
|
7258
|
+
function signInRequiredState(config) {
|
|
7259
|
+
return {
|
|
7260
|
+
phase: "sign-in-required",
|
|
7261
|
+
login: pendingLogin(config),
|
|
7262
|
+
toJSON: poisonToJSON
|
|
7263
|
+
};
|
|
7264
|
+
}
|
|
7265
|
+
function signInFailedState(config) {
|
|
7266
|
+
return {
|
|
7267
|
+
phase: "sign-in-failed",
|
|
7268
|
+
error: config.error,
|
|
7269
|
+
login: pendingLogin(config),
|
|
7270
|
+
toJSON: poisonToJSON
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
7273
|
+
function authUnavailableState(error) {
|
|
7274
|
+
return {
|
|
7275
|
+
phase: "auth-unavailable",
|
|
7276
|
+
error,
|
|
7277
|
+
toJSON: poisonToJSON
|
|
7278
|
+
};
|
|
7279
|
+
}
|
|
7280
|
+
function authConfigInvalidState(error) {
|
|
7281
|
+
return {
|
|
7282
|
+
phase: "auth-config-invalid",
|
|
7283
|
+
error,
|
|
7284
|
+
toJSON: poisonToJSON
|
|
7285
|
+
};
|
|
7286
|
+
}
|
|
7287
|
+
|
|
7288
|
+
// src/client/session.ts
|
|
7289
|
+
import * as errors11 from "@superbuilders/errors";
|
|
7290
|
+
|
|
6990
7291
|
// src/client/choice-state.ts
|
|
6991
|
-
import * as
|
|
6992
|
-
function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minChoices) {
|
|
7292
|
+
import * as errors5 from "@superbuilders/errors";
|
|
7293
|
+
function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minChoices, feedback) {
|
|
6993
7294
|
let submitPending;
|
|
6994
7295
|
let submitKey;
|
|
6995
7296
|
let timeoutPending;
|
|
@@ -6997,18 +7298,18 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
6997
7298
|
const submission = { type: "choice", selectedKeys };
|
|
6998
7299
|
const key = JSON.stringify(submission);
|
|
6999
7300
|
if (timeoutPending) {
|
|
7000
|
-
return Promise.resolve(ctx.errored(
|
|
7301
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7001
7302
|
}
|
|
7002
7303
|
if (submitPending) {
|
|
7003
7304
|
if (submitKey === key) {
|
|
7004
7305
|
return submitPending;
|
|
7005
7306
|
}
|
|
7006
|
-
return Promise.resolve(ctx.errored(
|
|
7307
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot submit a different choice payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7007
7308
|
}
|
|
7008
7309
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7009
7310
|
if (!validation.ok) {
|
|
7010
7311
|
ctx.logger.error({ selectedKeys, issues: validation.issues }, "choice submit invalid");
|
|
7011
|
-
return Promise.resolve(ctx.errored(
|
|
7312
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7012
7313
|
}
|
|
7013
7314
|
submitKey = key;
|
|
7014
7315
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7020,7 +7321,7 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
7020
7321
|
function beginTimeout() {
|
|
7021
7322
|
const intent = { kind: "timeout" };
|
|
7022
7323
|
if (submitPending) {
|
|
7023
|
-
return Promise.resolve(ctx.errored(
|
|
7324
|
+
return Promise.resolve(ctx.errored(errors5.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7024
7325
|
}
|
|
7025
7326
|
if (timeoutPending) {
|
|
7026
7327
|
return timeoutPending;
|
|
@@ -7036,6 +7337,7 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
7036
7337
|
body,
|
|
7037
7338
|
stimulus,
|
|
7038
7339
|
interaction,
|
|
7340
|
+
feedback,
|
|
7039
7341
|
options,
|
|
7040
7342
|
maxChoices,
|
|
7041
7343
|
minChoices,
|
|
@@ -7046,25 +7348,25 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
|
|
|
7046
7348
|
}
|
|
7047
7349
|
|
|
7048
7350
|
// src/client/extended-text-state.ts
|
|
7049
|
-
import * as
|
|
7050
|
-
function extendedTextState(ctx, body, stimulus, interaction) {
|
|
7351
|
+
import * as errors6 from "@superbuilders/errors";
|
|
7352
|
+
function extendedTextState(ctx, body, stimulus, interaction, feedback) {
|
|
7051
7353
|
if (interaction.cardinality === "single") {
|
|
7052
7354
|
let submitText = function(value) {
|
|
7053
7355
|
const submission = { type: "extended-text", values: [value] };
|
|
7054
7356
|
const key = JSON.stringify(submission);
|
|
7055
7357
|
if (timeoutPending2) {
|
|
7056
|
-
return Promise.resolve(ctx.errored(
|
|
7358
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7057
7359
|
}
|
|
7058
7360
|
if (submitPending2) {
|
|
7059
7361
|
if (submitKey2 === key) {
|
|
7060
7362
|
return submitPending2;
|
|
7061
7363
|
}
|
|
7062
|
-
return Promise.resolve(ctx.errored(
|
|
7364
|
+
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
7365
|
}
|
|
7064
7366
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7065
7367
|
if (!validation.ok) {
|
|
7066
7368
|
ctx.logger.error({ value, issues: validation.issues }, "extended-text submit invalid");
|
|
7067
|
-
return Promise.resolve(ctx.errored(
|
|
7369
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7068
7370
|
}
|
|
7069
7371
|
submitKey2 = key;
|
|
7070
7372
|
submitPending2 = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7075,7 +7377,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7075
7377
|
}, timeout2 = function() {
|
|
7076
7378
|
const intent = { kind: "timeout" };
|
|
7077
7379
|
if (submitPending2) {
|
|
7078
|
-
return Promise.resolve(ctx.errored(
|
|
7380
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7079
7381
|
}
|
|
7080
7382
|
if (timeoutPending2) {
|
|
7081
7383
|
return timeoutPending2;
|
|
@@ -7095,6 +7397,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7095
7397
|
body,
|
|
7096
7398
|
stimulus,
|
|
7097
7399
|
interaction,
|
|
7400
|
+
feedback,
|
|
7098
7401
|
submitText,
|
|
7099
7402
|
timeout: timeout2,
|
|
7100
7403
|
toJSON: poisonToJSON
|
|
@@ -7108,18 +7411,18 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7108
7411
|
const submission = { type: "extended-text", values };
|
|
7109
7412
|
const key = JSON.stringify(submission);
|
|
7110
7413
|
if (timeoutPending) {
|
|
7111
|
-
return Promise.resolve(ctx.errored(
|
|
7414
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7112
7415
|
}
|
|
7113
7416
|
if (submitPending) {
|
|
7114
7417
|
if (submitKey === key) {
|
|
7115
7418
|
return submitPending;
|
|
7116
7419
|
}
|
|
7117
|
-
return Promise.resolve(ctx.errored(
|
|
7420
|
+
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
7421
|
}
|
|
7119
7422
|
const validation = validateSubmissionForInteraction(multi, submission);
|
|
7120
7423
|
if (!validation.ok) {
|
|
7121
7424
|
ctx.logger.error({ values, issues: validation.issues }, "extended-text submit invalid");
|
|
7122
|
-
return Promise.resolve(ctx.errored(
|
|
7425
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7123
7426
|
}
|
|
7124
7427
|
submitKey = key;
|
|
7125
7428
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7131,7 +7434,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7131
7434
|
function timeout() {
|
|
7132
7435
|
const intent = { kind: "timeout" };
|
|
7133
7436
|
if (submitPending) {
|
|
7134
|
-
return Promise.resolve(ctx.errored(
|
|
7437
|
+
return Promise.resolve(ctx.errored(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7135
7438
|
}
|
|
7136
7439
|
if (timeoutPending) {
|
|
7137
7440
|
return timeoutPending;
|
|
@@ -7148,6 +7451,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7148
7451
|
body,
|
|
7149
7452
|
stimulus,
|
|
7150
7453
|
interaction: multi,
|
|
7454
|
+
feedback,
|
|
7151
7455
|
maxStrings: multi.maxStrings,
|
|
7152
7456
|
minStrings: multi.minStrings,
|
|
7153
7457
|
submitTexts,
|
|
@@ -7157,31 +7461,47 @@ function extendedTextState(ctx, body, stimulus, interaction) {
|
|
|
7157
7461
|
}
|
|
7158
7462
|
|
|
7159
7463
|
// src/client/feedback-state.ts
|
|
7160
|
-
function
|
|
7464
|
+
function createAdvance(ctx) {
|
|
7161
7465
|
let pending;
|
|
7466
|
+
return function advance() {
|
|
7467
|
+
if (pending) {
|
|
7468
|
+
return pending;
|
|
7469
|
+
}
|
|
7470
|
+
pending = ctx.execute({ kind: "observation" }, "observation");
|
|
7471
|
+
return pending;
|
|
7472
|
+
};
|
|
7473
|
+
}
|
|
7474
|
+
function submittedFeedbackState(ctx, body, stimulus, interaction, submission, assessmentOutcome, feedbackContent, review) {
|
|
7162
7475
|
return {
|
|
7163
7476
|
phase: "feedback",
|
|
7477
|
+
feedbackKind: "submitted",
|
|
7164
7478
|
body,
|
|
7165
7479
|
stimulus,
|
|
7166
7480
|
interaction,
|
|
7167
7481
|
submission,
|
|
7168
|
-
|
|
7482
|
+
assessmentOutcome,
|
|
7169
7483
|
feedbackContent,
|
|
7170
7484
|
review,
|
|
7171
|
-
advance:
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7485
|
+
advance: createAdvance(ctx),
|
|
7486
|
+
toJSON: poisonToJSON
|
|
7487
|
+
};
|
|
7488
|
+
}
|
|
7489
|
+
function timedOutFeedbackState(ctx, body, stimulus, interaction, feedbackContent) {
|
|
7490
|
+
return {
|
|
7491
|
+
phase: "feedback",
|
|
7492
|
+
feedbackKind: "timedOut",
|
|
7493
|
+
body,
|
|
7494
|
+
stimulus,
|
|
7495
|
+
interaction,
|
|
7496
|
+
feedbackContent,
|
|
7497
|
+
advance: createAdvance(ctx),
|
|
7178
7498
|
toJSON: poisonToJSON
|
|
7179
7499
|
};
|
|
7180
7500
|
}
|
|
7181
7501
|
|
|
7182
7502
|
// src/client/match-state.ts
|
|
7183
|
-
import * as
|
|
7184
|
-
function matchState(ctx, body, stimulus, interaction) {
|
|
7503
|
+
import * as errors7 from "@superbuilders/errors";
|
|
7504
|
+
function matchState(ctx, body, stimulus, interaction, feedback) {
|
|
7185
7505
|
let submitPending;
|
|
7186
7506
|
let submitKey;
|
|
7187
7507
|
let timeoutPending;
|
|
@@ -7189,18 +7509,18 @@ function matchState(ctx, body, stimulus, interaction) {
|
|
|
7189
7509
|
const submission = { type: "match", pairs };
|
|
7190
7510
|
const key = JSON.stringify(submission);
|
|
7191
7511
|
if (timeoutPending) {
|
|
7192
|
-
return Promise.resolve(ctx.errored(
|
|
7512
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7193
7513
|
}
|
|
7194
7514
|
if (submitPending) {
|
|
7195
7515
|
if (submitKey === key) {
|
|
7196
7516
|
return submitPending;
|
|
7197
7517
|
}
|
|
7198
|
-
return Promise.resolve(ctx.errored(
|
|
7518
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different match payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7199
7519
|
}
|
|
7200
7520
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7201
7521
|
if (!validation.ok) {
|
|
7202
7522
|
ctx.logger.error({ pairs, issues: validation.issues }, "match submit invalid");
|
|
7203
|
-
return Promise.resolve(ctx.errored(
|
|
7523
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7204
7524
|
}
|
|
7205
7525
|
submitKey = key;
|
|
7206
7526
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7212,7 +7532,7 @@ function matchState(ctx, body, stimulus, interaction) {
|
|
|
7212
7532
|
function timeout() {
|
|
7213
7533
|
const intent = { kind: "timeout" };
|
|
7214
7534
|
if (submitPending) {
|
|
7215
|
-
return Promise.resolve(ctx.errored(
|
|
7535
|
+
return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7216
7536
|
}
|
|
7217
7537
|
if (timeoutPending) {
|
|
7218
7538
|
return timeoutPending;
|
|
@@ -7228,6 +7548,7 @@ function matchState(ctx, body, stimulus, interaction) {
|
|
|
7228
7548
|
body,
|
|
7229
7549
|
stimulus,
|
|
7230
7550
|
interaction,
|
|
7551
|
+
feedback,
|
|
7231
7552
|
sourceChoices: interaction.sourceChoices,
|
|
7232
7553
|
targetChoices: interaction.targetChoices,
|
|
7233
7554
|
minAssociations: interaction.minAssociations,
|
|
@@ -7257,8 +7578,8 @@ function observationState(ctx, body, stimulus) {
|
|
|
7257
7578
|
}
|
|
7258
7579
|
|
|
7259
7580
|
// src/client/order-state.ts
|
|
7260
|
-
import * as
|
|
7261
|
-
function orderState(ctx, body, stimulus, interaction) {
|
|
7581
|
+
import * as errors8 from "@superbuilders/errors";
|
|
7582
|
+
function orderState(ctx, body, stimulus, interaction, feedback) {
|
|
7262
7583
|
let submitPending;
|
|
7263
7584
|
let submitKey;
|
|
7264
7585
|
let timeoutPending;
|
|
@@ -7266,18 +7587,18 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7266
7587
|
const submission = { type: "order", orderedKeys };
|
|
7267
7588
|
const key = JSON.stringify(submission);
|
|
7268
7589
|
if (timeoutPending) {
|
|
7269
|
-
return Promise.resolve(ctx.errored(
|
|
7590
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7270
7591
|
}
|
|
7271
7592
|
if (submitPending) {
|
|
7272
7593
|
if (submitKey === key) {
|
|
7273
7594
|
return submitPending;
|
|
7274
7595
|
}
|
|
7275
|
-
return Promise.resolve(ctx.errored(
|
|
7596
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot submit a different order payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7276
7597
|
}
|
|
7277
7598
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7278
7599
|
if (!validation.ok) {
|
|
7279
7600
|
ctx.logger.error({ orderedKeys, issues: validation.issues }, "order submit invalid");
|
|
7280
|
-
return Promise.resolve(ctx.errored(
|
|
7601
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7281
7602
|
}
|
|
7282
7603
|
submitKey = key;
|
|
7283
7604
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7289,7 +7610,7 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7289
7610
|
function timeout() {
|
|
7290
7611
|
const intent = { kind: "timeout" };
|
|
7291
7612
|
if (submitPending) {
|
|
7292
|
-
return Promise.resolve(ctx.errored(
|
|
7613
|
+
return Promise.resolve(ctx.errored(errors8.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7293
7614
|
}
|
|
7294
7615
|
if (timeoutPending) {
|
|
7295
7616
|
return timeoutPending;
|
|
@@ -7305,6 +7626,7 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7305
7626
|
body,
|
|
7306
7627
|
stimulus,
|
|
7307
7628
|
interaction,
|
|
7629
|
+
feedback,
|
|
7308
7630
|
choices: interaction.choices,
|
|
7309
7631
|
minChoices: interaction.minChoices,
|
|
7310
7632
|
maxChoices: interaction.maxChoices,
|
|
@@ -7315,8 +7637,8 @@ function orderState(ctx, body, stimulus, interaction) {
|
|
|
7315
7637
|
}
|
|
7316
7638
|
|
|
7317
7639
|
// src/client/pci-state.ts
|
|
7318
|
-
import * as
|
|
7319
|
-
function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
7640
|
+
import * as errors9 from "@superbuilders/errors";
|
|
7641
|
+
function pciInteractionState(ctx, body, stimulus, interaction, feedback) {
|
|
7320
7642
|
let submitPending;
|
|
7321
7643
|
let submitKey;
|
|
7322
7644
|
let timeoutPending;
|
|
@@ -7325,13 +7647,13 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7325
7647
|
const submission = { type: "portable-custom", pciId, value };
|
|
7326
7648
|
const key = JSON.stringify(submission);
|
|
7327
7649
|
if (timeoutPending) {
|
|
7328
|
-
return Promise.resolve(ctx.errored(
|
|
7650
|
+
return Promise.resolve(ctx.errored(errors9.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7329
7651
|
}
|
|
7330
7652
|
if (submitPending) {
|
|
7331
7653
|
if (submitKey === key) {
|
|
7332
7654
|
return submitPending;
|
|
7333
7655
|
}
|
|
7334
|
-
return Promise.resolve(ctx.errored(
|
|
7656
|
+
return Promise.resolve(ctx.errored(errors9.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7335
7657
|
}
|
|
7336
7658
|
ctx.logger.debug({ pciId }, "pci submit");
|
|
7337
7659
|
submitKey = key;
|
|
@@ -7344,7 +7666,7 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7344
7666
|
function timeout() {
|
|
7345
7667
|
const intent = { kind: "timeout" };
|
|
7346
7668
|
if (submitPending) {
|
|
7347
|
-
return Promise.resolve(ctx.errored(
|
|
7669
|
+
return Promise.resolve(ctx.errored(errors9.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7348
7670
|
}
|
|
7349
7671
|
if (timeoutPending) {
|
|
7350
7672
|
return timeoutPending;
|
|
@@ -7361,6 +7683,7 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7361
7683
|
body,
|
|
7362
7684
|
stimulus,
|
|
7363
7685
|
interaction,
|
|
7686
|
+
feedback,
|
|
7364
7687
|
pciId,
|
|
7365
7688
|
properties,
|
|
7366
7689
|
submit,
|
|
@@ -7370,8 +7693,8 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
|
|
|
7370
7693
|
}
|
|
7371
7694
|
|
|
7372
7695
|
// src/client/text-entry-state.ts
|
|
7373
|
-
import * as
|
|
7374
|
-
function textEntryState(ctx, body, stimulus, interaction) {
|
|
7696
|
+
import * as errors10 from "@superbuilders/errors";
|
|
7697
|
+
function textEntryState(ctx, body, stimulus, interaction, feedback) {
|
|
7375
7698
|
let submitPending;
|
|
7376
7699
|
let submitKey;
|
|
7377
7700
|
let timeoutPending;
|
|
@@ -7379,18 +7702,18 @@ function textEntryState(ctx, body, stimulus, interaction) {
|
|
|
7379
7702
|
const submission = { type: "text-entry", value };
|
|
7380
7703
|
const key = JSON.stringify(submission);
|
|
7381
7704
|
if (timeoutPending) {
|
|
7382
|
-
return Promise.resolve(ctx.errored(
|
|
7705
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7383
7706
|
}
|
|
7384
7707
|
if (submitPending) {
|
|
7385
7708
|
if (submitKey === key) {
|
|
7386
7709
|
return submitPending;
|
|
7387
7710
|
}
|
|
7388
|
-
return Promise.resolve(ctx.errored(
|
|
7711
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrConflict, "cannot submit a different text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
|
|
7389
7712
|
}
|
|
7390
7713
|
const validation = validateSubmissionForInteraction(interaction, submission);
|
|
7391
7714
|
if (!validation.ok) {
|
|
7392
7715
|
ctx.logger.error({ value, issues: validation.issues }, "text-entry submit invalid");
|
|
7393
|
-
return Promise.resolve(ctx.errored(
|
|
7716
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
|
|
7394
7717
|
}
|
|
7395
7718
|
submitKey = key;
|
|
7396
7719
|
submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
|
|
@@ -7402,7 +7725,7 @@ function textEntryState(ctx, body, stimulus, interaction) {
|
|
|
7402
7725
|
function timeout() {
|
|
7403
7726
|
const intent = { kind: "timeout" };
|
|
7404
7727
|
if (submitPending) {
|
|
7405
|
-
return Promise.resolve(ctx.errored(
|
|
7728
|
+
return Promise.resolve(ctx.errored(errors10.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
|
|
7406
7729
|
}
|
|
7407
7730
|
if (timeoutPending) {
|
|
7408
7731
|
return timeoutPending;
|
|
@@ -7418,6 +7741,7 @@ function textEntryState(ctx, body, stimulus, interaction) {
|
|
|
7418
7741
|
body,
|
|
7419
7742
|
stimulus,
|
|
7420
7743
|
interaction,
|
|
7744
|
+
feedback,
|
|
7421
7745
|
submitText,
|
|
7422
7746
|
timeout,
|
|
7423
7747
|
toJSON: poisonToJSON
|
|
@@ -7436,571 +7760,386 @@ var FATAL_SENTINELS = [
|
|
|
7436
7760
|
];
|
|
7437
7761
|
function isFatalError(err) {
|
|
7438
7762
|
for (const sentinel of FATAL_SENTINELS) {
|
|
7439
|
-
if (
|
|
7763
|
+
if (errors11.is(err, sentinel)) {
|
|
7440
7764
|
return true;
|
|
7441
7765
|
}
|
|
7442
7766
|
}
|
|
7443
7767
|
return false;
|
|
7444
7768
|
}
|
|
7445
7769
|
function isRetriableError(err) {
|
|
7446
|
-
return !
|
|
7770
|
+
return !errors11.is(err, ErrInvalidSubmission);
|
|
7447
7771
|
}
|
|
7448
7772
|
function makeSession(sc) {
|
|
7449
7773
|
const logger = sc.logger;
|
|
7450
7774
|
function resolve(result) {
|
|
7451
7775
|
switch (result.outcome) {
|
|
7452
7776
|
case "advanced":
|
|
7453
|
-
return fromAdvanced(result.frame.body, result.frame.stimulus, result.frame.interaction);
|
|
7777
|
+
return fromAdvanced(result.frame.body, result.frame.stimulus, result.frame.interaction, result.frame.feedback);
|
|
7454
7778
|
case "submitted": {
|
|
7455
7779
|
const interaction = result.frame.interaction;
|
|
7456
7780
|
if (interaction === null) {
|
|
7457
7781
|
logger.error("submitted result without interaction");
|
|
7458
|
-
return {
|
|
7459
|
-
phase: "fatal",
|
|
7460
|
-
error:
|
|
7461
|
-
retriable: false,
|
|
7462
|
-
toJSON: poisonToJSON
|
|
7463
|
-
};
|
|
7464
|
-
}
|
|
7465
|
-
return feedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
|
|
7466
|
-
}
|
|
7467
|
-
case "completed":
|
|
7468
|
-
return { phase: "completed", toJSON: poisonToJSON };
|
|
7469
|
-
}
|
|
7470
|
-
}
|
|
7471
|
-
function errored(error, failedPhase, intent) {
|
|
7472
|
-
let pending;
|
|
7473
|
-
const retriable = isRetriableError(error);
|
|
7474
|
-
if (!retriable) {
|
|
7475
|
-
return {
|
|
7476
|
-
phase: "errored",
|
|
7477
|
-
error,
|
|
7478
|
-
retriable: false,
|
|
7479
|
-
toJSON: poisonToJSON
|
|
7480
|
-
};
|
|
7481
|
-
}
|
|
7482
|
-
const state = {
|
|
7483
|
-
phase: "errored",
|
|
7484
|
-
error,
|
|
7485
|
-
retriable: true,
|
|
7486
|
-
retry: function retry() {
|
|
7487
|
-
if (pending) {
|
|
7488
|
-
return pending;
|
|
7489
|
-
}
|
|
7490
|
-
logger.debug({ failedPhase }, "retrying from errored state");
|
|
7491
|
-
pending = execute(intent, failedPhase);
|
|
7492
|
-
return pending;
|
|
7493
|
-
},
|
|
7494
|
-
toJSON: poisonToJSON
|
|
7495
|
-
};
|
|
7496
|
-
return state;
|
|
7497
|
-
}
|
|
7498
|
-
async function execute(intent, phase) {
|
|
7499
|
-
logger.debug({
|
|
7500
|
-
phase,
|
|
7501
|
-
intentKind: intent.kind,
|
|
7502
|
-
subject: sc.subject
|
|
7503
|
-
}, "session execute");
|
|
7504
|
-
const body = {
|
|
7505
|
-
subject: sc.subject,
|
|
7506
|
-
intent
|
|
7507
|
-
};
|
|
7508
|
-
const result = await sc.transport(body);
|
|
7509
|
-
if (!result.ok) {
|
|
7510
|
-
if ((errors10.is(result.error, ErrTokenExpired) || errors10.is(result.error, ErrInvalidAccessToken)) && sc.reauthenticate !== null) {
|
|
7511
|
-
logger.warn({ phase, intentKind: intent.kind }, "session token rejected");
|
|
7512
|
-
return sc.reauthenticate(result.error);
|
|
7513
|
-
}
|
|
7514
|
-
if (isFatalError(result.error)) {
|
|
7515
|
-
logger.error({
|
|
7516
|
-
error: result.error,
|
|
7517
|
-
phase,
|
|
7518
|
-
intentKind: intent.kind
|
|
7519
|
-
}, "fatal transport error");
|
|
7520
|
-
return {
|
|
7521
|
-
phase: "fatal",
|
|
7522
|
-
error: result.error,
|
|
7523
|
-
retriable: false,
|
|
7524
|
-
toJSON: poisonToJSON
|
|
7525
|
-
};
|
|
7526
|
-
}
|
|
7527
|
-
logger.warn({
|
|
7528
|
-
error: result.error,
|
|
7529
|
-
phase,
|
|
7530
|
-
intentKind: intent.kind
|
|
7531
|
-
}, "retriable transport error");
|
|
7532
|
-
return errored(result.error, phase, intent);
|
|
7533
|
-
}
|
|
7534
|
-
const next = resolve(result.data);
|
|
7535
|
-
logger.debug({
|
|
7536
|
-
phase,
|
|
7537
|
-
intentKind: intent.kind,
|
|
7538
|
-
outcome: result.data.outcome,
|
|
7539
|
-
nextPhase: next.phase
|
|
7540
|
-
}, "session execute resolved");
|
|
7541
|
-
return next;
|
|
7542
|
-
}
|
|
7543
|
-
function isPciSupported(pciId) {
|
|
7544
|
-
for (const id of sc.supportedPcis) {
|
|
7545
|
-
if (id === pciId) {
|
|
7546
|
-
return true;
|
|
7547
|
-
}
|
|
7548
|
-
}
|
|
7549
|
-
return false;
|
|
7550
|
-
}
|
|
7551
|
-
function fromAdvanced(body, stimulus, interaction) {
|
|
7552
|
-
if (interaction === null) {
|
|
7553
|
-
return observationState(ctx, body, stimulus);
|
|
7554
|
-
}
|
|
7555
|
-
if (interaction.type === "portable-custom") {
|
|
7556
|
-
if (!isPciSupported(interaction.pciId)) {
|
|
7557
|
-
logger.error({ pciId: interaction.pciId }, "unsupported pci in frame");
|
|
7558
|
-
return {
|
|
7559
|
-
phase: "fatal",
|
|
7560
|
-
error: errors10.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
|
|
7561
|
-
retriable: false,
|
|
7562
|
-
toJSON: poisonToJSON
|
|
7563
|
-
};
|
|
7564
|
-
}
|
|
7565
|
-
}
|
|
7566
|
-
return pendingInteractionState(body, stimulus, interaction);
|
|
7567
|
-
}
|
|
7568
|
-
function pendingInteractionState(body, stimulus, interaction) {
|
|
7569
|
-
switch (interaction.type) {
|
|
7570
|
-
case "choice":
|
|
7571
|
-
return choiceState(ctx, body, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices);
|
|
7572
|
-
case "text-entry":
|
|
7573
|
-
return textEntryState(ctx, body, stimulus, interaction);
|
|
7574
|
-
case "extended-text":
|
|
7575
|
-
return extendedTextState(ctx, body, stimulus, interaction);
|
|
7576
|
-
case "order":
|
|
7577
|
-
return orderState(ctx, body, stimulus, interaction);
|
|
7578
|
-
case "match":
|
|
7579
|
-
return matchState(ctx, body, stimulus, interaction);
|
|
7580
|
-
case "portable-custom":
|
|
7581
|
-
return pciInteractionState(ctx, body, stimulus, interaction);
|
|
7582
|
-
}
|
|
7583
|
-
}
|
|
7584
|
-
const ctx = { logger, execute, errored };
|
|
7585
|
-
return { execute };
|
|
7586
|
-
}
|
|
7587
|
-
|
|
7588
|
-
// src/client/auth/provider.ts
|
|
7589
|
-
import * as errors12 from "@superbuilders/errors";
|
|
7590
|
-
|
|
7591
|
-
// src/client/auth/browser.ts
|
|
7592
|
-
var POPUP_TARGET = "primer-auth";
|
|
7593
|
-
var POPUP_FEATURES = "popup,width=480,height=720";
|
|
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;
|
|
7782
|
+
return {
|
|
7783
|
+
phase: "fatal",
|
|
7784
|
+
error: errors11.wrap(ErrBadRequest, "submitted result missing interaction"),
|
|
7785
|
+
retriable: false,
|
|
7786
|
+
toJSON: poisonToJSON
|
|
7787
|
+
};
|
|
7726
7788
|
}
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
function handleMessage(event) {
|
|
7734
|
-
const result2 = readPopupMessage(event, popup, config, expectedOrigin);
|
|
7735
|
-
if (result2.kind === "ignore") {
|
|
7736
|
-
return;
|
|
7789
|
+
if (result.assessmentOutcome.value === "revisionRequested") {
|
|
7790
|
+
const feedback = {
|
|
7791
|
+
assessmentOutcome: result.assessmentOutcome,
|
|
7792
|
+
feedbackContent: result.feedbackContent
|
|
7793
|
+
};
|
|
7794
|
+
return fromAdvanced(result.frame.body, result.frame.stimulus, interaction, feedback);
|
|
7737
7795
|
}
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7796
|
+
return submittedFeedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.submission, result.assessmentOutcome, result.feedbackContent, result.review);
|
|
7797
|
+
}
|
|
7798
|
+
case "timedOut": {
|
|
7799
|
+
const interaction = result.frame.interaction;
|
|
7800
|
+
if (interaction === null) {
|
|
7801
|
+
logger.error("timed out result without interaction");
|
|
7802
|
+
return {
|
|
7803
|
+
phase: "fatal",
|
|
7804
|
+
error: errors11.wrap(ErrBadRequest, "timed out result missing interaction"),
|
|
7805
|
+
retriable: false,
|
|
7806
|
+
toJSON: poisonToJSON
|
|
7807
|
+
};
|
|
7742
7808
|
}
|
|
7743
|
-
|
|
7809
|
+
return timedOutFeedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.feedbackContent);
|
|
7744
7810
|
}
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
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;
|
|
7811
|
+
case "completed":
|
|
7812
|
+
return { phase: "completed", toJSON: poisonToJSON };
|
|
7813
|
+
}
|
|
7756
7814
|
}
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7815
|
+
function errored(error, failedPhase, intent) {
|
|
7816
|
+
let pending;
|
|
7817
|
+
const retriable = isRetriableError(error);
|
|
7818
|
+
if (!retriable) {
|
|
7819
|
+
return {
|
|
7820
|
+
phase: "errored",
|
|
7821
|
+
error,
|
|
7822
|
+
retriable: false,
|
|
7823
|
+
toJSON: poisonToJSON
|
|
7824
|
+
};
|
|
7825
|
+
}
|
|
7826
|
+
const state = {
|
|
7827
|
+
phase: "errored",
|
|
7828
|
+
error,
|
|
7829
|
+
retriable: true,
|
|
7830
|
+
retry: function retry() {
|
|
7831
|
+
if (pending) {
|
|
7832
|
+
return pending;
|
|
7833
|
+
}
|
|
7834
|
+
logger.debug({ failedPhase }, "retrying from errored state");
|
|
7835
|
+
pending = execute(intent, failedPhase);
|
|
7836
|
+
return pending;
|
|
7837
|
+
},
|
|
7838
|
+
toJSON: poisonToJSON
|
|
7839
|
+
};
|
|
7840
|
+
return state;
|
|
7764
7841
|
}
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7842
|
+
async function execute(intent, phase) {
|
|
7843
|
+
logger.debug({
|
|
7844
|
+
phase,
|
|
7845
|
+
intentKind: intent.kind,
|
|
7846
|
+
subject: sc.subject
|
|
7847
|
+
}, "session execute");
|
|
7848
|
+
const body = {
|
|
7849
|
+
subject: sc.subject,
|
|
7850
|
+
intent
|
|
7851
|
+
};
|
|
7852
|
+
const result = await sc.transport(body);
|
|
7853
|
+
if (!result.ok) {
|
|
7854
|
+
if ((errors11.is(result.error, ErrTokenExpired) || errors11.is(result.error, ErrInvalidAccessToken)) && sc.reauthenticate !== null) {
|
|
7855
|
+
logger.warn({ phase, intentKind: intent.kind }, "session token rejected");
|
|
7856
|
+
return sc.reauthenticate(result.error);
|
|
7857
|
+
}
|
|
7858
|
+
if (isFatalError(result.error)) {
|
|
7859
|
+
logger.error({
|
|
7860
|
+
error: result.error,
|
|
7861
|
+
phase,
|
|
7862
|
+
intentKind: intent.kind
|
|
7863
|
+
}, "fatal transport error");
|
|
7864
|
+
return {
|
|
7865
|
+
phase: "fatal",
|
|
7866
|
+
error: result.error,
|
|
7867
|
+
retriable: false,
|
|
7868
|
+
toJSON: poisonToJSON
|
|
7869
|
+
};
|
|
7870
|
+
}
|
|
7871
|
+
logger.warn({
|
|
7872
|
+
error: result.error,
|
|
7873
|
+
phase,
|
|
7874
|
+
intentKind: intent.kind
|
|
7875
|
+
}, "retriable transport error");
|
|
7876
|
+
return errored(result.error, phase, intent);
|
|
7877
|
+
}
|
|
7878
|
+
const next = resolve(result.data);
|
|
7879
|
+
logger.debug({
|
|
7880
|
+
phase,
|
|
7881
|
+
intentKind: intent.kind,
|
|
7882
|
+
outcome: result.data.outcome,
|
|
7883
|
+
nextPhase: next.phase
|
|
7884
|
+
}, "session execute resolved");
|
|
7885
|
+
return next;
|
|
7778
7886
|
}
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
return normalized;
|
|
7887
|
+
function isPciSupported(pciId) {
|
|
7888
|
+
for (const id of sc.supportedPcis) {
|
|
7889
|
+
if (id === pciId) {
|
|
7890
|
+
return true;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
return false;
|
|
7787
7894
|
}
|
|
7788
|
-
|
|
7789
|
-
|
|
7895
|
+
function fromAdvanced(body, stimulus, interaction, feedback) {
|
|
7896
|
+
if (interaction === null) {
|
|
7897
|
+
return observationState(ctx, body, stimulus);
|
|
7898
|
+
}
|
|
7899
|
+
if (interaction.type === "portable-custom") {
|
|
7900
|
+
if (!isPciSupported(interaction.pciId)) {
|
|
7901
|
+
logger.error({ pciId: interaction.pciId }, "unsupported pci in frame");
|
|
7902
|
+
return {
|
|
7903
|
+
phase: "fatal",
|
|
7904
|
+
error: errors11.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
|
|
7905
|
+
retriable: false,
|
|
7906
|
+
toJSON: poisonToJSON
|
|
7907
|
+
};
|
|
7908
|
+
}
|
|
7909
|
+
}
|
|
7910
|
+
return pendingInteractionState(body, stimulus, interaction, feedback);
|
|
7790
7911
|
}
|
|
7791
|
-
|
|
7792
|
-
|
|
7912
|
+
function extendedTextInteractionState(body, stimulus, interaction, feedback) {
|
|
7913
|
+
if (!("cardinality" in interaction)) {
|
|
7914
|
+
logger.error("extended-text interaction is unsupported");
|
|
7915
|
+
return {
|
|
7916
|
+
phase: "fatal",
|
|
7917
|
+
error: errors11.wrap(ErrBadRequest, "extended-text interaction unsupported"),
|
|
7918
|
+
retriable: false,
|
|
7919
|
+
toJSON: poisonToJSON
|
|
7920
|
+
};
|
|
7921
|
+
}
|
|
7922
|
+
const extendedInteraction = interaction;
|
|
7923
|
+
return extendedTextState(ctx, body, stimulus, extendedInteraction, feedback);
|
|
7924
|
+
}
|
|
7925
|
+
function pendingInteractionState(body, stimulus, interaction, feedback) {
|
|
7926
|
+
switch (interaction.type) {
|
|
7927
|
+
case "choice":
|
|
7928
|
+
return choiceState(ctx, body, stimulus, interaction, interaction.options, interaction.maxChoices, interaction.minChoices, feedback);
|
|
7929
|
+
case "text-entry":
|
|
7930
|
+
return textEntryState(ctx, body, stimulus, interaction, feedback);
|
|
7931
|
+
case "extended-text":
|
|
7932
|
+
return extendedTextInteractionState(body, stimulus, interaction, feedback);
|
|
7933
|
+
case "order":
|
|
7934
|
+
return orderState(ctx, body, stimulus, interaction, feedback);
|
|
7935
|
+
case "match":
|
|
7936
|
+
return matchState(ctx, body, stimulus, interaction, feedback);
|
|
7937
|
+
case "portable-custom":
|
|
7938
|
+
return pciInteractionState(ctx, body, stimulus, interaction, feedback);
|
|
7939
|
+
}
|
|
7793
7940
|
}
|
|
7794
|
-
|
|
7941
|
+
const ctx = { logger, execute, errored };
|
|
7942
|
+
return { execute };
|
|
7795
7943
|
}
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7944
|
+
|
|
7945
|
+
// src/client/transport.ts
|
|
7946
|
+
import * as errors12 from "@superbuilders/errors";
|
|
7947
|
+
|
|
7948
|
+
// src/version.ts
|
|
7949
|
+
var SDK_VERSION = "5.0.0";
|
|
7950
|
+
var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
|
|
7951
|
+
|
|
7952
|
+
// src/client/transport.ts
|
|
7953
|
+
var ADVANCE_PATH = "/api/v0/advance";
|
|
7954
|
+
function readStringField(value, key) {
|
|
7955
|
+
if (!(key in value)) {
|
|
7956
|
+
return;
|
|
7803
7957
|
}
|
|
7804
|
-
|
|
7958
|
+
const v = Reflect.get(value, key);
|
|
7959
|
+
if (typeof v !== "string") {
|
|
7960
|
+
return;
|
|
7961
|
+
}
|
|
7962
|
+
return v;
|
|
7805
7963
|
}
|
|
7806
|
-
function
|
|
7807
|
-
if (
|
|
7964
|
+
function parseAdvanceErrorBody(body) {
|
|
7965
|
+
if (body.length === 0) {
|
|
7808
7966
|
return null;
|
|
7809
7967
|
}
|
|
7810
|
-
const
|
|
7811
|
-
|
|
7968
|
+
const parsed = errors12.trySync(function parseJson() {
|
|
7969
|
+
return JSON.parse(body);
|
|
7970
|
+
});
|
|
7971
|
+
if (parsed.error) {
|
|
7812
7972
|
return null;
|
|
7813
7973
|
}
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
const segments = token.split(".");
|
|
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");
|
|
7822
|
-
}
|
|
7823
|
-
const payload = decodeBase64UrlJson(payloadSegment);
|
|
7824
|
-
const exp = numericClaim(payload, "exp");
|
|
7825
|
-
if (exp === null) {
|
|
7826
|
-
logger.error("access token exp missing");
|
|
7827
|
-
throw errors11.wrap(ErrMalformedAccessToken, "exp missing");
|
|
7974
|
+
const raw = parsed.data;
|
|
7975
|
+
if (typeof raw !== "object" || raw === null) {
|
|
7976
|
+
return null;
|
|
7828
7977
|
}
|
|
7829
|
-
const
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7978
|
+
const result = {};
|
|
7979
|
+
const error = readStringField(raw, "error");
|
|
7980
|
+
if (error !== undefined) {
|
|
7981
|
+
result.error = error;
|
|
7833
7982
|
}
|
|
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`);
|
|
7983
|
+
const detail = readStringField(raw, "detail");
|
|
7984
|
+
if (detail !== undefined) {
|
|
7985
|
+
result.detail = detail;
|
|
7839
7986
|
}
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
// src/client/auth/storage.ts
|
|
7845
|
-
var MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX = "primer:access-token";
|
|
7846
|
-
function managedAuthAccessTokenStorageKey(publishableKey) {
|
|
7847
|
-
return `${MANAGED_AUTH_ACCESS_TOKEN_KEY_PREFIX}:${publishableKey}`;
|
|
7848
|
-
}
|
|
7849
|
-
function managedAuthStorage(logger) {
|
|
7850
|
-
if (typeof globalThis.sessionStorage === "undefined") {
|
|
7851
|
-
logger.error("managed auth storage unavailable");
|
|
7852
|
-
throw ErrAuthUnavailable;
|
|
7987
|
+
const minimumSdkVersion = readStringField(raw, "minimumSdkVersion");
|
|
7988
|
+
if (minimumSdkVersion !== undefined) {
|
|
7989
|
+
result.minimumSdkVersion = minimumSdkVersion;
|
|
7853
7990
|
}
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
const token = storage.getItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7858
|
-
if (token === null || token.length === 0) {
|
|
7859
|
-
return null;
|
|
7991
|
+
const receivedSdkVersion = readStringField(raw, "receivedSdkVersion");
|
|
7992
|
+
if (receivedSdkVersion !== undefined) {
|
|
7993
|
+
result.receivedSdkVersion = receivedSdkVersion;
|
|
7860
7994
|
}
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
storage.setItem(managedAuthAccessTokenStorageKey(publishableKey), accessToken);
|
|
7865
|
-
}
|
|
7866
|
-
function clearManagedAuthAccessToken(storage, publishableKey) {
|
|
7867
|
-
storage.removeItem(managedAuthAccessTokenStorageKey(publishableKey));
|
|
7868
|
-
}
|
|
7869
|
-
|
|
7870
|
-
// src/client/auth/provider.ts
|
|
7871
|
-
function resolveProvidedAccessToken(token, logger) {
|
|
7872
|
-
const result = errors12.trySync(function resolveProvidedToken() {
|
|
7873
|
-
return resolveAccessToken(token, logger);
|
|
7874
|
-
});
|
|
7875
|
-
if (result.error) {
|
|
7876
|
-
return { kind: "fatal", error: result.error };
|
|
7995
|
+
const upgradeUrl = readStringField(raw, "upgradeUrl");
|
|
7996
|
+
if (upgradeUrl !== undefined) {
|
|
7997
|
+
result.upgradeUrl = upgradeUrl;
|
|
7877
7998
|
}
|
|
7878
|
-
return
|
|
7999
|
+
return result;
|
|
7879
8000
|
}
|
|
7880
|
-
function
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
8001
|
+
function httpSentinel(status, body) {
|
|
8002
|
+
if (status === 400) {
|
|
8003
|
+
const parsed = parseAdvanceErrorBody(body);
|
|
8004
|
+
if (parsed?.error === "sdk_upgrade_required") {
|
|
8005
|
+
return ErrSdkUpgradeRequired;
|
|
8006
|
+
}
|
|
8007
|
+
return ErrBadRequest;
|
|
7886
8008
|
}
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7892
|
-
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
8009
|
+
if (status === 401) {
|
|
8010
|
+
const parsed = parseAdvanceErrorBody(body);
|
|
8011
|
+
if (parsed?.detail === "token_expired") {
|
|
8012
|
+
return ErrTokenExpired;
|
|
7893
8013
|
}
|
|
7894
|
-
|
|
7895
|
-
}
|
|
7896
|
-
function resolveStoredAccessToken(storage, options) {
|
|
7897
|
-
const stored = loadManagedAuthAccessToken(storage, options.publishableKey);
|
|
7898
|
-
if (stored === null) {
|
|
7899
|
-
return { kind: "missing" };
|
|
8014
|
+
return ErrInvalidAccessToken;
|
|
7900
8015
|
}
|
|
7901
|
-
|
|
7902
|
-
return
|
|
7903
|
-
});
|
|
7904
|
-
if (result.error) {
|
|
7905
|
-
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7906
|
-
return { kind: "missing" };
|
|
8016
|
+
if (status === 403) {
|
|
8017
|
+
return ErrForbidden;
|
|
7907
8018
|
}
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
accessToken: result.data,
|
|
7911
|
-
clearCachedAccessToken: function clearCachedAccessToken() {
|
|
7912
|
-
clearManagedAuthAccessToken(storage, options.publishableKey);
|
|
7913
|
-
}
|
|
7914
|
-
};
|
|
7915
|
-
}
|
|
7916
|
-
function resolveExistingAccessToken(options) {
|
|
7917
|
-
if (options.accessToken !== undefined) {
|
|
7918
|
-
return resolveProvidedAccessToken(options.accessToken, options.logger);
|
|
8019
|
+
if (status === 404) {
|
|
8020
|
+
return ErrNotFound;
|
|
7919
8021
|
}
|
|
7920
|
-
|
|
7921
|
-
return
|
|
7922
|
-
});
|
|
7923
|
-
if (storageResult.error) {
|
|
7924
|
-
return { kind: "auth-unavailable", error: storageResult.error };
|
|
8022
|
+
if (status === 409) {
|
|
8023
|
+
return ErrConflict;
|
|
7925
8024
|
}
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
function authFailureResult(error) {
|
|
7929
|
-
if (errors12.is(error, ErrAuthConfigInvalid)) {
|
|
7930
|
-
return { kind: "auth-config-invalid", error };
|
|
8025
|
+
if (status === 429) {
|
|
8026
|
+
return ErrRateLimited;
|
|
7931
8027
|
}
|
|
7932
|
-
if (
|
|
7933
|
-
return
|
|
8028
|
+
if (status === 502 || status === 503 || status === 504) {
|
|
8029
|
+
return ErrServiceUnavailable;
|
|
7934
8030
|
}
|
|
7935
|
-
return
|
|
8031
|
+
return ErrServerError;
|
|
7936
8032
|
}
|
|
7937
|
-
|
|
7938
|
-
const
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
return
|
|
8033
|
+
function buildSdkUpgradeRequiredError(sentinel, text, logger) {
|
|
8034
|
+
const parsed = parseAdvanceErrorBody(text);
|
|
8035
|
+
if (parsed === null) {
|
|
8036
|
+
logger.error({
|
|
8037
|
+
receivedSdkVersion: null,
|
|
8038
|
+
minimumSdkVersion: "<unknown>",
|
|
8039
|
+
upgradeUrl: NPM_PACKAGE_URL
|
|
8040
|
+
}, "sdk upgrade required");
|
|
8041
|
+
const message2 = `<missing> < <unknown>; bump @superbuilders/primer-tives at ${NPM_PACKAGE_URL}`;
|
|
8042
|
+
return errors12.wrap(sentinel, message2);
|
|
7947
8043
|
}
|
|
7948
|
-
const
|
|
7949
|
-
const
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
8044
|
+
const minimumSdkVersion = parsed.minimumSdkVersion === undefined ? "<unknown>" : parsed.minimumSdkVersion;
|
|
8045
|
+
const receivedSdkVersion = parsed.receivedSdkVersion === undefined ? null : parsed.receivedSdkVersion;
|
|
8046
|
+
const receivedDisplay = receivedSdkVersion === null ? "<missing>" : receivedSdkVersion;
|
|
8047
|
+
const upgradeUrl = parsed.upgradeUrl === undefined ? NPM_PACKAGE_URL : parsed.upgradeUrl;
|
|
8048
|
+
logger.error({
|
|
8049
|
+
receivedSdkVersion,
|
|
8050
|
+
minimumSdkVersion,
|
|
8051
|
+
upgradeUrl
|
|
8052
|
+
}, "sdk upgrade required");
|
|
8053
|
+
const message = `${receivedDisplay} < ${minimumSdkVersion}; bump @superbuilders/primer-tives at ${upgradeUrl}`;
|
|
8054
|
+
return errors12.wrap(sentinel, message);
|
|
8055
|
+
}
|
|
8056
|
+
function isAbortError(err) {
|
|
8057
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
8058
|
+
return true;
|
|
7958
8059
|
}
|
|
7959
|
-
|
|
8060
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
8061
|
+
return true;
|
|
8062
|
+
}
|
|
8063
|
+
return false;
|
|
7960
8064
|
}
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
function
|
|
7966
|
-
if (
|
|
7967
|
-
return
|
|
8065
|
+
function createTransport(tc) {
|
|
8066
|
+
const fetchFn = tc.fetch ? tc.fetch : globalThis.fetch;
|
|
8067
|
+
const logger = tc.logger;
|
|
8068
|
+
const advanceUrl = new URL(ADVANCE_PATH, tc.origin).toString();
|
|
8069
|
+
function transportSignal() {
|
|
8070
|
+
if (tc.abort) {
|
|
8071
|
+
return tc.abort.signal;
|
|
7968
8072
|
}
|
|
7969
|
-
|
|
7970
|
-
|
|
8073
|
+
return;
|
|
8074
|
+
}
|
|
8075
|
+
async function transport(body) {
|
|
8076
|
+
logger.debug({
|
|
8077
|
+
intentKind: body.intent.kind,
|
|
8078
|
+
subject: body.subject
|
|
8079
|
+
}, "transport request");
|
|
8080
|
+
const fetchResult = await fetchFn(advanceUrl, {
|
|
8081
|
+
method: "POST",
|
|
8082
|
+
mode: "cors",
|
|
8083
|
+
credentials: "omit",
|
|
8084
|
+
headers: {
|
|
8085
|
+
"Content-Type": "application/json",
|
|
8086
|
+
Authorization: `Bearer ${tc.accessToken.value}`,
|
|
8087
|
+
"X-Primer-Publishable-Key": tc.publishableKey,
|
|
8088
|
+
"X-Primer-SDK-Version": SDK_VERSION
|
|
8089
|
+
},
|
|
8090
|
+
body: JSON.stringify(body),
|
|
8091
|
+
signal: transportSignal()
|
|
8092
|
+
}).then(function ok(r) {
|
|
8093
|
+
return { ok: true, response: r };
|
|
8094
|
+
}, function fail(err) {
|
|
8095
|
+
return { ok: false, error: err };
|
|
7971
8096
|
});
|
|
7972
|
-
|
|
8097
|
+
if (!fetchResult.ok) {
|
|
8098
|
+
if (isAbortError(fetchResult.error)) {
|
|
8099
|
+
logger.error({
|
|
8100
|
+
intentKind: body.intent.kind
|
|
8101
|
+
}, "transport timeout");
|
|
8102
|
+
return { ok: false, error: errors12.wrap(ErrTimeout, fetchResult.error.message) };
|
|
8103
|
+
}
|
|
8104
|
+
logger.error({
|
|
8105
|
+
error: fetchResult.error
|
|
8106
|
+
}, "transport network error");
|
|
8107
|
+
return { ok: false, error: errors12.wrap(ErrNetwork, fetchResult.error.message) };
|
|
8108
|
+
}
|
|
8109
|
+
const res = fetchResult.response;
|
|
8110
|
+
if (!res.ok) {
|
|
8111
|
+
const text = await res.text().catch(function fallback() {
|
|
8112
|
+
return "";
|
|
8113
|
+
});
|
|
8114
|
+
const sentinel = httpSentinel(res.status, text);
|
|
8115
|
+
if (errors12.is(sentinel, ErrSdkUpgradeRequired)) {
|
|
8116
|
+
return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
|
|
8117
|
+
}
|
|
8118
|
+
logger.error({
|
|
8119
|
+
status: res.status,
|
|
8120
|
+
body: text,
|
|
8121
|
+
intentKind: body.intent.kind,
|
|
8122
|
+
subject: body.subject
|
|
8123
|
+
}, "transport http error");
|
|
8124
|
+
return { ok: false, error: errors12.wrap(sentinel, text) };
|
|
8125
|
+
}
|
|
8126
|
+
const jsonResult = await res.json().then(function ok(data) {
|
|
8127
|
+
return { ok: true, data };
|
|
8128
|
+
}, function fail(err) {
|
|
8129
|
+
return { ok: false, error: err };
|
|
8130
|
+
});
|
|
8131
|
+
if (!jsonResult.ok) {
|
|
8132
|
+
logger.error({
|
|
8133
|
+
error: jsonResult.error
|
|
8134
|
+
}, "transport json parse failed");
|
|
8135
|
+
return { ok: false, error: errors12.wrap(ErrJsonParse, jsonResult.error.message) };
|
|
8136
|
+
}
|
|
8137
|
+
logger.debug({
|
|
8138
|
+
intentKind: body.intent.kind
|
|
8139
|
+
}, "transport success");
|
|
8140
|
+
return { ok: true, data: jsonResult.data };
|
|
7973
8141
|
}
|
|
7974
|
-
return
|
|
7975
|
-
}
|
|
7976
|
-
function signInRequiredState(config) {
|
|
7977
|
-
return {
|
|
7978
|
-
phase: "sign-in-required",
|
|
7979
|
-
login: pendingLogin(config),
|
|
7980
|
-
toJSON: poisonToJSON
|
|
7981
|
-
};
|
|
7982
|
-
}
|
|
7983
|
-
function signInFailedState(config) {
|
|
7984
|
-
return {
|
|
7985
|
-
phase: "sign-in-failed",
|
|
7986
|
-
error: config.error,
|
|
7987
|
-
login: pendingLogin(config),
|
|
7988
|
-
toJSON: poisonToJSON
|
|
7989
|
-
};
|
|
7990
|
-
}
|
|
7991
|
-
function authUnavailableState(error) {
|
|
7992
|
-
return {
|
|
7993
|
-
phase: "auth-unavailable",
|
|
7994
|
-
error,
|
|
7995
|
-
toJSON: poisonToJSON
|
|
7996
|
-
};
|
|
7997
|
-
}
|
|
7998
|
-
function authConfigInvalidState(error) {
|
|
7999
|
-
return {
|
|
8000
|
-
phase: "auth-config-invalid",
|
|
8001
|
-
error,
|
|
8002
|
-
toJSON: poisonToJSON
|
|
8003
|
-
};
|
|
8142
|
+
return transport;
|
|
8004
8143
|
}
|
|
8005
8144
|
|
|
8006
8145
|
// src/client/start.ts
|
|
@@ -8023,6 +8162,12 @@ function primerOrigin(origin) {
|
|
|
8023
8162
|
}
|
|
8024
8163
|
return DEFAULT_PRIMER_ORIGIN;
|
|
8025
8164
|
}
|
|
8165
|
+
function primerAuthOrigin(authOrigin, origin) {
|
|
8166
|
+
if (authOrigin !== undefined) {
|
|
8167
|
+
return authOrigin;
|
|
8168
|
+
}
|
|
8169
|
+
return origin;
|
|
8170
|
+
}
|
|
8026
8171
|
async function startRuntime(config, resolved) {
|
|
8027
8172
|
let reauthenticate = null;
|
|
8028
8173
|
if (resolved.clearCachedAccessToken !== undefined) {
|
|
@@ -8066,7 +8211,7 @@ function makeSignInFailedState(config, error) {
|
|
|
8066
8211
|
}
|
|
8067
8212
|
async function loginAndStart(config) {
|
|
8068
8213
|
const result = await beginHostedLogin({
|
|
8069
|
-
origin: config.
|
|
8214
|
+
origin: config.authOrigin,
|
|
8070
8215
|
publishableKey: config.publishableKey,
|
|
8071
8216
|
logger: config.logger
|
|
8072
8217
|
});
|
|
@@ -8091,6 +8236,7 @@ async function start(options) {
|
|
|
8091
8236
|
const config = {
|
|
8092
8237
|
publishableKey: options.publishableKey,
|
|
8093
8238
|
origin,
|
|
8239
|
+
authOrigin: primerAuthOrigin(options.authOrigin, origin),
|
|
8094
8240
|
fetch: options.fetch,
|
|
8095
8241
|
abort: options.abort,
|
|
8096
8242
|
logger,
|
|
@@ -8123,4 +8269,4 @@ export {
|
|
|
8123
8269
|
start
|
|
8124
8270
|
};
|
|
8125
8271
|
|
|
8126
|
-
//# debugId=
|
|
8272
|
+
//# debugId=9849F045A465657964756E2164756E21
|