@coinlist-co/react 0.12.2-rc.7b430dd → 0.12.2-rc.a59b344

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 (33) hide show
  1. package/dist/{chunk-DCW7NQXH.js → chunk-4IZ2F732.js} +52 -19
  2. package/dist/chunk-4IZ2F732.js.map +1 -0
  3. package/dist/{chunk-MOE5S6CE.js → chunk-NBO5E2RO.js} +152 -6
  4. package/dist/chunk-NBO5E2RO.js.map +1 -0
  5. package/dist/{chunk-VH34YC3Q.js → chunk-UJYSXXJF.js} +168 -3
  6. package/dist/chunk-UJYSXXJF.js.map +1 -0
  7. package/dist/client/index.cjs +469 -145
  8. package/dist/client/index.cjs.map +1 -1
  9. package/dist/client/index.d.cts +114 -35
  10. package/dist/client/index.d.ts +114 -35
  11. package/dist/client/index.js +297 -114
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/server/index.cjs +507 -116
  14. package/dist/server/index.cjs.map +1 -1
  15. package/dist/server/index.d.cts +65 -41
  16. package/dist/server/index.d.ts +65 -41
  17. package/dist/server/index.js +321 -65
  18. package/dist/server/index.js.map +1 -1
  19. package/dist/time-CFocgYyl.d.cts +50 -0
  20. package/dist/time-DvgN98Q9.d.ts +50 -0
  21. package/dist/{token-registry-snapshot-BNBNuDu1.d.ts → token-registry-snapshot-BsAfBL_b.d.ts} +9 -7
  22. package/dist/{token-registry-snapshot-CqI6dXM_.d.cts → token-registry-snapshot-Cc_eA5zM.d.cts} +9 -7
  23. package/dist/{tokens-namespace-CIWBtgt2.d.cts → tokens-namespace-D_XSpiS-.d.cts} +154 -19
  24. package/dist/{tokens-namespace-CIWBtgt2.d.ts → tokens-namespace-D_XSpiS-.d.ts} +154 -19
  25. package/dist/universal/index.cjs +333 -40
  26. package/dist/universal/index.cjs.map +1 -1
  27. package/dist/universal/index.d.cts +358 -5
  28. package/dist/universal/index.d.ts +358 -5
  29. package/dist/universal/index.js +28 -2
  30. package/package.json +1 -1
  31. package/dist/chunk-DCW7NQXH.js.map +0 -1
  32. package/dist/chunk-MOE5S6CE.js.map +0 -1
  33. package/dist/chunk-VH34YC3Q.js.map +0 -1
@@ -20,7 +20,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/server/index.ts
21
21
  var server_exports = {};
22
22
  __export(server_exports, {
23
- ServerAuthNamespaceImpl: () => ServerAuthNamespaceImpl,
24
23
  ServerOffersNamespaceImpl: () => ServerOffersNamespaceImpl,
25
24
  ServerRequirementsNamespaceImpl: () => ServerRequirementsNamespaceImpl,
26
25
  WritableSessionStoreRequiredError: () => WritableSessionStoreRequiredError,
@@ -30,6 +29,30 @@ __export(server_exports, {
30
29
  });
31
30
  module.exports = __toCommonJS(server_exports);
32
31
 
32
+ // src/universal/api/internal-access-token-source.ts
33
+ var SentWith = {
34
+ userToken: (token) => ({
35
+ type: "user-token",
36
+ token
37
+ }),
38
+ noUserToken: { type: "no-user-token" }
39
+ };
40
+ var TokenTrigger = {
41
+ beforeRequest: { type: "before-request" },
42
+ after401: (rejected) => ({
43
+ type: "after-401",
44
+ rejected
45
+ })
46
+ };
47
+ function isRejectedToken(token, rejected) {
48
+ switch (rejected.type) {
49
+ case "user-token":
50
+ return rejected.token.value === token.value;
51
+ case "no-user-token":
52
+ return false;
53
+ }
54
+ }
55
+
33
56
  // src/universal/api/http-attributes.ts
34
57
  var empty = {};
35
58
  var concat = (left, right) => ({
@@ -57,6 +80,10 @@ var retryAttempt = (attempt) => ({
57
80
  var renewAttempted = (value) => ({
58
81
  renewAttempted: value
59
82
  });
83
+ var notRetryable = () => ({ retryable: false });
84
+ var sentWith = (token) => ({
85
+ sentWith: token
86
+ });
60
87
  var requestId = (id) => ({ requestId: id });
61
88
  var getRequestId = (attrs) => attrs?.requestId ?? null;
62
89
  var isProtected = (attrs) => attrs?.protected === true;
@@ -64,6 +91,8 @@ var needUserAgent = (attrs) => attrs?.userAgent === true;
64
91
  var isIdempotent = (attrs) => attrs?.idempotencyKey === true;
65
92
  var getRetryAttempt = (attrs) => attrs?.retryAttempt ?? 0;
66
93
  var wasRenewAttempted = (attrs) => attrs?.renewAttempted === true;
94
+ var isRetryable = (attrs) => attrs?.retryable !== false;
95
+ var getSentWith = (attrs) => attrs?.sentWith === void 0 ? SentWith.noUserToken : SentWith.userToken(attrs.sentWith);
67
96
  var getClientCredentials = (attrs) => attrs?.clientCredentials;
68
97
  var Attributes = {
69
98
  empty,
@@ -79,6 +108,10 @@ var Attributes = {
79
108
  getRetryAttempt,
80
109
  renewAttempted,
81
110
  wasRenewAttempted,
111
+ notRetryable,
112
+ isRetryable,
113
+ sentWith,
114
+ getSentWith,
82
115
  clientCredentials,
83
116
  getClientCredentials,
84
117
  requestId,
@@ -448,11 +481,18 @@ var HttpClient = class {
448
481
  );
449
482
  let response = await this.executeRequest(preparedRequest);
450
483
  for (const middleware of this.middleware.afterRequest ?? []) {
484
+ let retried = false;
451
485
  response = await middleware({
452
486
  request: preparedRequest,
453
487
  response,
454
- retry: (nextRequest = preparedRequest) => this.runRequestWithAfterMiddleware(nextRequest)
488
+ retry: (nextRequest = preparedRequest) => {
489
+ retried = true;
490
+ return this.runRequestWithAfterMiddleware(nextRequest);
491
+ }
455
492
  });
493
+ if (retried) {
494
+ return response;
495
+ }
456
496
  }
457
497
  return response;
458
498
  }
@@ -585,23 +625,33 @@ function attachSessionMiddleware(fetchAccessToken) {
585
625
  if (!Attributes.isProtected(request.attributes)) {
586
626
  return request;
587
627
  }
588
- let accessToken = await fetchAccessToken(false);
589
- if (!accessToken) {
590
- const clientCreds = Attributes.getClientCredentials(request.attributes);
591
- if (clientCreds) {
592
- accessToken = clientCreds;
593
- }
628
+ const userToken = await fetchAccessToken(TokenTrigger.beforeRequest);
629
+ if (userToken !== null) {
630
+ return withBearer(
631
+ {
632
+ ...request,
633
+ attributes: Attributes.concat(
634
+ request.attributes ?? Attributes.empty,
635
+ Attributes.sentWith(userToken)
636
+ )
637
+ },
638
+ userToken.value
639
+ );
594
640
  }
595
- if (accessToken === null) {
596
- return request;
641
+ const clientCreds = Attributes.getClientCredentials(request.attributes);
642
+ if (clientCreds) {
643
+ return withBearer(request, clientCreds.value);
644
+ }
645
+ return request;
646
+ };
647
+ }
648
+ function withBearer(request, token) {
649
+ return {
650
+ ...request,
651
+ headers: {
652
+ ...request.headers ?? {},
653
+ Authorization: `Bearer ${token}`
597
654
  }
598
- return {
599
- ...request,
600
- headers: {
601
- ...request.headers ?? {},
602
- Authorization: `Bearer ${accessToken.value}`
603
- }
604
- };
605
655
  };
606
656
  }
607
657
 
@@ -660,7 +710,7 @@ function getRetryDelayMs(attempt) {
660
710
  function createRequestRetryMiddleware(options = {}) {
661
711
  const delayFn = options.delayFn ?? defaultDelay;
662
712
  return async ({ request, response, retry }) => {
663
- if (!isRetryableStatus(response.status)) {
713
+ if (!isRetryableStatus(response.status) || !Attributes.isRetryable(request.attributes)) {
664
714
  return response;
665
715
  }
666
716
  const currentAttempt = Attributes.getRetryAttempt(request.attributes);
@@ -693,7 +743,9 @@ function renewSessionMiddleware(fetchAccessToken) {
693
743
  if (Attributes.wasRenewAttempted(request.attributes)) {
694
744
  return response;
695
745
  }
696
- const newToken = await fetchAccessToken(true);
746
+ const newToken = await fetchAccessToken(
747
+ TokenTrigger.after401(Attributes.getSentWith(request.attributes))
748
+ );
697
749
  if (newToken) {
698
750
  const nextRequest = Request.concatAttributes(
699
751
  request,
@@ -750,6 +802,158 @@ var ApiClient = class {
750
802
  }
751
803
  };
752
804
 
805
+ // src/universal/types/time.ts
806
+ var MAX_EPOCH_MILLIS = 864e13;
807
+ var EpochMillis = {
808
+ /**
809
+ * @throws {@link ValidationError} unless `value` is an integer a `Date` can
810
+ * represent: at most {@link MAX_EPOCH_MILLIS} either side of the epoch.
811
+ */
812
+ of: (value) => {
813
+ if (!Number.isInteger(value) || Math.abs(value) > MAX_EPOCH_MILLIS) {
814
+ throw new ValidationError(
815
+ `EpochMillis must be an integer within \xB1${MAX_EPOCH_MILLIS}, got ${value}`
816
+ );
817
+ }
818
+ return value;
819
+ },
820
+ /** The wall clock. Code that needs the time takes a `TimeEff` instead. */
821
+ now: () => EpochMillis.of(Date.now()),
822
+ /** @throws {@link ValidationError} for an invalid `Date`. */
823
+ fromDate: (date) => EpochMillis.of(date.getTime()),
824
+ /** How long after `from` the instant `to` is; negative if it is before. */
825
+ elapsed: (from, to) => Millis.of(to - from),
826
+ plus: (at, duration) => EpochMillis.of(at + duration)
827
+ };
828
+ var Millis = {
829
+ of: (value) => value
830
+ };
831
+ var Seconds = {
832
+ /**
833
+ * @throws {@link ValidationError} unless `value` is a non-negative integer.
834
+ */
835
+ of: (value) => {
836
+ if (!Number.isInteger(value) || value < 0) {
837
+ throw new ValidationError(
838
+ `Seconds must be a non-negative integer, got ${value}`
839
+ );
840
+ }
841
+ return value;
842
+ },
843
+ toMillis: (value) => Millis.of(value * 1e3)
844
+ };
845
+ function isExpired({
846
+ expiresAt,
847
+ now,
848
+ buffer = Millis.of(0)
849
+ }) {
850
+ return now >= expiresAt - buffer;
851
+ }
852
+
853
+ // src/universal/core/shared/effects.ts
854
+ var systemTime = { time: { now: EpochMillis.now } };
855
+
856
+ // src/server/core/session-refresh.ts
857
+ var VERDICT_MIN_GAP = Millis.of(5e3);
858
+ var REJECTION_MEMORY = Millis.of(6e4);
859
+ var RefreshKey = {
860
+ of: ({
861
+ clientId,
862
+ session
863
+ }) => {
864
+ const identity = session.refreshToken === void 0 ? ["access", session.accessToken.value] : ["refresh", session.refreshToken];
865
+ return JSON.stringify([clientId, ...identity]);
866
+ }
867
+ };
868
+ var RefreshCoordinator = class {
869
+ constructor(env = systemTime) {
870
+ this.env = env;
871
+ this.inFlight = /* @__PURE__ */ new Map();
872
+ this.rejections = /* @__PURE__ */ new Map();
873
+ }
874
+ /**
875
+ * Runs `exchange` for `key` unless one is already in flight, in which case
876
+ * the caller joins it. `joined` tells the caller whether it led: only the
877
+ * leader reports the outcome, so a burst of N produces one verdict, one log
878
+ * line and one `onSessionExpired`.
879
+ */
880
+ refresh(key, exchange) {
881
+ const existing = this.inFlight.get(key);
882
+ if (existing !== void 0) {
883
+ return { outcome: existing, joined: true };
884
+ }
885
+ const outcome = this.run(key, exchange).finally(() => {
886
+ this.inFlight.delete(key);
887
+ });
888
+ this.inFlight.set(key, outcome);
889
+ return { outcome, joined: false };
890
+ }
891
+ async run(key, exchange) {
892
+ this.forgetStale(this.env.time.now());
893
+ if (this.rejections.get(key)?.type === "expired") {
894
+ return { type: "already-expired" };
895
+ }
896
+ const result = await exchange();
897
+ switch (result.type) {
898
+ case "renewed":
899
+ case "unknown":
900
+ return result;
901
+ case "rejected":
902
+ case "unrenewable": {
903
+ const judged = judge({
904
+ result: result.type,
905
+ prior: this.rejections.get(key),
906
+ now: this.env.time.now()
907
+ });
908
+ this.rejections.set(key, judged.memory);
909
+ return { type: judged.outcome };
910
+ }
911
+ }
912
+ }
913
+ forgetStale(now) {
914
+ for (const [key, rejection] of this.rejections) {
915
+ if (EpochMillis.elapsed(rejectedAt(rejection), now) > REJECTION_MEMORY) {
916
+ this.rejections.delete(key);
917
+ }
918
+ }
919
+ }
920
+ };
921
+ function judge({
922
+ result,
923
+ prior,
924
+ now
925
+ }) {
926
+ const verdict = {
927
+ outcome: "expired",
928
+ memory: { type: "expired", at: now }
929
+ };
930
+ switch (result) {
931
+ case "unrenewable":
932
+ return verdict;
933
+ case "rejected":
934
+ switch (prior?.type) {
935
+ case void 0:
936
+ return {
937
+ outcome: "suspected",
938
+ memory: { type: "suspected", since: now }
939
+ };
940
+ case "suspected":
941
+ return EpochMillis.elapsed(prior.since, now) >= VERDICT_MIN_GAP ? verdict : { outcome: "suspected", memory: prior };
942
+ case "expired":
943
+ return { outcome: "expired", memory: prior };
944
+ }
945
+ }
946
+ }
947
+ function rejectedAt(rejection) {
948
+ switch (rejection.type) {
949
+ case "suspected":
950
+ return rejection.since;
951
+ case "expired":
952
+ return rejection.at;
953
+ }
954
+ }
955
+ var processRefreshCoordinator = new RefreshCoordinator();
956
+
753
957
  // src/server/shared/errors.ts
754
958
  var WritableSessionStoreRequiredError = class extends Error {
755
959
  constructor(message = "This operation requires a writable SessionStore. Provide a `setSession` implementation in your SessionStore to persist or clear OAuth sessions.") {
@@ -758,6 +962,16 @@ var WritableSessionStoreRequiredError = class extends Error {
758
962
  }
759
963
  };
760
964
 
965
+ // src/universal/core/auth/session-expired.ts
966
+ function notifySessionExpired(onSessionExpired, log) {
967
+ if (onSessionExpired === void 0) {
968
+ return;
969
+ }
970
+ Promise.resolve().then(onSessionExpired).catch(
971
+ (error) => log.failure(() => ({ msg: "onSessionExpired threw" }), error)
972
+ );
973
+ }
974
+
761
975
  // src/universal/types/oauth-session.ts
762
976
  var ClientCredentialsOAuth = (value) => value;
763
977
  var OAuthRefreshToken = (value) => value;
@@ -774,10 +988,7 @@ var OAuthSession = {
774
988
  }
775
989
  };
776
990
 
777
- // src/server/core/server-auth-namespace.ts
778
- function emptySessionStore() {
779
- return { getSession: async () => null };
780
- }
991
+ // src/server/core/server-auth-namespace-impl.ts
781
992
  var ServerAuthNamespaceImpl = class {
782
993
  constructor(api, config) {
783
994
  this.api = api;
@@ -797,7 +1008,8 @@ var ServerAuthNamespaceImpl = class {
797
1008
  client_id: this.config.clientId,
798
1009
  client_secret: this.config.clientSecret,
799
1010
  code_verifier: params.codeVerifier
800
- }
1011
+ },
1012
+ attributes: Attributes.notRetryable()
801
1013
  });
802
1014
  const session = OAuthSession.fromDto(sessionDto);
803
1015
  await setSession(session);
@@ -805,39 +1017,118 @@ var ServerAuthNamespaceImpl = class {
805
1017
  });
806
1018
  }
807
1019
  async getAccessToken() {
1020
+ return this.tokenFor(TokenTrigger.beforeRequest);
1021
+ }
1022
+ /**
1023
+ * {@link getAccessToken} with the reason the SDK is asking, which is what
1024
+ * the authenticated HTTP pipeline calls: after a 401 the token's own expiry
1025
+ * no longer matters, because the API has answered for it.
1026
+ */
1027
+ async tokenFor(trigger) {
808
1028
  return this.log.wrap(
809
1029
  "getAccessToken",
810
1030
  void 0,
811
- () => this.readAccessToken()
1031
+ () => this.readAccessToken(trigger)
812
1032
  );
813
1033
  }
814
- async readAccessToken() {
1034
+ async readAccessToken(trigger) {
815
1035
  const sessionStore = this.config.sessionStore;
816
- const session = await sessionStore.getSession();
817
- if (session == null) return null;
818
- const now = Date.now();
819
- const expiresAt = session.accessToken.expiresAt.getTime();
820
- const bufferMs = this.config.accessTokenExpiryBufferSeconds * 1e3;
821
- if (expiresAt > now + bufferMs) {
822
- return session.accessToken;
823
- }
824
- const setSession = sessionStore.setSession?.bind(sessionStore);
825
- if (!setSession) {
826
- this.log.child({ op: "getAccessToken" }).warn(() => ({
827
- msg: "serving an expired token: the session store is read-only, so it cannot be refreshed"
828
- }));
829
- return session.accessToken;
1036
+ const classification = classifyAccessToken({
1037
+ session: await sessionStore.getSession(),
1038
+ store: sessionStoreAccess(sessionStore),
1039
+ trigger,
1040
+ now: this.config.time.now(),
1041
+ buffer: Seconds.toMillis(this.config.accessTokenExpiryBufferSeconds)
1042
+ });
1043
+ switch (classification.type) {
1044
+ case "no-session":
1045
+ case "refuse":
1046
+ return null;
1047
+ case "serve":
1048
+ return classification.token;
1049
+ case "serve-stale":
1050
+ this.log.child({ op: "getAccessToken" }).warn(() => ({
1051
+ msg: "serving an expired token: the session store is read-only, so it cannot be refreshed"
1052
+ }));
1053
+ return classification.token;
1054
+ case "refresh":
1055
+ return this.refresh(classification);
830
1056
  }
831
- return this.refreshSession(session.refreshToken, setSession);
832
1057
  }
833
- async refreshSession(refreshToken, setSession) {
1058
+ /**
1059
+ * Refreshes through the process-wide coordinator, then acts on the outcome
1060
+ * for this caller's own store. The store is written before anything is
1061
+ * logged or the host is notified, so `onSessionExpired` always finds it
1062
+ * cleared.
1063
+ */
1064
+ async refresh({
1065
+ session,
1066
+ setSession,
1067
+ fallback
1068
+ }) {
834
1069
  const log = this.log.child({ op: "refresh" });
835
- if (!refreshToken) {
836
- log.warn(() => ({
837
- msg: "clearing the session: it carries no refresh token"
838
- }));
839
- await setSession(null);
840
- return null;
1070
+ const key = RefreshKey.of({ clientId: this.config.clientId, session });
1071
+ const { outcome, joined } = this.config.refreshes.refresh(
1072
+ key,
1073
+ () => this.exchange(session.refreshToken)
1074
+ );
1075
+ if (joined) {
1076
+ log.debug(() => ({ msg: "joined a refresh already in flight" }));
1077
+ }
1078
+ const result = await outcome;
1079
+ switch (result.type) {
1080
+ case "renewed":
1081
+ await setSession(result.session);
1082
+ if (!joined) {
1083
+ log.info(() => ({ msg: "session renewed" }));
1084
+ }
1085
+ return result.session.accessToken;
1086
+ case "suspected":
1087
+ if (!joined) {
1088
+ log.warn(() => ({
1089
+ msg: "refresh rejected; not clearing until it is confirmed"
1090
+ }));
1091
+ }
1092
+ return fallback;
1093
+ case "expired":
1094
+ await setSession(null);
1095
+ if (!joined) {
1096
+ log.info(() => ({
1097
+ msg: "session expired",
1098
+ fields: {
1099
+ reason: session.refreshToken === void 0 ? "no-refresh-token" : "refresh-rejected"
1100
+ }
1101
+ }));
1102
+ notifySessionExpired(this.config.onSessionExpired, log);
1103
+ }
1104
+ return null;
1105
+ case "already-expired":
1106
+ await setSession(null);
1107
+ return null;
1108
+ case "unknown":
1109
+ if (fallback === null) {
1110
+ throw result.error;
1111
+ }
1112
+ if (!joined) {
1113
+ log.warning(
1114
+ () => ({ msg: "refresh failed; serving the current token" }),
1115
+ result.error
1116
+ );
1117
+ }
1118
+ return fallback;
1119
+ }
1120
+ }
1121
+ /**
1122
+ * One `POST /oauth/token`, total: every failure comes back as a value, so
1123
+ * the coordinator can judge it once for everyone who joined.
1124
+ *
1125
+ * Never retried. The refresh token is single-use, so a retry of an attempt
1126
+ * that reached the server would present a token already rotated away, and
1127
+ * its `invalid_grant` would be judged as a rejection of the session.
1128
+ */
1129
+ async exchange(refreshToken) {
1130
+ if (refreshToken === void 0) {
1131
+ return { type: "unrenewable" };
841
1132
  }
842
1133
  try {
843
1134
  const sessionDto = await this.api.send({
@@ -848,19 +1139,12 @@ var ServerAuthNamespaceImpl = class {
848
1139
  refresh_token: refreshToken,
849
1140
  client_id: this.config.clientId,
850
1141
  client_secret: this.config.clientSecret
851
- }
1142
+ },
1143
+ attributes: Attributes.notRetryable()
852
1144
  });
853
- const newSession = OAuthSession.fromDto(sessionDto);
854
- await setSession(newSession);
855
- log.info(() => ({ msg: "session renewed" }));
856
- return newSession.accessToken;
1145
+ return { type: "renewed", session: OAuthSession.fromDto(sessionDto) };
857
1146
  } catch (error) {
858
- log.failure(
859
- () => ({ msg: "refresh failed; clearing the session" }),
860
- error
861
- );
862
- await setSession(null);
863
- return null;
1147
+ return isInvalidGrant(error) ? { type: "rejected" } : { type: "unknown", error };
864
1148
  }
865
1149
  }
866
1150
  async clientCredentials() {
@@ -896,28 +1180,102 @@ var ServerAuthNamespaceImpl = class {
896
1180
  }
897
1181
  });
898
1182
  } catch (err) {
899
- if (!(err instanceof HttpError) || this.config.strict) {
1183
+ if (this.config.strict) {
900
1184
  throw err;
901
1185
  }
902
- this.log.child({ op: "logout" }).warn(() => ({
903
- msg: "the token was not revoked upstream; the local session is cleared regardless"
904
- }));
1186
+ this.log.child({ op: "logout" }).warning(
1187
+ () => ({ msg: "revoke failed; local session cleared regardless" }),
1188
+ err
1189
+ );
905
1190
  }
906
1191
  await setSession(null);
907
1192
  }
908
1193
  /**
909
- * Returns the store's `setSession`, or throws — the guard every
1194
+ * Returns the store's `setSession`, or throws - the guard every
910
1195
  * session-writing operation shares.
911
1196
  */
912
1197
  writableSessionStore() {
913
- const sessionStore = this.config.sessionStore;
914
- const setSession = sessionStore.setSession?.bind(sessionStore);
915
- if (!setSession) {
916
- throw new WritableSessionStoreRequiredError();
1198
+ const store = sessionStoreAccess(this.config.sessionStore);
1199
+ switch (store.type) {
1200
+ case "writable":
1201
+ return store.setSession;
1202
+ case "read-only":
1203
+ throw new WritableSessionStoreRequiredError();
917
1204
  }
918
- return setSession;
919
1205
  }
920
1206
  };
1207
+ function classifyAccessToken({
1208
+ session,
1209
+ store,
1210
+ trigger,
1211
+ now,
1212
+ buffer
1213
+ }) {
1214
+ if (session === null) {
1215
+ return { type: "no-session" };
1216
+ }
1217
+ const token = session.accessToken;
1218
+ const stale = isExpired({
1219
+ expiresAt: EpochMillis.fromDate(token.expiresAt),
1220
+ now,
1221
+ buffer
1222
+ });
1223
+ switch (store.type) {
1224
+ case "read-only":
1225
+ return classifyReadOnly(token, trigger, stale);
1226
+ case "writable": {
1227
+ if (!needsRefresh(token, trigger, stale)) {
1228
+ return { type: "serve", token };
1229
+ }
1230
+ const fallback = fallbackToken(token, trigger, now);
1231
+ return session.refreshToken === void 0 && fallback !== null ? { type: "serve", token: fallback } : {
1232
+ type: "refresh",
1233
+ session,
1234
+ setSession: store.setSession,
1235
+ fallback
1236
+ };
1237
+ }
1238
+ }
1239
+ }
1240
+ function classifyReadOnly(token, trigger, stale) {
1241
+ switch (trigger.type) {
1242
+ case "after-401":
1243
+ return isRejectedToken(token, trigger.rejected) ? { type: "refuse" } : { type: "serve", token };
1244
+ case "before-request":
1245
+ return stale ? { type: "serve-stale", token } : { type: "serve", token };
1246
+ }
1247
+ }
1248
+ function sessionStoreAccess(sessionStore) {
1249
+ const setSession = sessionStore.setSession?.bind(sessionStore);
1250
+ return setSession === void 0 ? { type: "read-only" } : { type: "writable", setSession };
1251
+ }
1252
+ function needsRefresh(token, trigger, stale) {
1253
+ switch (trigger.type) {
1254
+ case "before-request":
1255
+ return stale;
1256
+ case "after-401":
1257
+ return trigger.rejected.type === "no-user-token" || isRejectedToken(token, trigger.rejected) || stale;
1258
+ }
1259
+ }
1260
+ function fallbackToken(token, trigger, now) {
1261
+ const unexpired = !isExpired({
1262
+ expiresAt: EpochMillis.fromDate(token.expiresAt),
1263
+ now
1264
+ });
1265
+ switch (trigger.type) {
1266
+ case "after-401":
1267
+ return unexpired && !isRejectedToken(token, trigger.rejected) ? token : null;
1268
+ case "before-request":
1269
+ return unexpired ? token : null;
1270
+ }
1271
+ }
1272
+ function isInvalidGrant(error) {
1273
+ if (!(error instanceof HttpError) || error.response.status !== 400) {
1274
+ return false;
1275
+ }
1276
+ const body = error.response.body;
1277
+ return typeof body === "object" && body !== null && "error" in body && body.error === "invalid_grant";
1278
+ }
921
1279
 
922
1280
  // src/universal/api/pagination.ts
923
1281
  async function fetchAllPages(fetchPage, baseParams) {
@@ -935,40 +1293,6 @@ async function fetchAllPages(fetchPage, baseParams) {
935
1293
  return items;
936
1294
  }
937
1295
 
938
- // src/universal/core/shared/utils/wire.ts
939
- function unknownTypeError(dto, model) {
940
- const type = dto.type;
941
- throw new ValidationError(
942
- `${model}.type: unknown type ${JSON.stringify(type)}`
943
- );
944
- }
945
- function requiredString(value, field) {
946
- const text = notBlankStringOrNull(value);
947
- if (text === null) {
948
- throw new ValidationError(
949
- `${field}: expected a non-blank string, got ${typeof value === "string" ? "a blank string" : String(value)}`
950
- );
951
- }
952
- return text;
953
- }
954
- function requiredDate(value, field) {
955
- const date = typeof value === "string" ? new Date(value) : null;
956
- if (date === null || Number.isNaN(date.getTime())) {
957
- throw new ValidationError(
958
- `${field}: expected a date, got ${JSON.stringify(value)}`
959
- );
960
- }
961
- return date;
962
- }
963
- function requiredArray(value, field) {
964
- if (!Array.isArray(value)) {
965
- throw new ValidationError(
966
- `${field}: expected an array, got ${typeof value}`
967
- );
968
- }
969
- return value;
970
- }
971
-
972
1296
  // src/universal/types/blockchain/core.ts
973
1297
  var ETHEREUM_CHAINS = {
974
1298
  ethereum_mainnet: true,
@@ -1057,13 +1381,51 @@ function combineAmounts(a, b, op) {
1057
1381
  }
1058
1382
  var AssetSymbol = (value) => value;
1059
1383
 
1384
+ // src/universal/core/shared/utils/wire.ts
1385
+ function unknownTypeError(dto, model) {
1386
+ const type = dto.type;
1387
+ throw new ValidationError(
1388
+ `${model}.type: unknown type ${JSON.stringify(type)}`
1389
+ );
1390
+ }
1391
+ function requiredString(value, field) {
1392
+ const text = notBlankStringOrNull(value);
1393
+ if (text === null) {
1394
+ throw new ValidationError(
1395
+ `${field}: expected a non-blank string, got ${typeof value === "string" ? "a blank string" : String(value)}`
1396
+ );
1397
+ }
1398
+ return text;
1399
+ }
1400
+ function requiredDate(value, field) {
1401
+ const date = typeof value === "string" ? new Date(value) : null;
1402
+ if (date === null || Number.isNaN(date.getTime())) {
1403
+ throw new ValidationError(
1404
+ `${field}: expected a date, got ${JSON.stringify(value)}`
1405
+ );
1406
+ }
1407
+ return date;
1408
+ }
1409
+ function decimalOrNull(value) {
1410
+ return value === null ? null : DecimalString(value);
1411
+ }
1412
+ function requiredArray(value, field) {
1413
+ if (!Array.isArray(value)) {
1414
+ throw new ValidationError(
1415
+ `${field}: expected an array, got ${typeof value}`
1416
+ );
1417
+ }
1418
+ return value;
1419
+ }
1420
+
1060
1421
  // src/universal/types/offer.ts
1061
1422
  var OfferId = (value) => value;
1062
1423
  var OfferSlug = (value) => value;
1063
1424
  var OFFER_TYPES = {
1064
1425
  "coinlist::token_sale": true,
1065
1426
  "superstate::swap": true,
1066
- "ondo::swap": true
1427
+ "ondo::swap": true,
1428
+ "centrifuge::vault": true
1067
1429
  };
1068
1430
  var isOfferType = (value) => Object.keys(OFFER_TYPES).includes(value);
1069
1431
  var OfferEditorial = {
@@ -1105,6 +1467,7 @@ var Offer = {
1105
1467
  // on the wire: copying them would hand every consumer a `startsAt` that
1106
1468
  // is 1970 for one variant and a date for the others.
1107
1469
  case "ondo::swap":
1470
+ case "centrifuge::vault":
1108
1471
  return {
1109
1472
  ...base2,
1110
1473
  type: dto.type,
@@ -1169,6 +1532,19 @@ var OfferSwapContract = {
1169
1532
  address: EvmContractAddress(dto.address)
1170
1533
  })
1171
1534
  };
1535
+ var OfferVaultContract = {
1536
+ fromDto: (dto) => ({
1537
+ chain: Chain(dto.chain),
1538
+ address: EvmContractAddress(dto.address)
1539
+ }),
1540
+ /**
1541
+ * Drops every proxy on a chain this SDK does not name, as
1542
+ * `OfferToken.listFromDto` does: frontline publishes the full offer-token
1543
+ * chain list, which runs ahead of {@link Chain}, and one unmodelled chain
1544
+ * should not throw away the proxies the SDK can use.
1545
+ */
1546
+ listFromDto: (dtos) => dtos.filter((dto) => isChain(dto.chain)).map(OfferVaultContract.fromDto)
1547
+ };
1172
1548
  var OfferDetail = {
1173
1549
  fromDto: (dto) => {
1174
1550
  const fundingAssets = requiredArray(
@@ -1231,6 +1607,18 @@ var OfferDetail = {
1231
1607
  "OfferDetail.swap_contracts"
1232
1608
  ).map(OfferSwapContract.fromDto)
1233
1609
  };
1610
+ // No editorial, as for Ondo. `[]` is fine and a missing key is not, for
1611
+ // the same reason as `swap_contracts`: this is the spender a user
1612
+ // approves.
1613
+ case "centrifuge::vault":
1614
+ return {
1615
+ ...base2,
1616
+ type: dto.type,
1617
+ logoUrl: notBlankStringOrNull(dto.logo_url),
1618
+ vaultContracts: OfferVaultContract.listFromDto(
1619
+ requiredArray(dto.vault_contracts, "OfferDetail.vault_contracts")
1620
+ )
1621
+ };
1234
1622
  default:
1235
1623
  return unknownTypeError(dto, "OfferDetail");
1236
1624
  }
@@ -1821,6 +2209,8 @@ function formatRawAmount(amount) {
1821
2209
  return (0, import_viem4.formatUnits)(amount.raw, amount.decimals);
1822
2210
  }
1823
2211
  var NA_AMOUNT_ASSET_UI = FormattedAmountAssetUi("-");
2212
+ var SHARE_MAX_FRACTION_DIGITS = AssetDecimals(6);
2213
+ var SHARE_MIN_FRACTION_DIGITS = AssetDecimals(2);
1824
2214
  var USD_FRACTION_DIGITS = AssetDecimals(2);
1825
2215
 
1826
2216
  // src/universal/core/shared/blockchain/math.ts
@@ -1862,7 +2252,6 @@ var OndoTradingStatus = {
1862
2252
  };
1863
2253
  }
1864
2254
  };
1865
- var decimalOrNull = (value) => value === null ? null : DecimalString(value);
1866
2255
  var OndoQuote = {
1867
2256
  fromDto: (dto) => {
1868
2257
  const assetDecimals = AssetDecimals(dto.asset_decimals);
@@ -3666,28 +4055,26 @@ var WalletsNamespaceImpl = class {
3666
4055
  };
3667
4056
 
3668
4057
  // src/server/core/coinlist-server.ts
3669
- var ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;
4058
+ var ACCESS_TOKEN_EXPIRY_BUFFER = Seconds.of(60);
3670
4059
  var CoinListServerImpl = class {
3671
4060
  constructor(_config) {
3672
- this._config = _config;
3673
4061
  const logger = _config.logger ?? null;
3674
4062
  this.api = new ApiClient(
3675
4063
  {
3676
4064
  baseUrl: _config.baseUrl ?? PUBLIC_API_BASE_URL,
3677
4065
  xApiVersion: API_VERSION
3678
4066
  },
3679
- // When refresh=true the renewal middleware has received a 401 and wants a
3680
- // fresh token. A read-only store cannot persist a new session, so return
3681
- // null immediately — this tells the middleware to skip the retry rather
3682
- // than re-sending with the same expired token and wasting a round-trip.
3683
- (refresh) => refresh && !this._config.sessionStore.setSession ? Promise.resolve(null) : this.auth.getAccessToken(),
4067
+ (trigger) => auth.tokenFor(trigger),
3684
4068
  logger
3685
4069
  );
3686
- this.auth = new ServerAuthNamespaceImpl(this.api, {
4070
+ const auth = new ServerAuthNamespaceImpl(this.api, {
3687
4071
  ..._config,
3688
- accessTokenExpiryBufferSeconds: _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS,
3689
- strict: _config.strict ?? false
4072
+ accessTokenExpiryBufferSeconds: _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER,
4073
+ strict: _config.strict ?? false,
4074
+ refreshes: processRefreshCoordinator,
4075
+ ...systemTime
3690
4076
  });
4077
+ this.auth = auth;
3691
4078
  const ctx = {
3692
4079
  api: this.api,
3693
4080
  rpc: publicClients(this.api, _config.rpc, _config.logger ?? null),
@@ -3865,9 +4252,13 @@ function pinoServerLogger(options) {
3865
4252
  options.level
3866
4253
  );
3867
4254
  }
4255
+
4256
+ // src/server/core/server-auth-namespace.ts
4257
+ function emptySessionStore() {
4258
+ return { getSession: async () => null };
4259
+ }
3868
4260
  // Annotate the CommonJS export names for ESM import in node:
3869
4261
  0 && (module.exports = {
3870
- ServerAuthNamespaceImpl,
3871
4262
  ServerOffersNamespaceImpl,
3872
4263
  ServerRequirementsNamespaceImpl,
3873
4264
  WritableSessionStoreRequiredError,