@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.
Files changed (64) hide show
  1. package/README.md +42 -6
  2. package/dist/client/auth/access-token.d.ts +1 -1
  3. package/dist/client/auth/access-token.d.ts.map +1 -1
  4. package/dist/client/auth/hosted-popup.d.ts +1 -1
  5. package/dist/client/auth/hosted-popup.d.ts.map +1 -1
  6. package/dist/client/auth/provider.d.ts +8 -3
  7. package/dist/client/auth/provider.d.ts.map +1 -1
  8. package/dist/client/auth/storage.d.ts +8 -0
  9. package/dist/client/auth/storage.d.ts.map +1 -0
  10. package/dist/client/auth-state.d.ts +2 -2
  11. package/dist/client/auth-state.d.ts.map +1 -1
  12. package/dist/client/choice-state.d.ts +2 -2
  13. package/dist/client/choice-state.d.ts.map +1 -1
  14. package/dist/client/extended-text-state.d.ts +2 -2
  15. package/dist/client/extended-text-state.d.ts.map +1 -1
  16. package/dist/client/feedback-state.d.ts +4 -4
  17. package/dist/client/feedback-state.d.ts.map +1 -1
  18. package/dist/client/index.d.ts +2 -2
  19. package/dist/client/index.d.ts.map +1 -1
  20. package/dist/client/index.js +636 -556
  21. package/dist/client/index.js.map +25 -24
  22. package/dist/client/match-state.d.ts +2 -2
  23. package/dist/client/match-state.d.ts.map +1 -1
  24. package/dist/client/observation-state.d.ts +1 -1
  25. package/dist/client/observation-state.d.ts.map +1 -1
  26. package/dist/client/order-state.d.ts +2 -2
  27. package/dist/client/order-state.d.ts.map +1 -1
  28. package/dist/client/pci-state.d.ts +2 -2
  29. package/dist/client/pci-state.d.ts.map +1 -1
  30. package/dist/client/session-context.d.ts +2 -2
  31. package/dist/client/session-context.d.ts.map +1 -1
  32. package/dist/client/session.d.ts +5 -5
  33. package/dist/client/session.d.ts.map +1 -1
  34. package/dist/client/start.d.ts +3 -3
  35. package/dist/client/start.d.ts.map +1 -1
  36. package/dist/client/text-entry-state.d.ts +2 -2
  37. package/dist/client/text-entry-state.d.ts.map +1 -1
  38. package/dist/client/transport.d.ts +7 -6
  39. package/dist/client/transport.d.ts.map +1 -1
  40. package/dist/client/types.d.ts +8 -2
  41. package/dist/client/types.d.ts.map +1 -1
  42. package/dist/contracts/index.d.ts +4 -4
  43. package/dist/contracts/index.d.ts.map +1 -1
  44. package/dist/contracts/index.js +12 -12
  45. package/dist/contracts/index.js.map +6 -6
  46. package/dist/contracts/pci-schemas.d.ts +4 -0
  47. package/dist/contracts/pci-schemas.d.ts.map +1 -1
  48. package/dist/contracts/pci.d.ts +1 -1
  49. package/dist/contracts/pci.d.ts.map +1 -1
  50. package/dist/contracts/review.d.ts +1 -1
  51. package/dist/contracts/review.d.ts.map +1 -1
  52. package/dist/contracts/validation.d.ts +24 -2
  53. package/dist/contracts/validation.d.ts.map +1 -1
  54. package/dist/grade-level.d.ts +1 -1
  55. package/dist/grade-level.d.ts.map +1 -1
  56. package/dist/grade-level.js.map +1 -1
  57. package/dist/subject-pcis.d.ts +1 -1
  58. package/dist/subject-pcis.d.ts.map +1 -1
  59. package/dist/subject-pcis.js.map +2 -2
  60. package/dist/subject.d.ts +1 -1
  61. package/dist/subject.d.ts.map +1 -1
  62. package/dist/subject.js.map +1 -1
  63. package/dist/version.d.ts +1 -1
  64. package/package.json +1 -1
@@ -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/transport.ts
6783
- import * as errors3 from "@superbuilders/errors";
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/transport.ts
6790
- var ADVANCE_PATH = "/api/v0/advance";
6791
- function readStringField(value, key) {
6792
- if (!(key in value)) {
6793
- return;
6794
- }
6795
- const v = Reflect.get(value, key);
6796
- if (typeof v !== "string") {
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
- return v;
6794
+ const dotCount = token.split(".").length - 1;
6795
+ return dotCount !== 2;
6800
6796
  }
6801
- function parseAdvanceErrorBody(body) {
6802
- if (body.length === 0) {
6803
- return null;
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
- const parsed = errors3.trySync(function parseJson() {
6806
- return JSON.parse(body);
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 (parsed.error) {
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 raw = parsed.data;
6812
- if (typeof raw !== "object" || raw === null) {
6825
+ const value = Reflect.get(payload, claim);
6826
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6813
6827
  return null;
6814
6828
  }
6815
- const result = {};
6816
- const error = readStringField(raw, "error");
6817
- if (error !== undefined) {
6818
- result.error = error;
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 detail = readStringField(raw, "detail");
6821
- if (detail !== undefined) {
6822
- result.detail = detail;
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 minimumSdkVersion = readStringField(raw, "minimumSdkVersion");
6825
- if (minimumSdkVersion !== undefined) {
6826
- result.minimumSdkVersion = minimumSdkVersion;
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
- const receivedSdkVersion = readStringField(raw, "receivedSdkVersion");
6829
- if (receivedSdkVersion !== undefined) {
6830
- result.receivedSdkVersion = receivedSdkVersion;
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
- const upgradeUrl = readStringField(raw, "upgradeUrl");
6833
- if (upgradeUrl !== undefined) {
6834
- result.upgradeUrl = upgradeUrl;
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 httpSentinel(status, body) {
6839
- if (status === 400) {
6840
- const parsed = parseAdvanceErrorBody(body);
6841
- if (parsed?.error === "sdk_upgrade_required") {
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
- if (status === 401) {
6847
- const parsed = parseAdvanceErrorBody(body);
6848
- if (parsed?.detail === "token_expired") {
6849
- return ErrTokenExpired;
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
- if (status === 403) {
6854
- return ErrForbidden;
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
- if (status === 404) {
6857
- return ErrNotFound;
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
- if (status === 409) {
6860
- return ErrConflict;
6922
+ return field;
6923
+ }
6924
+ function readPopupMessage(event, popup, config, expectedOrigin) {
6925
+ if (event.source !== popup) {
6926
+ return { kind: "ignore" };
6861
6927
  }
6862
- if (status === 429) {
6863
- return ErrRateLimited;
6928
+ if (event.origin !== expectedOrigin) {
6929
+ return { kind: "ignore" };
6864
6930
  }
6865
- if (status === 502 || status === 503 || status === 504) {
6866
- return ErrServiceUnavailable;
6931
+ const data = event.data;
6932
+ if (!isRecord(data)) {
6933
+ return { kind: "ignore" };
6867
6934
  }
6868
- return ErrServerError;
6869
- }
6870
- function buildSdkUpgradeRequiredError(sentinel, text, logger) {
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 minimumSdkVersion = parsed.minimumSdkVersion === undefined ? "<unknown>" : parsed.minimumSdkVersion;
6882
- const receivedSdkVersion = parsed.receivedSdkVersion === undefined ? null : parsed.receivedSdkVersion;
6883
- const receivedDisplay = receivedSdkVersion === null ? "<missing>" : receivedSdkVersion;
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 (err instanceof DOMException && err.name === "TimeoutError") {
6898
- return true;
6943
+ if (state !== config.clientState) {
6944
+ return { kind: "error", error: ErrAuthStateMismatch };
6899
6945
  }
6900
- return false;
6901
- }
6902
- function createTransport(tc) {
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
- async function transport(body) {
6913
- logger.debug({
6914
- intentKind: body.intent.kind,
6915
- subject: body.subject
6916
- }, "transport request");
6917
- const fetchResult = await fetchFn(advanceUrl, {
6918
- method: "POST",
6919
- mode: "cors",
6920
- credentials: "omit",
6921
- headers: {
6922
- "Content-Type": "application/json",
6923
- Authorization: `Bearer ${tc.accessToken.value}`,
6924
- "X-Primer-Publishable-Key": tc.publishableKey,
6925
- "X-Primer-SDK-Version": SDK_VERSION
6926
- },
6927
- body: JSON.stringify(body),
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
- if (!fetchResult.ok) {
6935
- if (isAbortError(fetchResult.error)) {
6936
- logger.error({
6937
- intentKind: body.intent.kind
6938
- }, "transport timeout");
6939
- return { ok: false, error: errors3.wrap(ErrTimeout, fetchResult.error.message) };
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
- logger.error({
6942
- error: fetchResult.error
6943
- }, "transport network error");
6944
- return { ok: false, error: errors3.wrap(ErrNetwork, fetchResult.error.message) };
6945
- }
6946
- const res = fetchResult.response;
6947
- if (!res.ok) {
6948
- const text = await res.text().catch(function fallback() {
6949
- return "";
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 sentinel = httpSentinel(res.status, text);
6952
- if (errors3.is(sentinel, ErrSdkUpgradeRequired)) {
6953
- return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
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
- logger.error({
6956
- status: res.status,
6957
- body: text,
6958
- intentKind: body.intent.kind,
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
- if (!jsonResult.ok) {
6969
- logger.error({
6970
- error: jsonResult.error
6971
- }, "transport json parse failed");
6972
- return { ok: false, error: errors3.wrap(ErrJsonParse, jsonResult.error.message) };
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
- return transport;
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/session.ts
6983
- import * as errors10 from "@superbuilders/errors";
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 errors4 from "@superbuilders/errors";
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(errors4.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors4.wrap(ErrConflict, "cannot submit a different choice payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
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(errors4.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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 errors5 from "@superbuilders/errors";
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(errors5.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors5.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
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(errors5.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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(errors5.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors5.wrap(ErrConflict, "cannot submit a different extended-text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
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(errors5.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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, isCorrect, feedbackContent, review) {
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
- isCorrect,
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 errors6 from "@superbuilders/errors";
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(errors6.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors6.wrap(ErrConflict, "cannot submit a different match payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
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(errors6.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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 errors7 from "@superbuilders/errors";
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(errors7.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors7.wrap(ErrConflict, "cannot submit a different order payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors7.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
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(errors7.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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 errors8 from "@superbuilders/errors";
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(errors8.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors8.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors8.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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 errors9 from "@superbuilders/errors";
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(errors9.wrap(ErrConflict, "cannot submit while timeout is in flight"), "interaction", { kind: "interaction", submission }));
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(errors9.wrap(ErrConflict, "cannot submit a different text payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
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(errors9.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
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(errors9.wrap(ErrConflict, "cannot timeout while submission is in flight"), "timeout", intent));
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 (errors10.is(err, sentinel)) {
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 !errors10.is(err, ErrInvalidSubmission);
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: errors10.wrap(ErrBadRequest, "submitted result missing interaction"),
7678
+ error: errors11.wrap(ErrBadRequest, "submitted result missing interaction"),
7461
7679
  retriable: false,
7462
7680
  toJSON: poisonToJSON
7463
7681
  };
7464
7682
  }
7465
- return feedbackState(ctx, result.frame.body, result.frame.stimulus, interaction, result.submission, result.isCorrect, result.feedbackContent, result.review);
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 (errors10.is(result.error, ErrTokenExpired) && sc.reauthenticate !== null) {
7511
- logger.warn({ phase, intentKind: intent.kind }, "session token expired");
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: errors10.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
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 extendedTextState(ctx, body, stimulus, interaction);
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/auth/provider.ts
7822
+ // src/client/transport.ts
7589
7823
  import * as errors12 from "@superbuilders/errors";
7590
7824
 
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;
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/auth/access-token.ts
7771
- import * as errors11 from "@superbuilders/errors";
7772
- var ACCESS_TOKEN_PREFIX = "eyJ";
7773
- var TOKEN_EXPIRY_SKEW_MS = 60000;
7774
- var resolvedAccessTokenBrand = Symbol("primer resolved access token");
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
- if (remainder === 3) {
7792
- return `${normalized}=`;
7835
+ const v = Reflect.get(value, key);
7836
+ if (typeof v !== "string") {
7837
+ return;
7793
7838
  }
7794
- throw errors11.wrap(ErrMalformedAccessToken, "payload base64");
7839
+ return v;
7795
7840
  }
7796
- function decodeBase64UrlJson(segment) {
7797
- const decoded = errors11.trySync(function decodePayload() {
7798
- const json = globalThis.atob(paddedBase64(segment));
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
- return decoded.data;
7805
- }
7806
- function numericClaim(payload, claim) {
7807
- if (typeof payload !== "object" || payload === null) {
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 value = Reflect.get(payload, claim);
7811
- if (typeof value !== "number" || !Number.isFinite(value)) {
7851
+ const raw = parsed.data;
7852
+ if (typeof raw !== "object" || raw === null) {
7812
7853
  return null;
7813
7854
  }
7814
- return value;
7815
- }
7816
- function validateAccessTokenTimestamp(token, logger) {
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");
7855
+ const result = {};
7856
+ const error = readStringField(raw, "error");
7857
+ if (error !== undefined) {
7858
+ result.error = error;
7822
7859
  }
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");
7860
+ const detail = readStringField(raw, "detail");
7861
+ if (detail !== undefined) {
7862
+ result.detail = detail;
7828
7863
  }
7829
- const expiresAtMs = exp * 1000;
7830
- if (expiresAtMs <= Date.now() + TOKEN_EXPIRY_SKEW_MS) {
7831
- logger.error("access token expired");
7832
- throw ErrTokenExpired;
7864
+ const minimumSdkVersion = readStringField(raw, "minimumSdkVersion");
7865
+ if (minimumSdkVersion !== undefined) {
7866
+ result.minimumSdkVersion = minimumSdkVersion;
7833
7867
  }
7834
- }
7835
- function resolveAccessToken(token, logger) {
7836
- if (isMalformedJws(token)) {
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
- validateAccessTokenTimestamp(token, logger);
7841
- return { value: token, [resolvedAccessTokenBrand]: true };
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 { kind: "resolved", accessToken: result.data };
7876
+ return result;
7853
7877
  }
7854
- function resolveHostedAccessToken(token, logger) {
7855
- const result = errors12.trySync(function resolveHostedToken() {
7856
- return resolveAccessToken(token, logger);
7857
- });
7858
- if (result.error) {
7859
- return { kind: "sign-in-failed", error: result.error };
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
- return { kind: "resolved", accessToken: result.data };
7862
- }
7863
- function resolveExistingAccessToken(options) {
7864
- if (options.accessToken !== undefined) {
7865
- return resolveProvidedAccessToken(options.accessToken, options.logger);
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
- return { kind: "missing" };
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 (errors12.is(error, ErrAuthUnavailable)) {
7874
- return { kind: "auth-unavailable", error };
7896
+ if (status === 404) {
7897
+ return ErrNotFound;
7875
7898
  }
7876
- return { kind: "sign-in-failed", error };
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
- async function beginHostedLogin(options) {
7879
- const logger = options.logger;
7880
- const contextResult = errors12.trySync(function readContext() {
7881
- const url = currentUrl(logger);
7882
- const clientState = randomClientState(logger);
7883
- return { url, clientState };
7884
- });
7885
- if (contextResult.error) {
7886
- return authFailureResult(contextResult.error);
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 context = contextResult.data;
7889
- const accessTokenResult = await errors12.try(beginHostedPopup({
7890
- origin: options.origin,
7891
- publishableKey: options.publishableKey,
7892
- currentUrl: context.url,
7893
- clientState: context.clientState,
7894
- logger
7895
- }));
7896
- if (accessTokenResult.error) {
7897
- return authFailureResult(accessTokenResult.error);
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 resolveHostedAccessToken(accessTokenResult.data, logger);
7940
+ return false;
7900
7941
  }
7901
-
7902
- // src/client/auth-state.ts
7903
- function pendingLogin(config) {
7904
- let pending;
7905
- function login() {
7906
- if (pending !== undefined) {
7907
- return pending;
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
- pending = config.login().finally(function clearPending() {
7910
- pending = undefined;
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
- return pending;
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 login;
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.reauthenticate !== undefined) {
7969
- reauthenticate = resolved.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
- reauthenticate: function reauthenticate() {
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=1270BDB9A874B19364756E2164756E21
8142
+ //# debugId=8BB175F09E303E4A64756E2164756E21