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

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 (32) hide show
  1. package/dist/{chunk-MOE5S6CE.js → chunk-BNMF2HMW.js} +114 -4
  2. package/dist/chunk-BNMF2HMW.js.map +1 -0
  3. package/dist/{chunk-VH34YC3Q.js → chunk-KRA6EM2L.js} +2 -2
  4. package/dist/{chunk-DCW7NQXH.js → chunk-STAHYDLY.js} +52 -19
  5. package/dist/chunk-STAHYDLY.js.map +1 -0
  6. package/dist/client/index.cjs +322 -96
  7. package/dist/client/index.cjs.map +1 -1
  8. package/dist/client/index.d.cts +26 -10
  9. package/dist/client/index.d.ts +26 -10
  10. package/dist/client/index.js +225 -102
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/server/index.cjs +437 -76
  13. package/dist/server/index.cjs.map +1 -1
  14. package/dist/server/index.d.cts +47 -38
  15. package/dist/server/index.d.ts +47 -38
  16. package/dist/server/index.js +316 -61
  17. package/dist/server/index.js.map +1 -1
  18. package/dist/time-B5YOQxuh.d.ts +50 -0
  19. package/dist/time-BY3apBpc.d.cts +50 -0
  20. package/dist/{token-registry-snapshot-BNBNuDu1.d.ts → token-registry-snapshot-C1NxdBmF.d.ts} +1 -1
  21. package/dist/{token-registry-snapshot-CqI6dXM_.d.cts → token-registry-snapshot-DyjrA32p.d.cts} +1 -1
  22. package/dist/{tokens-namespace-CIWBtgt2.d.cts → tokens-namespace-BJfFeowE.d.cts} +53 -1
  23. package/dist/{tokens-namespace-CIWBtgt2.d.ts → tokens-namespace-BJfFeowE.d.ts} +53 -1
  24. package/dist/universal/index.cjs +100 -3
  25. package/dist/universal/index.cjs.map +1 -1
  26. package/dist/universal/index.d.cts +23 -5
  27. package/dist/universal/index.d.ts +23 -5
  28. package/dist/universal/index.js +12 -2
  29. package/package.json +1 -1
  30. package/dist/chunk-DCW7NQXH.js.map +0 -1
  31. package/dist/chunk-MOE5S6CE.js.map +0 -1
  32. /package/dist/{chunk-VH34YC3Q.js.map → chunk-KRA6EM2L.js.map} +0 -0
@@ -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() {
@@ -906,18 +1190,91 @@ var ServerAuthNamespaceImpl = class {
906
1190
  await setSession(null);
907
1191
  }
908
1192
  /**
909
- * Returns the store's `setSession`, or throws — the guard every
1193
+ * Returns the store's `setSession`, or throws - the guard every
910
1194
  * session-writing operation shares.
911
1195
  */
912
1196
  writableSessionStore() {
913
- const sessionStore = this.config.sessionStore;
914
- const setSession = sessionStore.setSession?.bind(sessionStore);
915
- if (!setSession) {
916
- throw new WritableSessionStoreRequiredError();
1197
+ const store = sessionStoreAccess(this.config.sessionStore);
1198
+ switch (store.type) {
1199
+ case "writable":
1200
+ return store.setSession;
1201
+ case "read-only":
1202
+ throw new WritableSessionStoreRequiredError();
917
1203
  }
918
- return setSession;
919
1204
  }
920
1205
  };
1206
+ function classifyAccessToken({
1207
+ session,
1208
+ store,
1209
+ trigger,
1210
+ now,
1211
+ buffer
1212
+ }) {
1213
+ if (session === null) {
1214
+ return { type: "no-session" };
1215
+ }
1216
+ const token = session.accessToken;
1217
+ const stale = isExpired({
1218
+ expiresAt: EpochMillis.fromDate(token.expiresAt),
1219
+ now,
1220
+ buffer
1221
+ });
1222
+ switch (store.type) {
1223
+ case "read-only":
1224
+ return classifyReadOnly(token, trigger, stale);
1225
+ case "writable": {
1226
+ if (!needsRefresh(token, trigger, stale)) {
1227
+ return { type: "serve", token };
1228
+ }
1229
+ const fallback = fallbackToken(token, trigger, now);
1230
+ return session.refreshToken === void 0 && fallback !== null ? { type: "serve", token: fallback } : {
1231
+ type: "refresh",
1232
+ session,
1233
+ setSession: store.setSession,
1234
+ fallback
1235
+ };
1236
+ }
1237
+ }
1238
+ }
1239
+ function classifyReadOnly(token, trigger, stale) {
1240
+ switch (trigger.type) {
1241
+ case "after-401":
1242
+ return isRejectedToken(token, trigger.rejected) ? { type: "refuse" } : { type: "serve", token };
1243
+ case "before-request":
1244
+ return stale ? { type: "serve-stale", token } : { type: "serve", token };
1245
+ }
1246
+ }
1247
+ function sessionStoreAccess(sessionStore) {
1248
+ const setSession = sessionStore.setSession?.bind(sessionStore);
1249
+ return setSession === void 0 ? { type: "read-only" } : { type: "writable", setSession };
1250
+ }
1251
+ function needsRefresh(token, trigger, stale) {
1252
+ switch (trigger.type) {
1253
+ case "before-request":
1254
+ return stale;
1255
+ case "after-401":
1256
+ return trigger.rejected.type === "no-user-token" || isRejectedToken(token, trigger.rejected) || stale;
1257
+ }
1258
+ }
1259
+ function fallbackToken(token, trigger, now) {
1260
+ const unexpired = !isExpired({
1261
+ expiresAt: EpochMillis.fromDate(token.expiresAt),
1262
+ now
1263
+ });
1264
+ switch (trigger.type) {
1265
+ case "after-401":
1266
+ return unexpired && !isRejectedToken(token, trigger.rejected) ? token : null;
1267
+ case "before-request":
1268
+ return unexpired ? token : null;
1269
+ }
1270
+ }
1271
+ function isInvalidGrant(error) {
1272
+ if (!(error instanceof HttpError) || error.response.status !== 400) {
1273
+ return false;
1274
+ }
1275
+ const body = error.response.body;
1276
+ return typeof body === "object" && body !== null && "error" in body && body.error === "invalid_grant";
1277
+ }
921
1278
 
922
1279
  // src/universal/api/pagination.ts
923
1280
  async function fetchAllPages(fetchPage, baseParams) {
@@ -1821,6 +2178,8 @@ function formatRawAmount(amount) {
1821
2178
  return (0, import_viem4.formatUnits)(amount.raw, amount.decimals);
1822
2179
  }
1823
2180
  var NA_AMOUNT_ASSET_UI = FormattedAmountAssetUi("-");
2181
+ var SHARE_MAX_FRACTION_DIGITS = AssetDecimals(6);
2182
+ var SHARE_MIN_FRACTION_DIGITS = AssetDecimals(2);
1824
2183
  var USD_FRACTION_DIGITS = AssetDecimals(2);
1825
2184
 
1826
2185
  // src/universal/core/shared/blockchain/math.ts
@@ -3666,28 +4025,26 @@ var WalletsNamespaceImpl = class {
3666
4025
  };
3667
4026
 
3668
4027
  // src/server/core/coinlist-server.ts
3669
- var ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;
4028
+ var ACCESS_TOKEN_EXPIRY_BUFFER = Seconds.of(60);
3670
4029
  var CoinListServerImpl = class {
3671
4030
  constructor(_config) {
3672
- this._config = _config;
3673
4031
  const logger = _config.logger ?? null;
3674
4032
  this.api = new ApiClient(
3675
4033
  {
3676
4034
  baseUrl: _config.baseUrl ?? PUBLIC_API_BASE_URL,
3677
4035
  xApiVersion: API_VERSION
3678
4036
  },
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(),
4037
+ (trigger) => auth.tokenFor(trigger),
3684
4038
  logger
3685
4039
  );
3686
- this.auth = new ServerAuthNamespaceImpl(this.api, {
4040
+ const auth = new ServerAuthNamespaceImpl(this.api, {
3687
4041
  ..._config,
3688
- accessTokenExpiryBufferSeconds: _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS,
3689
- strict: _config.strict ?? false
4042
+ accessTokenExpiryBufferSeconds: _config.accessTokenExpiryBufferSeconds ?? ACCESS_TOKEN_EXPIRY_BUFFER,
4043
+ strict: _config.strict ?? false,
4044
+ refreshes: processRefreshCoordinator,
4045
+ ...systemTime
3690
4046
  });
4047
+ this.auth = auth;
3691
4048
  const ctx = {
3692
4049
  api: this.api,
3693
4050
  rpc: publicClients(this.api, _config.rpc, _config.logger ?? null),
@@ -3865,9 +4222,13 @@ function pinoServerLogger(options) {
3865
4222
  options.level
3866
4223
  );
3867
4224
  }
4225
+
4226
+ // src/server/core/server-auth-namespace.ts
4227
+ function emptySessionStore() {
4228
+ return { getSession: async () => null };
4229
+ }
3868
4230
  // Annotate the CommonJS export names for ESM import in node:
3869
4231
  0 && (module.exports = {
3870
- ServerAuthNamespaceImpl,
3871
4232
  ServerOffersNamespaceImpl,
3872
4233
  ServerRequirementsNamespaceImpl,
3873
4234
  WritableSessionStoreRequiredError,