@superbuilders/primer-tives 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ bun add @superbuilders/primer-tives
17
17
 
18
18
  ## Version
19
19
 
20
- The current SDK version is `4.0.0`.
20
+ The current SDK version is `4.0.1`.
21
21
 
22
22
  ## Entrypoints
23
23
 
@@ -38,7 +38,7 @@ There is no package-root export. Import from the public subpath that owns the su
38
38
  Math content can require the fraction-input PCI capability, so a math renderer must declare it.
39
39
 
40
40
  ```ts
41
- import * as logger from "@superbuilders/slog"
41
+ import { logger } from "@/logger"
42
42
  import { start, type PrimerOptions } from "@superbuilders/primer-tives/client"
43
43
 
44
44
  const options = {
@@ -133,7 +133,7 @@ Always switch on `state.phase`. Do not assume the first state is renderable lear
133
133
 
134
134
  ```ts
135
135
  import * as errors from "@superbuilders/errors"
136
- import * as logger from "@superbuilders/slog"
136
+ import { logger } from "@/logger"
137
137
  import { start, type PrimerOptions } from "@superbuilders/primer-tives/client"
138
138
  import { ErrAuthUnavailable, ErrMalformedAccessToken } from "@superbuilders/primer-tives/errors"
139
139
 
@@ -222,7 +222,7 @@ Applications should handle user-actionable auth failures directly and log unexpe
222
222
 
223
223
  ```ts
224
224
  import * as errors from "@superbuilders/errors"
225
- import * as logger from "@superbuilders/slog"
225
+ import { logger } from "@/logger"
226
226
  import {
227
227
  ErrAuthCancelled,
228
228
  ErrAuthPopupBlocked,
@@ -309,7 +309,7 @@ The runtime also protects the trust boundary. If Primer presents a portable cust
309
309
  Every renderer should switch on `state.phase`. Interaction rendering should then switch on `state.kind`.
310
310
 
311
311
  ```ts
312
- import * as logger from "@superbuilders/slog"
312
+ import { logger } from "@/logger"
313
313
  import type { PrimerState } from "@superbuilders/primer-tives/client"
314
314
 
315
315
  async function runPrimer(initialState: PrimerState): Promise<void> {
@@ -916,7 +916,7 @@ Always use `safeParse` when parsing arbitrary input.
916
916
 
917
917
  ```ts
918
918
  import * as errors from "@superbuilders/errors"
919
- import * as logger from "@superbuilders/slog"
919
+ import { logger } from "@/logger"
920
920
  import { RendererSubmissionSchema } from "@superbuilders/primer-tives/contracts"
921
921
 
922
922
  const parsed = RendererSubmissionSchema.safeParse(payload)
@@ -965,7 +965,7 @@ The built-in standard interaction state methods call this before submitting. Cus
965
965
 
966
966
  ```ts
967
967
  import * as errors from "@superbuilders/errors"
968
- import * as logger from "@superbuilders/slog"
968
+ import { logger } from "@/logger"
969
969
  import {
970
970
  submissionValidationMessage,
971
971
  validateSubmissionForInteraction
@@ -1274,10 +1274,10 @@ interface PrimerLogger {
1274
1274
  }
1275
1275
  ```
1276
1276
 
1277
- `@superbuilders/slog` matches this interface directly.
1277
+ Pino through the central `@/logger` module matches this interface directly.
1278
1278
 
1279
1279
  ```ts
1280
- import * as logger from "@superbuilders/slog"
1280
+ import { logger } from "@/logger"
1281
1281
  import { start, type PrimerOptions } from "@superbuilders/primer-tives/client"
1282
1282
 
1283
1283
  const options = {
@@ -1 +1 @@
1
- {"version":3,"file":"access-token.d.ts","sourceRoot":"","sources":["../../../src/client/auth/access-token.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAGtE,QAAA,MAAM,wBAAwB,EAAE,OAAO,MAA+C,CAAA;AAEtF,KAAK,mBAAmB,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,CAAC,wBAAwB,CAAC,EAAE,IAAI,CAAA;CACzC,CAAA;AAUD,iBAAS,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,mBAAmB,CASpF;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAC7B,YAAY,EAAE,mBAAmB,EAAE,CAAA"}
1
+ {"version":3,"file":"access-token.d.ts","sourceRoot":"","sources":["../../../src/client/auth/access-token.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAItE,QAAA,MAAM,wBAAwB,EAAE,OAAO,MAA+C,CAAA;AAEtF,KAAK,mBAAmB,GAAG;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,CAAC,wBAAwB,CAAC,EAAE,IAAI,CAAA;CACzC,CAAA;AAmED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,mBAAmB,CAUpF;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAC7B,YAAY,EAAE,mBAAmB,EAAE,CAAA"}
@@ -11,6 +11,7 @@ type AccessTokenResolverOptions = {
11
11
  type ResolvedAccessTokenResult = {
12
12
  readonly kind: "resolved";
13
13
  readonly accessToken: ResolvedAccessToken;
14
+ readonly invalidate?: () => void;
14
15
  };
15
16
  type UnauthenticatedAccessTokenResult = {
16
17
  readonly kind: "unauthenticated";
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/client/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,EAIN,KAAK,iBAAiB,EACtB,MAAM,iDAAiD,CAAA;AAExD,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,sDAAsD,CAAA;AAS7D,KAAK,0BAA0B,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B,CAAA;AAED,KAAK,yBAAyB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;CACzC,CAAA;AAED,KAAK,gCAAgC,GAAG;IACvC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAC5B,CAAA;AAED,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACrB,CAAA;AAED,KAAK,yBAAyB,GAC3B,yBAAyB,GACzB,gCAAgC,GAChC,sBAAsB,CAAA;AAEzB,KAAK,iBAAiB,GAAG,yBAAyB,GAAG,gCAAgC,CAAA;AA4DrF,iBAAS,0BAA0B,CAClC,OAAO,EAAE,0BAA0B,GACjC,yBAAyB,CAe3B;AAED,iBAAe,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqC/F;AAED,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,CAAA"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/client/auth/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,EAIN,KAAK,iBAAiB,EACtB,MAAM,iDAAiD,CAAA;AAExD,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,sDAAsD,CAAA;AAS7D,KAAK,0BAA0B,GAAG;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B,CAAA;AAED,KAAK,yBAAyB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;CAChC,CAAA;AAED,KAAK,gCAAgC,GAAG;IACvC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAC5B,CAAA;AAED,KAAK,sBAAsB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACrB,CAAA;AAED,KAAK,yBAAyB,GAC3B,yBAAyB,GACzB,gCAAgC,GAChC,sBAAsB,CAAA;AAEzB,KAAK,iBAAiB,GAAG,yBAAyB,GAAG,gCAAgC,CAAA;AAmErF,iBAAS,0BAA0B,CAClC,OAAO,EAAE,0BAA0B,GACjC,yBAAyB,CAe3B;AAED,iBAAe,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0C/F;AAED,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,CAAA;AACvD,YAAY,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"consumed.d.ts","sourceRoot":"","sources":["../../src/client/consumed.ts"],"names":[],"mappings":"AAEA,iBAAS,YAAY,IAAI,KAAK,CAG7B;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"consumed.d.ts","sourceRoot":"","sources":["../../src/client/consumed.ts"],"names":[],"mappings":"AAEA,iBAAS,YAAY,IAAI,KAAK,CAE7B;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -379,7 +379,7 @@ function submissionValidationMessage(result) {
379
379
  import * as errors2 from "@superbuilders/errors";
380
380
 
381
381
  // src/version.ts
382
- var SDK_VERSION = "4.0.0";
382
+ var SDK_VERSION = "4.0.1";
383
383
  var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
384
384
 
385
385
  // src/client/transport.ts
@@ -466,11 +466,11 @@ function httpSentinel(status, body) {
466
466
  function buildSdkUpgradeRequiredError(sentinel, text, logger) {
467
467
  const parsed = parseAdvanceErrorBody(text);
468
468
  if (parsed === null) {
469
- logger.error("sdk upgrade required", {
469
+ logger.error({
470
470
  receivedSdkVersion: null,
471
471
  minimumSdkVersion: "<unknown>",
472
472
  upgradeUrl: NPM_PACKAGE_URL
473
- });
473
+ }, "sdk upgrade required");
474
474
  const message2 = `<missing> < <unknown>; bump @superbuilders/primer-tives at ${NPM_PACKAGE_URL}`;
475
475
  return errors2.wrap(sentinel, message2);
476
476
  }
@@ -478,11 +478,11 @@ function buildSdkUpgradeRequiredError(sentinel, text, logger) {
478
478
  const receivedSdkVersion = parsed.receivedSdkVersion === undefined ? null : parsed.receivedSdkVersion;
479
479
  const receivedDisplay = receivedSdkVersion === null ? "<missing>" : receivedSdkVersion;
480
480
  const upgradeUrl = parsed.upgradeUrl === undefined ? NPM_PACKAGE_URL : parsed.upgradeUrl;
481
- logger.error("sdk upgrade required", {
481
+ logger.error({
482
482
  receivedSdkVersion,
483
483
  minimumSdkVersion,
484
484
  upgradeUrl
485
- });
485
+ }, "sdk upgrade required");
486
486
  const message = `${receivedDisplay} < ${minimumSdkVersion}; bump @superbuilders/primer-tives at ${upgradeUrl}`;
487
487
  return errors2.wrap(sentinel, message);
488
488
  }
@@ -506,10 +506,10 @@ function createTransport(tc) {
506
506
  return;
507
507
  }
508
508
  async function transport(body) {
509
- logger.debug("transport request", {
509
+ logger.debug({
510
510
  intentKind: body.intent.kind,
511
511
  subject: body.subject
512
- });
512
+ }, "transport request");
513
513
  const fetchResult = await fetchFn(advanceUrl, {
514
514
  method: "POST",
515
515
  mode: "cors",
@@ -529,14 +529,14 @@ function createTransport(tc) {
529
529
  });
530
530
  if (!fetchResult.ok) {
531
531
  if (isAbortError(fetchResult.error)) {
532
- logger.error("transport timeout", {
532
+ logger.error({
533
533
  intentKind: body.intent.kind
534
- });
534
+ }, "transport timeout");
535
535
  return { ok: false, error: errors2.wrap(ErrTimeout, fetchResult.error.message) };
536
536
  }
537
- logger.error("transport network error", {
537
+ logger.error({
538
538
  error: fetchResult.error
539
- });
539
+ }, "transport network error");
540
540
  return { ok: false, error: errors2.wrap(ErrNetwork, fetchResult.error.message) };
541
541
  }
542
542
  const res = fetchResult.response;
@@ -548,12 +548,12 @@ function createTransport(tc) {
548
548
  if (errors2.is(sentinel, ErrSdkUpgradeRequired)) {
549
549
  return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
550
550
  }
551
- logger.error("transport http error", {
551
+ logger.error({
552
552
  status: res.status,
553
553
  body: text,
554
554
  intentKind: body.intent.kind,
555
555
  subject: body.subject
556
- });
556
+ }, "transport http error");
557
557
  return { ok: false, error: errors2.wrap(sentinel, text) };
558
558
  }
559
559
  const jsonResult = await res.json().then(function ok(data) {
@@ -562,14 +562,14 @@ function createTransport(tc) {
562
562
  return { ok: false, error: err };
563
563
  });
564
564
  if (!jsonResult.ok) {
565
- logger.error("transport json parse failed", {
565
+ logger.error({
566
566
  error: jsonResult.error
567
- });
567
+ }, "transport json parse failed");
568
568
  return { ok: false, error: errors2.wrap(ErrJsonParse, jsonResult.error.message) };
569
569
  }
570
- logger.debug("transport success", {
570
+ logger.debug({
571
571
  intentKind: body.intent.kind
572
- });
572
+ }, "transport success");
573
573
  return { ok: true, data: jsonResult.data };
574
574
  }
575
575
  return transport;
@@ -603,7 +603,7 @@ function choiceState(ctx, body, stimulus, interaction, options, maxChoices, minC
603
603
  }
604
604
  const validation = validateSubmissionForInteraction(interaction, submission);
605
605
  if (!validation.ok) {
606
- ctx.logger.error("choice submit invalid", { selectedKeys, issues: validation.issues });
606
+ ctx.logger.error({ selectedKeys, issues: validation.issues }, "choice submit invalid");
607
607
  return Promise.resolve(ctx.errored(errors3.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
608
608
  }
609
609
  submitKey = key;
@@ -659,7 +659,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
659
659
  }
660
660
  const validation = validateSubmissionForInteraction(interaction, submission);
661
661
  if (!validation.ok) {
662
- ctx.logger.error("extended-text submit invalid", { value, issues: validation.issues });
662
+ ctx.logger.error({ value, issues: validation.issues }, "extended-text submit invalid");
663
663
  return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
664
664
  }
665
665
  submitKey2 = key;
@@ -714,7 +714,7 @@ function extendedTextState(ctx, body, stimulus, interaction) {
714
714
  }
715
715
  const validation = validateSubmissionForInteraction(multi, submission);
716
716
  if (!validation.ok) {
717
- ctx.logger.error("extended-text submit invalid", { values, issues: validation.issues });
717
+ ctx.logger.error({ values, issues: validation.issues }, "extended-text submit invalid");
718
718
  return Promise.resolve(ctx.errored(errors4.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
719
719
  }
720
720
  submitKey = key;
@@ -795,7 +795,7 @@ function matchState(ctx, body, stimulus, interaction) {
795
795
  }
796
796
  const validation = validateSubmissionForInteraction(interaction, submission);
797
797
  if (!validation.ok) {
798
- ctx.logger.error("match submit invalid", { pairs, issues: validation.issues });
798
+ ctx.logger.error({ pairs, issues: validation.issues }, "match submit invalid");
799
799
  return Promise.resolve(ctx.errored(errors5.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
800
800
  }
801
801
  submitKey = key;
@@ -872,7 +872,7 @@ function orderState(ctx, body, stimulus, interaction) {
872
872
  }
873
873
  const validation = validateSubmissionForInteraction(interaction, submission);
874
874
  if (!validation.ok) {
875
- ctx.logger.error("order submit invalid", { orderedKeys, issues: validation.issues });
875
+ ctx.logger.error({ orderedKeys, issues: validation.issues }, "order submit invalid");
876
876
  return Promise.resolve(ctx.errored(errors6.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
877
877
  }
878
878
  submitKey = key;
@@ -929,7 +929,7 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
929
929
  }
930
930
  return Promise.resolve(ctx.errored(errors7.wrap(ErrConflict, "cannot submit a different pci payload while submit is in flight"), "interaction", { kind: "interaction", submission }));
931
931
  }
932
- ctx.logger.debug("pci submit", { pciId });
932
+ ctx.logger.debug({ pciId }, "pci submit");
933
933
  submitKey = key;
934
934
  submitPending = ctx.execute({ kind: "interaction", submission }, "interaction").finally(function clearPending() {
935
935
  submitPending = undefined;
@@ -945,7 +945,7 @@ function pciInteractionState(ctx, body, stimulus, interaction) {
945
945
  if (timeoutPending) {
946
946
  return timeoutPending;
947
947
  }
948
- ctx.logger.debug("pci timeout", { pciId });
948
+ ctx.logger.debug({ pciId }, "pci timeout");
949
949
  timeoutPending = ctx.execute(intent, "timeout").finally(function clearPending() {
950
950
  timeoutPending = undefined;
951
951
  });
@@ -985,7 +985,7 @@ function textEntryState(ctx, body, stimulus, interaction) {
985
985
  }
986
986
  const validation = validateSubmissionForInteraction(interaction, submission);
987
987
  if (!validation.ok) {
988
- ctx.logger.error("text-entry submit invalid", { value, issues: validation.issues });
988
+ ctx.logger.error({ value, issues: validation.issues }, "text-entry submit invalid");
989
989
  return Promise.resolve(ctx.errored(errors8.wrap(ErrInvalidSubmission, submissionValidationMessage(validation)), "interaction", { kind: "interaction", submission }));
990
990
  }
991
991
  submitKey = key;
@@ -1073,13 +1073,13 @@ function makeSession(sc) {
1073
1073
  retriable,
1074
1074
  retry: function retry() {
1075
1075
  if (!retriable) {
1076
- logger.debug("retry ignored for non-retriable error", { failedPhase });
1076
+ logger.debug({ failedPhase }, "retry ignored for non-retriable error");
1077
1077
  return Promise.resolve(state);
1078
1078
  }
1079
1079
  if (pending) {
1080
1080
  return pending;
1081
1081
  }
1082
- logger.debug("retrying from errored state", { failedPhase });
1082
+ logger.debug({ failedPhase }, "retrying from errored state");
1083
1083
  pending = execute(intent, failedPhase);
1084
1084
  return pending;
1085
1085
  },
@@ -1088,23 +1088,27 @@ function makeSession(sc) {
1088
1088
  return state;
1089
1089
  }
1090
1090
  async function execute(intent, phase) {
1091
- logger.debug("session execute", {
1091
+ logger.debug({
1092
1092
  phase,
1093
1093
  intentKind: intent.kind,
1094
1094
  subject: sc.subject
1095
- });
1095
+ }, "session execute");
1096
1096
  const body = {
1097
1097
  subject: sc.subject,
1098
1098
  intent
1099
1099
  };
1100
1100
  const result = await sc.transport(body);
1101
1101
  if (!result.ok) {
1102
+ if (errors9.is(result.error, ErrTokenExpired) && sc.reauthenticate !== null) {
1103
+ logger.warn({ phase, intentKind: intent.kind }, "session token expired");
1104
+ return sc.reauthenticate();
1105
+ }
1102
1106
  if (isFatalError(result.error)) {
1103
- logger.error("fatal transport error", {
1107
+ logger.error({
1104
1108
  error: result.error,
1105
1109
  phase,
1106
1110
  intentKind: intent.kind
1107
- });
1111
+ }, "fatal transport error");
1108
1112
  return {
1109
1113
  phase: "fatal",
1110
1114
  error: result.error,
@@ -1112,20 +1116,20 @@ function makeSession(sc) {
1112
1116
  toJSON: poisonToJSON
1113
1117
  };
1114
1118
  }
1115
- logger.warn("retriable transport error", {
1119
+ logger.warn({
1116
1120
  error: result.error,
1117
1121
  phase,
1118
1122
  intentKind: intent.kind
1119
- });
1123
+ }, "retriable transport error");
1120
1124
  return errored(result.error, phase, intent);
1121
1125
  }
1122
1126
  const next = resolve(result.data);
1123
- logger.debug("session execute resolved", {
1127
+ logger.debug({
1124
1128
  phase,
1125
1129
  intentKind: intent.kind,
1126
1130
  outcome: result.data.outcome,
1127
1131
  nextPhase: next.phase
1128
- });
1132
+ }, "session execute resolved");
1129
1133
  return next;
1130
1134
  }
1131
1135
  function isPciSupported(pciId) {
@@ -1142,7 +1146,7 @@ function makeSession(sc) {
1142
1146
  }
1143
1147
  if (interaction.type === "portable-custom") {
1144
1148
  if (!isPciSupported(interaction.pciId)) {
1145
- logger.error("unsupported pci in frame", { pciId: interaction.pciId });
1149
+ logger.error({ pciId: interaction.pciId }, "unsupported pci in frame");
1146
1150
  return {
1147
1151
  phase: "fatal",
1148
1152
  error: errors9.wrap(ErrUnsupportedPci, `pci '${interaction.pciId}'`),
@@ -1190,7 +1194,7 @@ function browserStorage(options, logger) {
1190
1194
  function currentUrl(options, logger) {
1191
1195
  if (options !== undefined && options.currentUrl !== undefined) {
1192
1196
  if (!URL.canParse(options.currentUrl)) {
1193
- logger.error("auth current url invalid", { currentUrl: options.currentUrl });
1197
+ logger.error({ currentUrl: options.currentUrl }, "auth current url invalid");
1194
1198
  throw ErrAuthConfigInvalid;
1195
1199
  }
1196
1200
  return new URL(options.currentUrl);
@@ -1204,7 +1208,7 @@ function currentUrl(options, logger) {
1204
1208
  function redirectUri(options, url, logger) {
1205
1209
  if (options !== undefined && options.redirectUri !== undefined) {
1206
1210
  if (!URL.canParse(options.redirectUri)) {
1207
- logger.error("auth redirect uri invalid", { redirectUri: options.redirectUri });
1211
+ logger.error({ redirectUri: options.redirectUri }, "auth redirect uri invalid");
1208
1212
  throw ErrAuthConfigInvalid;
1209
1213
  }
1210
1214
  return options.redirectUri;
@@ -1253,7 +1257,7 @@ var AUTH_RESPONSE_MODE = "web_message";
1253
1257
  function hostedAuthUrl(config) {
1254
1258
  const logger = config.logger;
1255
1259
  if (!URL.canParse(config.origin)) {
1256
- logger.error("hosted auth origin invalid", { origin: config.origin });
1260
+ logger.error({ origin: config.origin }, "hosted auth origin invalid");
1257
1261
  throw ErrAuthCallbackInvalid;
1258
1262
  }
1259
1263
  const authUrl = new URL("/auth/timeback", config.origin);
@@ -1362,7 +1366,7 @@ async function waitForPopupMessage(popup, config, expectedOrigin) {
1362
1366
  return;
1363
1367
  }
1364
1368
  if (result2.kind === "error") {
1365
- logger.error("hosted auth popup failed", { error: result2.error });
1369
+ logger.error({ error: result2.error }, "hosted auth popup failed");
1366
1370
  finishWithError(result2.error);
1367
1371
  return;
1368
1372
  }
@@ -1385,7 +1389,7 @@ async function beginHostedPopup(config) {
1385
1389
  const logger = config.logger;
1386
1390
  const url = hostedAuthUrl(config);
1387
1391
  if (!URL.canParse(config.origin)) {
1388
- logger.error("hosted auth origin invalid", { origin: config.origin });
1392
+ logger.error({ origin: config.origin }, "hosted auth origin invalid");
1389
1393
  throw ErrAuthCallbackInvalid;
1390
1394
  }
1391
1395
  const expectedOrigin = new URL(config.origin).origin;
@@ -1396,6 +1400,7 @@ async function beginHostedPopup(config) {
1396
1400
  // src/client/auth/access-token.ts
1397
1401
  import * as errors10 from "@superbuilders/errors";
1398
1402
  var ACCESS_TOKEN_PREFIX = "eyJ";
1403
+ var TOKEN_EXPIRY_SKEW_MS = 60000;
1399
1404
  var resolvedAccessTokenBrand = Symbol("primer resolved access token");
1400
1405
  function isMalformedJws(token) {
1401
1406
  if (!token.startsWith(ACCESS_TOKEN_PREFIX)) {
@@ -1404,11 +1409,65 @@ function isMalformedJws(token) {
1404
1409
  const dotCount = token.split(".").length - 1;
1405
1410
  return dotCount !== 2;
1406
1411
  }
1412
+ function paddedBase64(base64Url) {
1413
+ const normalized = base64Url.replaceAll("-", "+").replaceAll("_", "/");
1414
+ const remainder = normalized.length % 4;
1415
+ if (remainder === 0) {
1416
+ return normalized;
1417
+ }
1418
+ if (remainder === 2) {
1419
+ return `${normalized}==`;
1420
+ }
1421
+ if (remainder === 3) {
1422
+ return `${normalized}=`;
1423
+ }
1424
+ throw errors10.wrap(ErrMalformedAccessToken, "payload base64");
1425
+ }
1426
+ function decodeBase64UrlJson(segment) {
1427
+ const decoded = errors10.trySync(function decodePayload() {
1428
+ const json = globalThis.atob(paddedBase64(segment));
1429
+ return JSON.parse(json);
1430
+ });
1431
+ if (decoded.error) {
1432
+ throw errors10.wrap(ErrMalformedAccessToken, "payload decode");
1433
+ }
1434
+ return decoded.data;
1435
+ }
1436
+ function numericClaim(payload, claim) {
1437
+ if (typeof payload !== "object" || payload === null) {
1438
+ return null;
1439
+ }
1440
+ const value = Reflect.get(payload, claim);
1441
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1442
+ return null;
1443
+ }
1444
+ return value;
1445
+ }
1446
+ function validateAccessTokenTimestamp(token, logger) {
1447
+ const segments = token.split(".");
1448
+ const payloadSegment = segments[1];
1449
+ if (payloadSegment === undefined || payloadSegment.length === 0) {
1450
+ logger.error("access token payload missing");
1451
+ throw errors10.wrap(ErrMalformedAccessToken, "payload missing");
1452
+ }
1453
+ const payload = decodeBase64UrlJson(payloadSegment);
1454
+ const exp = numericClaim(payload, "exp");
1455
+ if (exp === null) {
1456
+ logger.error("access token exp missing");
1457
+ throw errors10.wrap(ErrMalformedAccessToken, "exp missing");
1458
+ }
1459
+ const expiresAtMs = exp * 1000;
1460
+ if (expiresAtMs <= Date.now() + TOKEN_EXPIRY_SKEW_MS) {
1461
+ logger.error("access token expired");
1462
+ throw ErrTokenExpired;
1463
+ }
1464
+ }
1407
1465
  function resolveAccessToken(token, logger) {
1408
1466
  if (isMalformedJws(token)) {
1409
- logger.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
1467
+ logger.error({ prefix: ACCESS_TOKEN_PREFIX }, "malformed access token");
1410
1468
  throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
1411
1469
  }
1470
+ validateAccessTokenTimestamp(token, logger);
1412
1471
  return { value: token, [resolvedAccessTokenBrand]: true };
1413
1472
  }
1414
1473
 
@@ -1451,14 +1510,14 @@ function resolveProvidedAccessToken(token, logger) {
1451
1510
  }
1452
1511
  return { kind: "resolved", accessToken: result.data };
1453
1512
  }
1454
- function resolveManagedAccessToken(token, logger) {
1513
+ function resolveManagedAccessToken(token, logger, invalidate) {
1455
1514
  const result = errors11.trySync(function resolveManagedToken() {
1456
1515
  return resolveAccessToken(token, logger);
1457
1516
  });
1458
1517
  if (result.error) {
1459
1518
  return { kind: "unauthenticated", error: result.error };
1460
1519
  }
1461
- return { kind: "resolved", accessToken: result.data };
1520
+ return { kind: "resolved", accessToken: result.data, invalidate };
1462
1521
  }
1463
1522
  function readBrowserAuthContext(options) {
1464
1523
  const logger = options.logger;
@@ -1477,10 +1536,13 @@ function resolveStoredAccessToken(context, options) {
1477
1536
  if (stored === null) {
1478
1537
  return null;
1479
1538
  }
1480
- const resolved = resolveManagedAccessToken(stored, options.logger);
1481
- if (resolved.kind === "unauthenticated") {
1539
+ function invalidate() {
1482
1540
  clearStoredAccessToken(context.storage, options.publishableKey);
1483
1541
  }
1542
+ const resolved = resolveManagedAccessToken(stored, options.logger, invalidate);
1543
+ if (resolved.kind === "unauthenticated") {
1544
+ invalidate();
1545
+ }
1484
1546
  return resolved;
1485
1547
  }
1486
1548
  function resolveExistingAccessToken(options) {
@@ -1503,9 +1565,11 @@ async function beginHostedLogin(options) {
1503
1565
  if ("kind" in context) {
1504
1566
  return context;
1505
1567
  }
1568
+ const storage = context.storage;
1569
+ const url = context.url;
1506
1570
  const clientStateResult = errors11.trySync(function prepareHostedLogin() {
1507
1571
  const clientState2 = randomClientState(logger);
1508
- storeAuthState(context.storage, options.publishableKey, clientState2);
1572
+ storeAuthState(storage, options.publishableKey, clientState2);
1509
1573
  return clientState2;
1510
1574
  });
1511
1575
  if (clientStateResult.error) {
@@ -1515,22 +1579,25 @@ async function beginHostedLogin(options) {
1515
1579
  const accessTokenResult = await errors11.try(beginHostedPopup({
1516
1580
  origin: options.origin,
1517
1581
  publishableKey: options.publishableKey,
1518
- currentUrl: context.url,
1582
+ currentUrl: url,
1519
1583
  clientState,
1520
1584
  options: options.hostedAuth,
1521
1585
  logger
1522
1586
  }));
1523
1587
  if (accessTokenResult.error) {
1524
- clearAuthState(context.storage, options.publishableKey);
1588
+ clearAuthState(storage, options.publishableKey);
1525
1589
  return { kind: "unauthenticated", error: accessTokenResult.error };
1526
1590
  }
1527
- const resolved = resolveManagedAccessToken(accessTokenResult.data, logger);
1591
+ function invalidate() {
1592
+ clearStoredAccessToken(storage, options.publishableKey);
1593
+ }
1594
+ const resolved = resolveManagedAccessToken(accessTokenResult.data, logger, invalidate);
1528
1595
  if (resolved.kind === "unauthenticated") {
1529
- clearAuthState(context.storage, options.publishableKey);
1596
+ clearAuthState(storage, options.publishableKey);
1530
1597
  return resolved;
1531
1598
  }
1532
- storeAccessToken(context.storage, options.publishableKey, accessTokenResult.data);
1533
- clearAuthState(context.storage, options.publishableKey);
1599
+ storeAccessToken(storage, options.publishableKey, accessTokenResult.data);
1600
+ clearAuthState(storage, options.publishableKey);
1534
1601
  return resolved;
1535
1602
  }
1536
1603
 
@@ -1574,9 +1641,17 @@ function primerOrigin(origin) {
1574
1641
  }
1575
1642
  return DEFAULT_PRIMER_ORIGIN;
1576
1643
  }
1577
- async function startRuntime(config, accessToken) {
1644
+ async function startRuntime(config, resolved) {
1645
+ let reauthenticate = null;
1646
+ const invalidate = resolved.invalidate;
1647
+ if (invalidate !== undefined) {
1648
+ reauthenticate = function reauthenticate2() {
1649
+ invalidate();
1650
+ return Promise.resolve(makeUnauthenticatedState(config, ErrTokenExpired));
1651
+ };
1652
+ }
1578
1653
  const transport = createTransport({
1579
- accessToken,
1654
+ accessToken: resolved.accessToken,
1580
1655
  publishableKey: config.publishableKey,
1581
1656
  subject: config.subject,
1582
1657
  origin: config.origin,
@@ -1588,7 +1663,8 @@ async function startRuntime(config, accessToken) {
1588
1663
  subject: config.subject,
1589
1664
  supportedPcis: config.supportedPcis,
1590
1665
  logger: config.logger,
1591
- transport
1666
+ transport,
1667
+ reauthenticate
1592
1668
  });
1593
1669
  return session.execute({ kind: "observation" }, "start");
1594
1670
  }
@@ -1609,7 +1685,7 @@ async function loginAndStart(config) {
1609
1685
  if (result.kind === "unauthenticated") {
1610
1686
  return makeUnauthenticatedState(config, result.error);
1611
1687
  }
1612
- return startRuntime(config, result.accessToken);
1688
+ return startRuntime(config, result);
1613
1689
  }
1614
1690
  async function start(options) {
1615
1691
  const logger = options.logger;
@@ -1641,10 +1717,10 @@ async function start(options) {
1641
1717
  if (accessToken.kind === "unauthenticated") {
1642
1718
  return makeUnauthenticatedState(config, accessToken.error);
1643
1719
  }
1644
- return startRuntime(config, accessToken.accessToken);
1720
+ return startRuntime(config, accessToken);
1645
1721
  }
1646
1722
  export {
1647
1723
  start
1648
1724
  };
1649
1725
 
1650
- //# debugId=C8409E7AF1FF3E2964756E2164756E21
1726
+ //# debugId=F138BD9662B6EC5C64756E2164756E21