@yeying-community/web3-bs 1.0.10 → 1.0.12

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
@@ -22,12 +22,13 @@ npm install @yeying-community/web3-bs
22
22
 
23
23
  - `getProvider` / `requireProvider`
24
24
  - `watchProvider`
25
- - `requestAccounts` / `getAccounts` / `getPreferredAccount` / `watchAccounts`
25
+ - `requestAccounts` / `focusPendingApproval` / `getAccounts` / `getPreferredAccount` / `watchAccounts`
26
26
  - `getChainId` / `getBalance`
27
27
  - `onAccountsChanged` / `onChainChanged`
28
28
  - `classifyWalletError` / `isUserRejectedWalletAction` / `isWalletReconnectError`
29
29
 
30
30
  `requestAccounts` 默认会复用同一 provider 上尚未完成的连接请求,避免用户重复点击时触发多个钱包授权弹窗。
31
+ 当钱包已经存在待确认的连接、签名或解锁窗口时,可调用 `focusPendingApproval` 将该窗口重新拉到前台,而不是再发起一次新的请求。
31
32
 
32
33
  ### 2) SIWE + JWT
33
34
 
@@ -72,7 +73,17 @@ npm install @yeying-community/web3-bs
72
73
  ### 单后端登录(SIWE + JWT)
73
74
 
74
75
  ```ts
75
- import { loginWithChallenge, authFetch } from '@yeying-community/web3-bs';
76
+ import {
77
+ authFetch,
78
+ focusPendingApproval,
79
+ loginWithChallenge,
80
+ requestAccounts,
81
+ } from '@yeying-community/web3-bs';
82
+
83
+ const pending = await focusPendingApproval().catch(() => ({ focused: false }));
84
+ if (!pending.focused) {
85
+ await requestAccounts();
86
+ }
76
87
 
77
88
  await loginWithChallenge({
78
89
  baseUrl: 'http://localhost:3203/api/v1/public/auth',
@@ -1,4 +1,4 @@
1
- import { Eip1193Provider, ProviderDiscoveryOptions, ProviderInfo, RequestAccountsOptions, AccountSelection, PreferredAccountOptions, WatchAccountsOptions, AccountsChangedHandler, WatchProviderOptions, ProviderChangedHandler, WalletErrorInfo } from './types';
1
+ import { Eip1193Provider, ProviderDiscoveryOptions, ProviderInfo, RequestAccountsOptions, AccountSelection, PreferredAccountOptions, WatchAccountsOptions, AccountsChangedHandler, WatchProviderOptions, ProviderChangedHandler, WalletErrorInfo, FocusPendingApprovalResult } from './types';
2
2
  export declare function isYeYingProvider(provider?: Eip1193Provider | null, info?: ProviderInfo): boolean;
3
3
  export declare function getProvider(options?: ProviderDiscoveryOptions): Promise<Eip1193Provider | null>;
4
4
  export declare function watchProvider(handler: ProviderChangedHandler, options?: WatchProviderOptions): () => void;
@@ -9,6 +9,7 @@ export declare function isUserRejectedWalletAction(error: unknown): boolean;
9
9
  export declare function isWalletReconnectError(error: unknown): boolean;
10
10
  export declare function requireProvider(options?: ProviderDiscoveryOptions): Promise<Eip1193Provider>;
11
11
  export declare function requestAccounts(options?: RequestAccountsOptions): Promise<string[]>;
12
+ export declare function focusPendingApproval(provider?: Eip1193Provider): Promise<FocusPendingApprovalResult>;
12
13
  export declare function getAccounts(provider?: Eip1193Provider): Promise<string[]>;
13
14
  export declare function getChainId(provider?: Eip1193Provider): Promise<string | null>;
14
15
  export declare function getPreferredAccount(options?: PreferredAccountOptions): Promise<AccountSelection>;
@@ -51,6 +51,13 @@ export interface RequestAccountsOptions {
51
51
  provider?: Eip1193Provider;
52
52
  dedupe?: boolean;
53
53
  }
54
+ export type FocusPendingApprovalResult = {
55
+ focused: boolean;
56
+ type: string | null;
57
+ requestId?: string | null;
58
+ origin?: string;
59
+ tabId?: number | null;
60
+ };
54
61
  export type AccountSelection = {
55
62
  account: string | null;
56
63
  accounts: string[];
@@ -77,6 +77,11 @@ export type CreateUcanTokenOptions = {
77
77
  notBeforeMs?: number;
78
78
  proofs?: UcanProof[];
79
79
  };
80
+ export type GetOrCreateInvocationUcanOptions = CreateUcanTokenOptions & {
81
+ ucan?: string;
82
+ skewMs?: number;
83
+ nowMs?: number;
84
+ };
80
85
  export type UcanFetchOptions = {
81
86
  ucan?: string;
82
87
  audience?: string;
@@ -87,8 +92,37 @@ export type UcanFetchOptions = {
87
92
  proofs?: UcanProof[];
88
93
  expiresInMs?: number;
89
94
  notBeforeMs?: number;
95
+ skewMs?: number;
90
96
  fetcher?: typeof fetch;
91
97
  };
98
+ export type UcanTokenTiming = {
99
+ valid: boolean;
100
+ payload: UcanTokenPayload | null;
101
+ exp: number | null;
102
+ nbf: number | null;
103
+ issuedAt: number | null;
104
+ nowMs: number;
105
+ remainingMs: number | null;
106
+ activeInMs: number;
107
+ expired: boolean;
108
+ notBefore: boolean;
109
+ };
110
+ export type UcanTtlPolicy = {
111
+ expiresInMs?: number;
112
+ skewMs?: number;
113
+ };
114
+ export type UcanAuthErrorType = 'expired' | 'not-before' | 'unauthorized' | 'forbidden' | 'invalid-token' | 'unknown';
115
+ export type UcanAuthErrorInfo = {
116
+ type: UcanAuthErrorType;
117
+ message: string;
118
+ retryable: boolean;
119
+ shouldRefresh: boolean;
120
+ status?: number;
121
+ code?: string | number;
122
+ };
123
+ export declare const DEFAULT_UCAN_SESSION_TTL_MS: number;
124
+ export declare const DEFAULT_UCAN_TOKEN_TTL_MS: number;
125
+ export declare const DEFAULT_UCAN_TOKEN_SKEW_MS: number;
92
126
  export declare function getCapabilityResource(cap: UcanCapability | null | undefined): string;
93
127
  export declare function getCapabilityAction(cap: UcanCapability | null | undefined): string;
94
128
  export declare function normalizeUcanCapability(cap: UcanCapability | null | undefined, options?: {
@@ -97,6 +131,15 @@ export declare function normalizeUcanCapability(cap: UcanCapability | null | und
97
131
  export declare function normalizeUcanCapabilities(caps: UcanCapability[] | undefined, options?: {
98
132
  includeLegacyAliases?: boolean;
99
133
  }): UcanCapability[];
134
+ export declare function normalizeUcanExpiry(exp: number | undefined, fallbackMs: number): number;
135
+ export declare function decodeUcanPayload(token: string): UcanTokenPayload | null;
136
+ export declare function getUcanTokenTiming(token: string, options?: {
137
+ nowMs?: number;
138
+ }): UcanTokenTiming;
139
+ export declare function isUcanTokenFresh(tokenOrTiming: string | UcanTokenTiming, options?: UcanTtlPolicy & {
140
+ nowMs?: number;
141
+ }): boolean;
142
+ export declare function classifyUcanAuthError(error: unknown): UcanAuthErrorInfo;
100
143
  export declare function getUcanSession(id?: string, provider?: Eip1193Provider): Promise<UcanSessionKey | null>;
101
144
  export declare function createUcanSession(options?: CreateUcanSessionOptions): Promise<UcanSessionKey>;
102
145
  export declare function clearUcanSession(id?: string): Promise<void>;
@@ -106,4 +149,5 @@ export declare function getOrCreateUcanRoot(options: CreateRootUcanOptions): Pro
106
149
  export declare function createRootUcan(options: CreateRootUcanOptions): Promise<UcanRootProof>;
107
150
  export declare function createDelegationUcan(options: CreateUcanTokenOptions): Promise<string>;
108
151
  export declare function createInvocationUcan(options: CreateUcanTokenOptions): Promise<string>;
152
+ export declare function getOrCreateInvocationUcan(options: GetOrCreateInvocationUcanOptions): Promise<string>;
109
153
  export declare function authUcanFetch(input: RequestInfo | URL, init?: RequestInit, options?: UcanFetchOptions): Promise<Response>;
package/dist/dapp.d.ts CHANGED
@@ -18,6 +18,7 @@ export type InitWebDavStorageOptions = {
18
18
  root?: UcanRootProof;
19
19
  rootExpiresInMs?: number;
20
20
  invocationExpiresInMs?: number;
21
+ invocationSkewMs?: number;
21
22
  notBeforeMs?: number;
22
23
  fetcher?: typeof fetch;
23
24
  credentials?: RequestCredentials;
@@ -294,6 +294,25 @@ async function requestAccounts(options = {}) {
294
294
  requestAccountsInFlight.delete(provider);
295
295
  }
296
296
  }
297
+ async function focusPendingApproval(provider) {
298
+ const p = provider || (await requireProvider());
299
+ const result = await p.request({
300
+ method: 'wallet_focusPendingApproval',
301
+ });
302
+ if (!result || typeof result !== 'object') {
303
+ return { focused: false, type: null };
304
+ }
305
+ const payload = result;
306
+ return {
307
+ focused: Boolean(payload.focused),
308
+ type: typeof payload.type === 'string' ? payload.type : null,
309
+ requestId: typeof payload.requestId === 'string' ? payload.requestId : null,
310
+ origin: typeof payload.origin === 'string' ? payload.origin : '',
311
+ tabId: typeof payload.tabId === 'number' && Number.isFinite(payload.tabId)
312
+ ? payload.tabId
313
+ : null,
314
+ };
315
+ }
297
316
  async function getAccounts(provider) {
298
317
  const p = provider || (await requireProvider());
299
318
  const accounts = (await p.request({ method: 'eth_accounts' }));
@@ -669,8 +688,9 @@ async function authFetch(input, init = {}, options = {}) {
669
688
  }
670
689
 
671
690
  const DEFAULT_SESSION_ID = 'default';
672
- const DEFAULT_SESSION_TTL = 24 * 60 * 60 * 1000;
673
- const DEFAULT_UCAN_TTL = 5 * 60 * 1000;
691
+ const DEFAULT_UCAN_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
692
+ const DEFAULT_UCAN_TOKEN_TTL_MS = 40 * 60 * 1000;
693
+ const DEFAULT_UCAN_TOKEN_SKEW_MS = 60 * 1000;
674
694
  const DB_NAME = 'yeying-web3';
675
695
  const DB_STORE = 'ucan-sessions';
676
696
  const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
@@ -751,6 +771,30 @@ function normalizeUcanCapabilities(caps, options = {}) {
751
771
  function encodeJson(value) {
752
772
  return toBase64Url(textEncoder.encode(JSON.stringify(value)));
753
773
  }
774
+ function decodeBase64Url(input) {
775
+ if (!input)
776
+ return null;
777
+ const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
778
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
779
+ try {
780
+ if (typeof atob === 'function') {
781
+ return atob(padded);
782
+ }
783
+ }
784
+ catch {
785
+ // Try Node-compatible fallback below.
786
+ }
787
+ try {
788
+ const nodeBuffer = globalThis.Buffer;
789
+ if (nodeBuffer) {
790
+ return nodeBuffer.from(padded, 'base64').toString('utf8');
791
+ }
792
+ }
793
+ catch {
794
+ return null;
795
+ }
796
+ return null;
797
+ }
754
798
  function randomNonce(bytes = 16) {
755
799
  const buffer = new Uint8Array(bytes);
756
800
  crypto.getRandomValues(buffer);
@@ -759,8 +803,144 @@ function randomNonce(bytes = 16) {
759
803
  .join('');
760
804
  }
761
805
  function normalizeExpiry(exp, fallbackMs) {
806
+ if (typeof exp === 'number' && !Number.isNaN(exp))
807
+ return exp;
762
808
  return Date.now() + fallbackMs;
763
809
  }
810
+ function normalizeUcanExpiry(exp, fallbackMs) {
811
+ return normalizeExpiry(exp, fallbackMs);
812
+ }
813
+ function decodeUcanPayload(token) {
814
+ const parts = String(token || '').split('.');
815
+ if (parts.length < 2)
816
+ return null;
817
+ const decoded = decodeBase64Url(parts[1]);
818
+ if (!decoded)
819
+ return null;
820
+ try {
821
+ const payload = JSON.parse(decoded);
822
+ if (!payload || typeof payload !== 'object')
823
+ return null;
824
+ return payload;
825
+ }
826
+ catch {
827
+ return null;
828
+ }
829
+ }
830
+ function getUcanTokenTiming(token, options = {}) {
831
+ const nowMs = options.nowMs ?? Date.now();
832
+ const payload = decodeUcanPayload(token);
833
+ const exp = typeof payload?.exp === 'number' ? payload.exp : null;
834
+ const nbf = typeof payload?.nbf === 'number' ? payload.nbf : null;
835
+ const payloadWithIat = payload;
836
+ const issuedAt = typeof payloadWithIat?.iat === 'number'
837
+ ? payloadWithIat.iat
838
+ : null;
839
+ const remainingMs = exp === null ? null : exp - nowMs;
840
+ const activeInMs = nbf === null ? 0 : Math.max(0, nbf - nowMs);
841
+ const expired = exp === null || (remainingMs !== null && remainingMs <= 0);
842
+ const notBefore = activeInMs > 0;
843
+ return {
844
+ valid: Boolean(payload && !expired && !notBefore),
845
+ payload,
846
+ exp,
847
+ nbf,
848
+ issuedAt,
849
+ nowMs,
850
+ remainingMs,
851
+ activeInMs,
852
+ expired,
853
+ notBefore,
854
+ };
855
+ }
856
+ function isUcanTokenFresh(tokenOrTiming, options = {}) {
857
+ const timing = typeof tokenOrTiming === 'string'
858
+ ? getUcanTokenTiming(tokenOrTiming, { nowMs: options.nowMs })
859
+ : tokenOrTiming;
860
+ if (!timing.valid)
861
+ return false;
862
+ const skewMs = Math.max(0, options.skewMs ?? DEFAULT_UCAN_TOKEN_SKEW_MS);
863
+ return typeof timing.remainingMs === 'number' && timing.remainingMs > skewMs;
864
+ }
865
+ function readErrorField(error, field) {
866
+ if (!error || typeof error !== 'object')
867
+ return undefined;
868
+ const value = error[field];
869
+ if (value !== undefined)
870
+ return value;
871
+ const nestedError = error.error;
872
+ if (nestedError && typeof nestedError === 'object') {
873
+ return nestedError[field];
874
+ }
875
+ return undefined;
876
+ }
877
+ function classifyUcanAuthError(error) {
878
+ const messageValue = readErrorField(error, 'message');
879
+ const codeValue = readErrorField(error, 'code');
880
+ const statusValue = readErrorField(error, 'status') ?? readErrorField(error, 'statusCode');
881
+ const message = typeof messageValue === 'string'
882
+ ? messageValue
883
+ : error instanceof Error
884
+ ? error.message
885
+ : String(messageValue || error || '');
886
+ const status = typeof statusValue === 'number' ? statusValue : undefined;
887
+ const code = typeof codeValue === 'string' || typeof codeValue === 'number' ? codeValue : undefined;
888
+ const normalized = `${message} ${String(code || '')}`.toLowerCase();
889
+ if (/ucan.*expired|expired.*ucan|token.*expired|jwt.*expired|\bexp\b/.test(normalized)) {
890
+ return { type: 'expired', message, retryable: true, shouldRefresh: true, status, code };
891
+ }
892
+ if (/not.?before|\bnbf\b|not yet valid/.test(normalized)) {
893
+ return { type: 'not-before', message, retryable: true, shouldRefresh: false, status, code };
894
+ }
895
+ if (/invalid.*token|malformed.*token|bad.*ucan|invalid.*ucan/.test(normalized)) {
896
+ return { type: 'invalid-token', message, retryable: true, shouldRefresh: true, status, code };
897
+ }
898
+ if (status === 401 || /unauthori[sz]ed|unauthenticated/.test(normalized)) {
899
+ return { type: 'unauthorized', message, retryable: true, shouldRefresh: true, status, code };
900
+ }
901
+ if (status === 403 || /forbidden|permission denied|capability/.test(normalized)) {
902
+ return { type: 'forbidden', message, retryable: false, shouldRefresh: false, status, code };
903
+ }
904
+ return { type: 'unknown', message, retryable: false, shouldRefresh: false, status, code };
905
+ }
906
+ function isReplayableRequestBody(body) {
907
+ if (body == null)
908
+ return true;
909
+ if (typeof body === 'string')
910
+ return true;
911
+ if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams)
912
+ return true;
913
+ if (typeof FormData !== 'undefined' && body instanceof FormData)
914
+ return true;
915
+ if (typeof Blob !== 'undefined' && body instanceof Blob)
916
+ return true;
917
+ if (body instanceof ArrayBuffer)
918
+ return true;
919
+ if (ArrayBuffer.isView(body))
920
+ return true;
921
+ return false;
922
+ }
923
+ async function parseResponseJsonBody(response) {
924
+ const text = await response.text();
925
+ if (!text)
926
+ return null;
927
+ try {
928
+ return JSON.parse(text);
929
+ }
930
+ catch {
931
+ return { raw: text };
932
+ }
933
+ }
934
+ function shouldRetryUcanFetch(response, errorInfo) {
935
+ if (errorInfo.type === 'expired' || errorInfo.shouldRefresh) {
936
+ return true;
937
+ }
938
+ if (response.status === 401)
939
+ return true;
940
+ if (response.status === 403 && errorInfo.type === 'forbidden')
941
+ return false;
942
+ return false;
943
+ }
764
944
  function isSessionExpired(expiresAt, nowMs = Date.now()) {
765
945
  return typeof expiresAt === 'number' && nowMs >= expiresAt;
766
946
  }
@@ -857,7 +1037,7 @@ async function createLocalSession(options, record) {
857
1037
  buildDidKey(pair.publicKey),
858
1038
  ]);
859
1039
  const createdAt = Date.now();
860
- const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
1040
+ const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
861
1041
  const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
862
1042
  await writeSessionRecord({
863
1043
  id: sessionId,
@@ -1125,7 +1305,7 @@ async function createRootUcan(options) {
1125
1305
  const domain = options.domain || (typeof window !== 'undefined' ? window.location.host : '127.0.0.1');
1126
1306
  const uri = options.uri || (typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1');
1127
1307
  const nonce = options.nonce || randomNonce(8);
1128
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
1308
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
1129
1309
  const nbf = options.notBeforeMs;
1130
1310
  const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1131
1311
  if (!normalizedCapabilities.length) {
@@ -1209,7 +1389,7 @@ async function createDelegationUcan(options) {
1209
1389
  if (!normalizedCapabilities.length) {
1210
1390
  throw new Error('Missing UCAN capabilities');
1211
1391
  }
1212
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
1392
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
1213
1393
  const payload = {
1214
1394
  iss: issuer.did,
1215
1395
  aud: options.audience,
@@ -1231,7 +1411,7 @@ async function createInvocationUcan(options) {
1231
1411
  if (!normalizedCapabilities.length) {
1232
1412
  throw new Error('Missing UCAN capabilities');
1233
1413
  }
1234
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
1414
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
1235
1415
  const payload = {
1236
1416
  iss: issuer.did,
1237
1417
  aud: options.audience,
@@ -1242,30 +1422,84 @@ async function createInvocationUcan(options) {
1242
1422
  };
1243
1423
  return await signUcanPayload(payload, issuer);
1244
1424
  }
1425
+ async function getOrCreateInvocationUcan(options) {
1426
+ if (options.ucan &&
1427
+ isUcanTokenFresh(options.ucan, {
1428
+ nowMs: options.nowMs,
1429
+ skewMs: options.skewMs,
1430
+ })) {
1431
+ return options.ucan;
1432
+ }
1433
+ return await createInvocationUcan({
1434
+ issuer: options.issuer,
1435
+ sessionId: options.sessionId,
1436
+ provider: options.provider,
1437
+ audience: options.audience,
1438
+ capabilities: options.capabilities,
1439
+ expiresInMs: options.expiresInMs,
1440
+ notBeforeMs: options.notBeforeMs,
1441
+ proofs: options.proofs,
1442
+ });
1443
+ }
1245
1444
  async function authUcanFetch(input, init = {}, options = {}) {
1246
1445
  const fetcher = options.fetcher || fetch;
1247
- let token = options.ucan;
1248
- if (!token) {
1249
- if (!options.audience || !options.capabilities) {
1446
+ const audience = options.audience;
1447
+ const capabilities = options.capabilities;
1448
+ const canRefresh = Boolean(audience && capabilities);
1449
+ const canRetry = canRefresh && isReplayableRequestBody(init.body);
1450
+ let token = options.ucan || '';
1451
+ if (!token || (canRefresh && !isUcanTokenFresh(token, { skewMs: options.skewMs }))) {
1452
+ if (!audience || !capabilities) {
1250
1453
  throw new Error('Missing UCAN audience or capabilities');
1251
1454
  }
1252
- token = await createInvocationUcan({
1455
+ token = await getOrCreateInvocationUcan({
1456
+ ucan: options.ucan,
1253
1457
  issuer: options.issuer,
1254
1458
  sessionId: options.sessionId,
1255
1459
  provider: options.provider,
1256
- audience: options.audience,
1257
- capabilities: options.capabilities,
1460
+ audience,
1461
+ capabilities,
1258
1462
  expiresInMs: options.expiresInMs,
1259
1463
  notBeforeMs: options.notBeforeMs,
1260
1464
  proofs: options.proofs,
1465
+ skewMs: options.skewMs,
1261
1466
  });
1262
1467
  }
1263
- const headers = new Headers(init.headers || {});
1264
- headers.set('Authorization', `Bearer ${token}`);
1265
- return fetcher(input, {
1468
+ const makeHeaders = (bearer) => {
1469
+ const headers = new Headers(init.headers || {});
1470
+ headers.set('Authorization', `Bearer ${bearer}`);
1471
+ return headers;
1472
+ };
1473
+ let response = await fetcher(input, {
1266
1474
  ...init,
1267
- headers,
1475
+ headers: makeHeaders(token),
1268
1476
  });
1477
+ if (!canRetry || response.ok) {
1478
+ return response;
1479
+ }
1480
+ const payload = await parseResponseJsonBody(response.clone()).catch(() => null);
1481
+ const errorInfo = classifyUcanAuthError(payload || response.statusText || response);
1482
+ if (!shouldRetryUcanFetch(response, errorInfo)) {
1483
+ return response;
1484
+ }
1485
+ if (!audience || !capabilities) {
1486
+ return response;
1487
+ }
1488
+ const refreshedToken = await createInvocationUcan({
1489
+ issuer: options.issuer,
1490
+ sessionId: options.sessionId,
1491
+ provider: options.provider,
1492
+ audience,
1493
+ capabilities,
1494
+ expiresInMs: options.expiresInMs,
1495
+ notBeforeMs: options.notBeforeMs,
1496
+ proofs: options.proofs,
1497
+ });
1498
+ response = await fetcher(input, {
1499
+ ...init,
1500
+ headers: makeHeaders(refreshedToken),
1501
+ });
1502
+ return response;
1269
1503
  }
1270
1504
 
1271
1505
  const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
@@ -1860,7 +2094,6 @@ function createWebDavClient(options) {
1860
2094
  }
1861
2095
 
1862
2096
  const tokenCache = new Map();
1863
- const TOKEN_SKEW_MS = 5000;
1864
2097
  const DEFAULT_APP_ACTION = 'write';
1865
2098
  const LOOPBACK_HOST_ALIASES = new Set([
1866
2099
  'localhost',
@@ -2002,64 +2235,19 @@ function resolveInvocationCaps(options, fallbackCaps) {
2002
2235
  const caps = options.invocationCapabilities || fallbackCaps;
2003
2236
  return ensureAppCapability(caps, options);
2004
2237
  }
2005
- function isTokenValid(entry, nowMs) {
2006
- if (!entry.exp)
2007
- return false;
2008
- if (entry.nbf && nowMs < entry.nbf)
2009
- return false;
2010
- return entry.exp - TOKEN_SKEW_MS > nowMs;
2011
- }
2012
- function decodeBase64Url(input) {
2013
- if (!input)
2014
- return null;
2015
- const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
2016
- const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
2017
- try {
2018
- if (typeof atob === 'function') {
2019
- return atob(padded);
2020
- }
2021
- }
2022
- catch {
2023
- // ignore
2024
- }
2025
- try {
2026
- const nodeBuffer = globalThis.Buffer;
2027
- if (nodeBuffer) {
2028
- return nodeBuffer.from(padded, 'base64').toString('utf8');
2029
- }
2030
- }
2031
- catch {
2032
- return null;
2033
- }
2034
- return null;
2035
- }
2036
- function decodeUcanPayload(token) {
2037
- const parts = token.split('.');
2038
- if (parts.length < 2)
2039
- return null;
2040
- const decoded = decodeBase64Url(parts[1]);
2041
- if (!decoded)
2042
- return null;
2043
- try {
2044
- return JSON.parse(decoded);
2045
- }
2046
- catch {
2047
- return null;
2048
- }
2049
- }
2050
2238
  async function getCachedInvocationToken(options) {
2051
2239
  const cacheKey = buildTokenCacheKey(options.issuer, options.audience, options.capabilities);
2052
2240
  const cached = tokenCache.get(cacheKey);
2053
2241
  const nowMs = Date.now();
2054
- if (cached && isTokenValid(cached, nowMs)) {
2055
- return cached.token;
2056
- }
2057
- const token = await createInvocationUcan({
2242
+ const token = await getOrCreateInvocationUcan({
2243
+ ucan: cached?.token,
2058
2244
  issuer: options.issuer,
2059
2245
  audience: options.audience,
2060
2246
  capabilities: options.capabilities,
2061
2247
  proofs: options.proofs,
2062
2248
  expiresInMs: options.expiresInMs,
2249
+ skewMs: options.skewMs,
2250
+ nowMs,
2063
2251
  notBeforeMs: options.notBeforeMs,
2064
2252
  });
2065
2253
  const payload = decodeUcanPayload(token);
@@ -2110,6 +2298,7 @@ async function initWebDavStorage(options) {
2110
2298
  capabilities: invocationCaps,
2111
2299
  proofs: [root],
2112
2300
  expiresInMs: options.invocationExpiresInMs,
2301
+ skewMs: options.invocationSkewMs,
2113
2302
  notBeforeMs: options.notBeforeMs,
2114
2303
  });
2115
2304
  const client = createWebDavClient({
@@ -2166,5 +2355,5 @@ async function initDappSession(options) {
2166
2355
  return result;
2167
2356
  }
2168
2357
 
2169
- export { WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, classifyWalletError, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, deriveAppIdFromHost, deriveAppIdFromLocation, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, getWalletErrorCode, getWalletErrorMessage, initDappSession, initWebDavStorage, isUserRejectedWalletAction, isWalletReconnectError, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeAppHostnameForAppId, normalizeUcanCapabilities, normalizeUcanCapability, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts, watchProvider };
2358
+ export { DEFAULT_UCAN_SESSION_TTL_MS, DEFAULT_UCAN_TOKEN_SKEW_MS, DEFAULT_UCAN_TOKEN_TTL_MS, WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, classifyUcanAuthError, classifyWalletError, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, decodeUcanPayload, deriveAppIdFromHost, deriveAppIdFromLocation, focusPendingApproval, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateInvocationUcan, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, getUcanTokenTiming, getWalletErrorCode, getWalletErrorMessage, initDappSession, initWebDavStorage, isUcanTokenFresh, isUserRejectedWalletAction, isWalletReconnectError, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeAppHostnameForAppId, normalizeUcanCapabilities, normalizeUcanCapability, normalizeUcanExpiry, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts, watchProvider };
2170
2359
  //# sourceMappingURL=web3-bs.esm.js.map