@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.
@@ -300,6 +300,25 @@
300
300
  requestAccountsInFlight.delete(provider);
301
301
  }
302
302
  }
303
+ async function focusPendingApproval(provider) {
304
+ const p = provider || (await requireProvider());
305
+ const result = await p.request({
306
+ method: 'wallet_focusPendingApproval',
307
+ });
308
+ if (!result || typeof result !== 'object') {
309
+ return { focused: false, type: null };
310
+ }
311
+ const payload = result;
312
+ return {
313
+ focused: Boolean(payload.focused),
314
+ type: typeof payload.type === 'string' ? payload.type : null,
315
+ requestId: typeof payload.requestId === 'string' ? payload.requestId : null,
316
+ origin: typeof payload.origin === 'string' ? payload.origin : '',
317
+ tabId: typeof payload.tabId === 'number' && Number.isFinite(payload.tabId)
318
+ ? payload.tabId
319
+ : null,
320
+ };
321
+ }
303
322
  async function getAccounts(provider) {
304
323
  const p = provider || (await requireProvider());
305
324
  const accounts = (await p.request({ method: 'eth_accounts' }));
@@ -675,8 +694,9 @@
675
694
  }
676
695
 
677
696
  const DEFAULT_SESSION_ID = 'default';
678
- const DEFAULT_SESSION_TTL = 24 * 60 * 60 * 1000;
679
- const DEFAULT_UCAN_TTL = 5 * 60 * 1000;
697
+ const DEFAULT_UCAN_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
698
+ const DEFAULT_UCAN_TOKEN_TTL_MS = 40 * 60 * 1000;
699
+ const DEFAULT_UCAN_TOKEN_SKEW_MS = 60 * 1000;
680
700
  const DB_NAME = 'yeying-web3';
681
701
  const DB_STORE = 'ucan-sessions';
682
702
  const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
@@ -757,6 +777,30 @@
757
777
  function encodeJson(value) {
758
778
  return toBase64Url(textEncoder.encode(JSON.stringify(value)));
759
779
  }
780
+ function decodeBase64Url(input) {
781
+ if (!input)
782
+ return null;
783
+ const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
784
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
785
+ try {
786
+ if (typeof atob === 'function') {
787
+ return atob(padded);
788
+ }
789
+ }
790
+ catch {
791
+ // Try Node-compatible fallback below.
792
+ }
793
+ try {
794
+ const nodeBuffer = globalThis.Buffer;
795
+ if (nodeBuffer) {
796
+ return nodeBuffer.from(padded, 'base64').toString('utf8');
797
+ }
798
+ }
799
+ catch {
800
+ return null;
801
+ }
802
+ return null;
803
+ }
760
804
  function randomNonce(bytes = 16) {
761
805
  const buffer = new Uint8Array(bytes);
762
806
  crypto.getRandomValues(buffer);
@@ -765,8 +809,144 @@
765
809
  .join('');
766
810
  }
767
811
  function normalizeExpiry(exp, fallbackMs) {
812
+ if (typeof exp === 'number' && !Number.isNaN(exp))
813
+ return exp;
768
814
  return Date.now() + fallbackMs;
769
815
  }
816
+ function normalizeUcanExpiry(exp, fallbackMs) {
817
+ return normalizeExpiry(exp, fallbackMs);
818
+ }
819
+ function decodeUcanPayload(token) {
820
+ const parts = String(token || '').split('.');
821
+ if (parts.length < 2)
822
+ return null;
823
+ const decoded = decodeBase64Url(parts[1]);
824
+ if (!decoded)
825
+ return null;
826
+ try {
827
+ const payload = JSON.parse(decoded);
828
+ if (!payload || typeof payload !== 'object')
829
+ return null;
830
+ return payload;
831
+ }
832
+ catch {
833
+ return null;
834
+ }
835
+ }
836
+ function getUcanTokenTiming(token, options = {}) {
837
+ const nowMs = options.nowMs ?? Date.now();
838
+ const payload = decodeUcanPayload(token);
839
+ const exp = typeof payload?.exp === 'number' ? payload.exp : null;
840
+ const nbf = typeof payload?.nbf === 'number' ? payload.nbf : null;
841
+ const payloadWithIat = payload;
842
+ const issuedAt = typeof payloadWithIat?.iat === 'number'
843
+ ? payloadWithIat.iat
844
+ : null;
845
+ const remainingMs = exp === null ? null : exp - nowMs;
846
+ const activeInMs = nbf === null ? 0 : Math.max(0, nbf - nowMs);
847
+ const expired = exp === null || (remainingMs !== null && remainingMs <= 0);
848
+ const notBefore = activeInMs > 0;
849
+ return {
850
+ valid: Boolean(payload && !expired && !notBefore),
851
+ payload,
852
+ exp,
853
+ nbf,
854
+ issuedAt,
855
+ nowMs,
856
+ remainingMs,
857
+ activeInMs,
858
+ expired,
859
+ notBefore,
860
+ };
861
+ }
862
+ function isUcanTokenFresh(tokenOrTiming, options = {}) {
863
+ const timing = typeof tokenOrTiming === 'string'
864
+ ? getUcanTokenTiming(tokenOrTiming, { nowMs: options.nowMs })
865
+ : tokenOrTiming;
866
+ if (!timing.valid)
867
+ return false;
868
+ const skewMs = Math.max(0, options.skewMs ?? DEFAULT_UCAN_TOKEN_SKEW_MS);
869
+ return typeof timing.remainingMs === 'number' && timing.remainingMs > skewMs;
870
+ }
871
+ function readErrorField(error, field) {
872
+ if (!error || typeof error !== 'object')
873
+ return undefined;
874
+ const value = error[field];
875
+ if (value !== undefined)
876
+ return value;
877
+ const nestedError = error.error;
878
+ if (nestedError && typeof nestedError === 'object') {
879
+ return nestedError[field];
880
+ }
881
+ return undefined;
882
+ }
883
+ function classifyUcanAuthError(error) {
884
+ const messageValue = readErrorField(error, 'message');
885
+ const codeValue = readErrorField(error, 'code');
886
+ const statusValue = readErrorField(error, 'status') ?? readErrorField(error, 'statusCode');
887
+ const message = typeof messageValue === 'string'
888
+ ? messageValue
889
+ : error instanceof Error
890
+ ? error.message
891
+ : String(messageValue || error || '');
892
+ const status = typeof statusValue === 'number' ? statusValue : undefined;
893
+ const code = typeof codeValue === 'string' || typeof codeValue === 'number' ? codeValue : undefined;
894
+ const normalized = `${message} ${String(code || '')}`.toLowerCase();
895
+ if (/ucan.*expired|expired.*ucan|token.*expired|jwt.*expired|\bexp\b/.test(normalized)) {
896
+ return { type: 'expired', message, retryable: true, shouldRefresh: true, status, code };
897
+ }
898
+ if (/not.?before|\bnbf\b|not yet valid/.test(normalized)) {
899
+ return { type: 'not-before', message, retryable: true, shouldRefresh: false, status, code };
900
+ }
901
+ if (/invalid.*token|malformed.*token|bad.*ucan|invalid.*ucan/.test(normalized)) {
902
+ return { type: 'invalid-token', message, retryable: true, shouldRefresh: true, status, code };
903
+ }
904
+ if (status === 401 || /unauthori[sz]ed|unauthenticated/.test(normalized)) {
905
+ return { type: 'unauthorized', message, retryable: true, shouldRefresh: true, status, code };
906
+ }
907
+ if (status === 403 || /forbidden|permission denied|capability/.test(normalized)) {
908
+ return { type: 'forbidden', message, retryable: false, shouldRefresh: false, status, code };
909
+ }
910
+ return { type: 'unknown', message, retryable: false, shouldRefresh: false, status, code };
911
+ }
912
+ function isReplayableRequestBody(body) {
913
+ if (body == null)
914
+ return true;
915
+ if (typeof body === 'string')
916
+ return true;
917
+ if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams)
918
+ return true;
919
+ if (typeof FormData !== 'undefined' && body instanceof FormData)
920
+ return true;
921
+ if (typeof Blob !== 'undefined' && body instanceof Blob)
922
+ return true;
923
+ if (body instanceof ArrayBuffer)
924
+ return true;
925
+ if (ArrayBuffer.isView(body))
926
+ return true;
927
+ return false;
928
+ }
929
+ async function parseResponseJsonBody(response) {
930
+ const text = await response.text();
931
+ if (!text)
932
+ return null;
933
+ try {
934
+ return JSON.parse(text);
935
+ }
936
+ catch {
937
+ return { raw: text };
938
+ }
939
+ }
940
+ function shouldRetryUcanFetch(response, errorInfo) {
941
+ if (errorInfo.type === 'expired' || errorInfo.shouldRefresh) {
942
+ return true;
943
+ }
944
+ if (response.status === 401)
945
+ return true;
946
+ if (response.status === 403 && errorInfo.type === 'forbidden')
947
+ return false;
948
+ return false;
949
+ }
770
950
  function isSessionExpired(expiresAt, nowMs = Date.now()) {
771
951
  return typeof expiresAt === 'number' && nowMs >= expiresAt;
772
952
  }
@@ -863,7 +1043,7 @@
863
1043
  buildDidKey(pair.publicKey),
864
1044
  ]);
865
1045
  const createdAt = Date.now();
866
- const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
1046
+ const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
867
1047
  const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
868
1048
  await writeSessionRecord({
869
1049
  id: sessionId,
@@ -1131,7 +1311,7 @@
1131
1311
  const domain = options.domain || (typeof window !== 'undefined' ? window.location.host : '127.0.0.1');
1132
1312
  const uri = options.uri || (typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1');
1133
1313
  const nonce = options.nonce || randomNonce(8);
1134
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
1314
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
1135
1315
  const nbf = options.notBeforeMs;
1136
1316
  const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1137
1317
  if (!normalizedCapabilities.length) {
@@ -1215,7 +1395,7 @@
1215
1395
  if (!normalizedCapabilities.length) {
1216
1396
  throw new Error('Missing UCAN capabilities');
1217
1397
  }
1218
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
1398
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
1219
1399
  const payload = {
1220
1400
  iss: issuer.did,
1221
1401
  aud: options.audience,
@@ -1237,7 +1417,7 @@
1237
1417
  if (!normalizedCapabilities.length) {
1238
1418
  throw new Error('Missing UCAN capabilities');
1239
1419
  }
1240
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
1420
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
1241
1421
  const payload = {
1242
1422
  iss: issuer.did,
1243
1423
  aud: options.audience,
@@ -1248,30 +1428,84 @@
1248
1428
  };
1249
1429
  return await signUcanPayload(payload, issuer);
1250
1430
  }
1431
+ async function getOrCreateInvocationUcan(options) {
1432
+ if (options.ucan &&
1433
+ isUcanTokenFresh(options.ucan, {
1434
+ nowMs: options.nowMs,
1435
+ skewMs: options.skewMs,
1436
+ })) {
1437
+ return options.ucan;
1438
+ }
1439
+ return await createInvocationUcan({
1440
+ issuer: options.issuer,
1441
+ sessionId: options.sessionId,
1442
+ provider: options.provider,
1443
+ audience: options.audience,
1444
+ capabilities: options.capabilities,
1445
+ expiresInMs: options.expiresInMs,
1446
+ notBeforeMs: options.notBeforeMs,
1447
+ proofs: options.proofs,
1448
+ });
1449
+ }
1251
1450
  async function authUcanFetch(input, init = {}, options = {}) {
1252
1451
  const fetcher = options.fetcher || fetch;
1253
- let token = options.ucan;
1254
- if (!token) {
1255
- if (!options.audience || !options.capabilities) {
1452
+ const audience = options.audience;
1453
+ const capabilities = options.capabilities;
1454
+ const canRefresh = Boolean(audience && capabilities);
1455
+ const canRetry = canRefresh && isReplayableRequestBody(init.body);
1456
+ let token = options.ucan || '';
1457
+ if (!token || (canRefresh && !isUcanTokenFresh(token, { skewMs: options.skewMs }))) {
1458
+ if (!audience || !capabilities) {
1256
1459
  throw new Error('Missing UCAN audience or capabilities');
1257
1460
  }
1258
- token = await createInvocationUcan({
1461
+ token = await getOrCreateInvocationUcan({
1462
+ ucan: options.ucan,
1259
1463
  issuer: options.issuer,
1260
1464
  sessionId: options.sessionId,
1261
1465
  provider: options.provider,
1262
- audience: options.audience,
1263
- capabilities: options.capabilities,
1466
+ audience,
1467
+ capabilities,
1264
1468
  expiresInMs: options.expiresInMs,
1265
1469
  notBeforeMs: options.notBeforeMs,
1266
1470
  proofs: options.proofs,
1471
+ skewMs: options.skewMs,
1267
1472
  });
1268
1473
  }
1269
- const headers = new Headers(init.headers || {});
1270
- headers.set('Authorization', `Bearer ${token}`);
1271
- return fetcher(input, {
1474
+ const makeHeaders = (bearer) => {
1475
+ const headers = new Headers(init.headers || {});
1476
+ headers.set('Authorization', `Bearer ${bearer}`);
1477
+ return headers;
1478
+ };
1479
+ let response = await fetcher(input, {
1272
1480
  ...init,
1273
- headers,
1481
+ headers: makeHeaders(token),
1274
1482
  });
1483
+ if (!canRetry || response.ok) {
1484
+ return response;
1485
+ }
1486
+ const payload = await parseResponseJsonBody(response.clone()).catch(() => null);
1487
+ const errorInfo = classifyUcanAuthError(payload || response.statusText || response);
1488
+ if (!shouldRetryUcanFetch(response, errorInfo)) {
1489
+ return response;
1490
+ }
1491
+ if (!audience || !capabilities) {
1492
+ return response;
1493
+ }
1494
+ const refreshedToken = await createInvocationUcan({
1495
+ issuer: options.issuer,
1496
+ sessionId: options.sessionId,
1497
+ provider: options.provider,
1498
+ audience,
1499
+ capabilities,
1500
+ expiresInMs: options.expiresInMs,
1501
+ notBeforeMs: options.notBeforeMs,
1502
+ proofs: options.proofs,
1503
+ });
1504
+ response = await fetcher(input, {
1505
+ ...init,
1506
+ headers: makeHeaders(refreshedToken),
1507
+ });
1508
+ return response;
1275
1509
  }
1276
1510
 
1277
1511
  const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
@@ -1866,7 +2100,6 @@
1866
2100
  }
1867
2101
 
1868
2102
  const tokenCache = new Map();
1869
- const TOKEN_SKEW_MS = 5000;
1870
2103
  const DEFAULT_APP_ACTION = 'write';
1871
2104
  const LOOPBACK_HOST_ALIASES = new Set([
1872
2105
  'localhost',
@@ -2008,64 +2241,19 @@
2008
2241
  const caps = options.invocationCapabilities || fallbackCaps;
2009
2242
  return ensureAppCapability(caps, options);
2010
2243
  }
2011
- function isTokenValid(entry, nowMs) {
2012
- if (!entry.exp)
2013
- return false;
2014
- if (entry.nbf && nowMs < entry.nbf)
2015
- return false;
2016
- return entry.exp - TOKEN_SKEW_MS > nowMs;
2017
- }
2018
- function decodeBase64Url(input) {
2019
- if (!input)
2020
- return null;
2021
- const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
2022
- const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
2023
- try {
2024
- if (typeof atob === 'function') {
2025
- return atob(padded);
2026
- }
2027
- }
2028
- catch {
2029
- // ignore
2030
- }
2031
- try {
2032
- const nodeBuffer = globalThis.Buffer;
2033
- if (nodeBuffer) {
2034
- return nodeBuffer.from(padded, 'base64').toString('utf8');
2035
- }
2036
- }
2037
- catch {
2038
- return null;
2039
- }
2040
- return null;
2041
- }
2042
- function decodeUcanPayload(token) {
2043
- const parts = token.split('.');
2044
- if (parts.length < 2)
2045
- return null;
2046
- const decoded = decodeBase64Url(parts[1]);
2047
- if (!decoded)
2048
- return null;
2049
- try {
2050
- return JSON.parse(decoded);
2051
- }
2052
- catch {
2053
- return null;
2054
- }
2055
- }
2056
2244
  async function getCachedInvocationToken(options) {
2057
2245
  const cacheKey = buildTokenCacheKey(options.issuer, options.audience, options.capabilities);
2058
2246
  const cached = tokenCache.get(cacheKey);
2059
2247
  const nowMs = Date.now();
2060
- if (cached && isTokenValid(cached, nowMs)) {
2061
- return cached.token;
2062
- }
2063
- const token = await createInvocationUcan({
2248
+ const token = await getOrCreateInvocationUcan({
2249
+ ucan: cached?.token,
2064
2250
  issuer: options.issuer,
2065
2251
  audience: options.audience,
2066
2252
  capabilities: options.capabilities,
2067
2253
  proofs: options.proofs,
2068
2254
  expiresInMs: options.expiresInMs,
2255
+ skewMs: options.skewMs,
2256
+ nowMs,
2069
2257
  notBeforeMs: options.notBeforeMs,
2070
2258
  });
2071
2259
  const payload = decodeUcanPayload(token);
@@ -2116,6 +2304,7 @@
2116
2304
  capabilities: invocationCaps,
2117
2305
  proofs: [root],
2118
2306
  expiresInMs: options.invocationExpiresInMs,
2307
+ skewMs: options.invocationSkewMs,
2119
2308
  notBeforeMs: options.notBeforeMs,
2120
2309
  });
2121
2310
  const client = createWebDavClient({
@@ -2172,10 +2361,14 @@
2172
2361
  return result;
2173
2362
  }
2174
2363
 
2364
+ exports.DEFAULT_UCAN_SESSION_TTL_MS = DEFAULT_UCAN_SESSION_TTL_MS;
2365
+ exports.DEFAULT_UCAN_TOKEN_SKEW_MS = DEFAULT_UCAN_TOKEN_SKEW_MS;
2366
+ exports.DEFAULT_UCAN_TOKEN_TTL_MS = DEFAULT_UCAN_TOKEN_TTL_MS;
2175
2367
  exports.WebDavClient = WebDavClient;
2176
2368
  exports.authCentralUcanFetch = authCentralUcanFetch;
2177
2369
  exports.authFetch = authFetch;
2178
2370
  exports.authUcanFetch = authUcanFetch;
2371
+ exports.classifyUcanAuthError = classifyUcanAuthError;
2179
2372
  exports.classifyWalletError = classifyWalletError;
2180
2373
  exports.clearAccessToken = clearAccessToken;
2181
2374
  exports.clearCentralSessionToken = clearCentralSessionToken;
@@ -2187,8 +2380,10 @@
2187
2380
  exports.createRootUcan = createRootUcan;
2188
2381
  exports.createUcanSession = createUcanSession;
2189
2382
  exports.createWebDavClient = createWebDavClient;
2383
+ exports.decodeUcanPayload = decodeUcanPayload;
2190
2384
  exports.deriveAppIdFromHost = deriveAppIdFromHost;
2191
2385
  exports.deriveAppIdFromLocation = deriveAppIdFromLocation;
2386
+ exports.focusPendingApproval = focusPendingApproval;
2192
2387
  exports.getAccessToken = getAccessToken;
2193
2388
  exports.getAccounts = getAccounts;
2194
2389
  exports.getBalance = getBalance;
@@ -2197,15 +2392,18 @@
2197
2392
  exports.getCentralIssuerInfo = getCentralIssuerInfo;
2198
2393
  exports.getCentralSessionToken = getCentralSessionToken;
2199
2394
  exports.getChainId = getChainId;
2395
+ exports.getOrCreateInvocationUcan = getOrCreateInvocationUcan;
2200
2396
  exports.getOrCreateUcanRoot = getOrCreateUcanRoot;
2201
2397
  exports.getPreferredAccount = getPreferredAccount;
2202
2398
  exports.getProvider = getProvider;
2203
2399
  exports.getStoredUcanRoot = getStoredUcanRoot;
2204
2400
  exports.getUcanSession = getUcanSession;
2401
+ exports.getUcanTokenTiming = getUcanTokenTiming;
2205
2402
  exports.getWalletErrorCode = getWalletErrorCode;
2206
2403
  exports.getWalletErrorMessage = getWalletErrorMessage;
2207
2404
  exports.initDappSession = initDappSession;
2208
2405
  exports.initWebDavStorage = initWebDavStorage;
2406
+ exports.isUcanTokenFresh = isUcanTokenFresh;
2209
2407
  exports.isUserRejectedWalletAction = isUserRejectedWalletAction;
2210
2408
  exports.isWalletReconnectError = isWalletReconnectError;
2211
2409
  exports.isYeYingProvider = isYeYingProvider;
@@ -2215,6 +2413,7 @@
2215
2413
  exports.normalizeAppHostnameForAppId = normalizeAppHostnameForAppId;
2216
2414
  exports.normalizeUcanCapabilities = normalizeUcanCapabilities;
2217
2415
  exports.normalizeUcanCapability = normalizeUcanCapability;
2416
+ exports.normalizeUcanExpiry = normalizeUcanExpiry;
2218
2417
  exports.onAccountsChanged = onAccountsChanged;
2219
2418
  exports.onChainChanged = onChainChanged;
2220
2419
  exports.refreshAccessToken = refreshAccessToken;