@yeying-community/web3-bs 1.0.4 → 1.0.6

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.
@@ -193,12 +193,12 @@ function onChainChanged(provider, handler) {
193
193
  return () => provider.removeListener?.('chainChanged', handler);
194
194
  }
195
195
 
196
- function normalizeBaseUrl$1(baseUrl) {
196
+ function normalizeBaseUrl$2(baseUrl) {
197
197
  return baseUrl.replace(/\/+$/, '');
198
198
  }
199
- function joinUrl$1(baseUrl, path) {
199
+ function joinUrl$2(baseUrl, path) {
200
200
  const trimmed = path.replace(/^\/+/, '');
201
- return `${normalizeBaseUrl$1(baseUrl)}/${trimmed}`;
201
+ return `${normalizeBaseUrl$2(baseUrl)}/${trimmed}`;
202
202
  }
203
203
  const DEFAULT_TOKEN_KEY = 'authToken';
204
204
  let cachedAccessToken = null;
@@ -209,10 +209,10 @@ function resolveTokenKey(options) {
209
209
  function shouldStoreToken(options) {
210
210
  return options?.storeToken !== false;
211
211
  }
212
- function resolveFetcher(options) {
212
+ function resolveFetcher$1(options) {
213
213
  return options?.fetcher || fetch;
214
214
  }
215
- function resolveCredentials(options) {
215
+ function resolveCredentials$1(options) {
216
216
  return options?.credentials ?? 'include';
217
217
  }
218
218
  function readStoredToken(options) {
@@ -352,11 +352,11 @@ async function signMessage(options) {
352
352
  async function loginWithChallenge(options = {}) {
353
353
  const provider = options.provider || (await requireProvider());
354
354
  const address = await resolveAddress$1(provider, options.address);
355
- const fetcher = resolveFetcher(options);
356
- const credentials = resolveCredentials(options);
355
+ const fetcher = resolveFetcher$1(options);
356
+ const credentials = resolveCredentials$1(options);
357
357
  const baseUrl = options.baseUrl || '/api/v1/public/auth';
358
- const challengeUrl = joinUrl$1(baseUrl, options.challengePath || 'challenge');
359
- const verifyUrl = joinUrl$1(baseUrl, options.verifyPath || 'verify');
358
+ const challengeUrl = joinUrl$2(baseUrl, options.challengePath || 'challenge');
359
+ const verifyUrl = joinUrl$2(baseUrl, options.verifyPath || 'verify');
360
360
  const challengeBody = {
361
361
  address,
362
362
  };
@@ -420,10 +420,10 @@ async function refreshAccessToken(options = {}) {
420
420
  return refreshInFlight;
421
421
  }
422
422
  const task = (async () => {
423
- const fetcher = resolveFetcher(options);
424
- const credentials = resolveCredentials(options);
423
+ const fetcher = resolveFetcher$1(options);
424
+ const credentials = resolveCredentials$1(options);
425
425
  const baseUrl = options.baseUrl || '/api/v1/public/auth';
426
- const refreshUrl = joinUrl$1(baseUrl, options.refreshPath || 'refresh');
426
+ const refreshUrl = joinUrl$2(baseUrl, options.refreshPath || 'refresh');
427
427
  const refreshRes = await fetcher(refreshUrl, {
428
428
  method: 'POST',
429
429
  headers: {
@@ -452,10 +452,10 @@ async function refreshAccessToken(options = {}) {
452
452
  }
453
453
  }
454
454
  async function logout(options = {}) {
455
- const fetcher = resolveFetcher(options);
456
- const credentials = resolveCredentials(options);
455
+ const fetcher = resolveFetcher$1(options);
456
+ const credentials = resolveCredentials$1(options);
457
457
  const baseUrl = options.baseUrl || '/api/v1/public/auth';
458
- const logoutUrl = joinUrl$1(baseUrl, options.logoutPath || 'logout');
458
+ const logoutUrl = joinUrl$2(baseUrl, options.logoutPath || 'logout');
459
459
  const logoutRes = await fetcher(logoutUrl, {
460
460
  method: 'POST',
461
461
  headers: {
@@ -478,8 +478,8 @@ async function logout(options = {}) {
478
478
  return { response: payload };
479
479
  }
480
480
  async function authFetch(input, init = {}, options = {}) {
481
- const fetcher = resolveFetcher(options);
482
- const credentials = resolveCredentials(options);
481
+ const fetcher = resolveFetcher$1(options);
482
+ const credentials = resolveCredentials$1(options);
483
483
  const retryOnUnauthorized = options.retryOnUnauthorized !== false;
484
484
  const performRequest = async (tokenOverride) => {
485
485
  const headers = new Headers(init.headers || {});
@@ -511,6 +511,8 @@ const DEFAULT_SESSION_TTL = 24 * 60 * 60 * 1000;
511
511
  const DEFAULT_UCAN_TTL = 5 * 60 * 1000;
512
512
  const DB_NAME = 'yeying-web3';
513
513
  const DB_STORE = 'ucan-sessions';
514
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
515
+ const DID_KEY_ED25519_MULTICODEC = new Uint8Array([0xed, 0x01]);
514
516
  const textEncoder = new TextEncoder();
515
517
  function toBase64Url(data) {
516
518
  const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -520,6 +522,70 @@ function toBase64Url(data) {
520
522
  }
521
523
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
522
524
  }
525
+ function normalizeActionExpression(raw) {
526
+ const normalized = String(raw || '').trim().toLowerCase().replace(/\|/g, ',');
527
+ if (!normalized)
528
+ return '';
529
+ const parts = normalized
530
+ .split(',')
531
+ .map(part => part.trim())
532
+ .filter(Boolean);
533
+ if (!parts.length)
534
+ return '';
535
+ return Array.from(new Set(parts)).join(',');
536
+ }
537
+ function getCapabilityResource(cap) {
538
+ if (!cap || typeof cap !== 'object')
539
+ return '';
540
+ const withValue = typeof cap.with === 'string' ? cap.with.trim() : '';
541
+ if (withValue)
542
+ return withValue;
543
+ return typeof cap.resource === 'string' ? cap.resource.trim() : '';
544
+ }
545
+ function getCapabilityAction(cap) {
546
+ if (!cap || typeof cap !== 'object')
547
+ return '';
548
+ const canValue = typeof cap.can === 'string' ? cap.can.trim() : '';
549
+ if (canValue)
550
+ return normalizeActionExpression(canValue);
551
+ const actionValue = typeof cap.action === 'string' ? cap.action.trim() : '';
552
+ return normalizeActionExpression(actionValue);
553
+ }
554
+ function normalizeUcanCapability(cap, options = {}) {
555
+ const includeLegacyAliases = options.includeLegacyAliases !== false;
556
+ const resource = getCapabilityResource(cap);
557
+ const action = getCapabilityAction(cap);
558
+ if (!resource || !action)
559
+ return null;
560
+ const normalized = {
561
+ with: resource,
562
+ can: action,
563
+ };
564
+ if (includeLegacyAliases) {
565
+ normalized.resource = resource;
566
+ normalized.action = action;
567
+ }
568
+ if (cap && Object.prototype.hasOwnProperty.call(cap, 'nb')) {
569
+ normalized.nb = cap.nb;
570
+ }
571
+ return normalized;
572
+ }
573
+ function normalizeUcanCapabilities(caps, options = {}) {
574
+ const includeLegacyAliases = options.includeLegacyAliases !== false;
575
+ const seen = new Set();
576
+ const result = [];
577
+ for (const cap of caps || []) {
578
+ const normalized = normalizeUcanCapability(cap, { includeLegacyAliases });
579
+ if (!normalized)
580
+ continue;
581
+ const key = `${normalized.with}|${normalized.can}`;
582
+ if (seen.has(key))
583
+ continue;
584
+ seen.add(key);
585
+ result.push(normalized);
586
+ }
587
+ return result;
588
+ }
523
589
  function encodeJson(value) {
524
590
  return toBase64Url(textEncoder.encode(JSON.stringify(value)));
525
591
  }
@@ -533,6 +599,123 @@ function randomNonce(bytes = 16) {
533
599
  function normalizeExpiry(exp, fallbackMs) {
534
600
  return Date.now() + fallbackMs;
535
601
  }
602
+ function isSessionExpired(expiresAt, nowMs = Date.now()) {
603
+ return typeof expiresAt === 'number' && nowMs >= expiresAt;
604
+ }
605
+ function encodeBase58(bytes) {
606
+ if (bytes.length === 0)
607
+ return '';
608
+ let value = 0n;
609
+ for (const byte of bytes) {
610
+ value = (value << 8n) + BigInt(byte);
611
+ }
612
+ let encoded = '';
613
+ while (value > 0n) {
614
+ const mod = Number(value % 58n);
615
+ encoded = `${BASE58_ALPHABET[mod]}${encoded}`;
616
+ value /= 58n;
617
+ }
618
+ let leadingZeroCount = 0;
619
+ while (leadingZeroCount < bytes.length && bytes[leadingZeroCount] === 0) {
620
+ leadingZeroCount += 1;
621
+ }
622
+ if (leadingZeroCount > 0) {
623
+ encoded = `${'1'.repeat(leadingZeroCount)}${encoded}`;
624
+ }
625
+ return encoded || '1';
626
+ }
627
+ function ensureWebCrypto() {
628
+ if (typeof crypto === 'undefined' || !crypto.subtle) {
629
+ throw new Error('WebCrypto not available for UCAN session');
630
+ }
631
+ return crypto;
632
+ }
633
+ function parseSessionId(options) {
634
+ return options.id || DEFAULT_SESSION_ID;
635
+ }
636
+ function isLocalSessionRecord(record) {
637
+ return Boolean(record?.source === 'local' || record?.privateKeyJwk);
638
+ }
639
+ async function buildDidKey(publicKey) {
640
+ const webCrypto = ensureWebCrypto();
641
+ const raw = new Uint8Array(await webCrypto.subtle.exportKey('raw', publicKey));
642
+ const prefixed = new Uint8Array(DID_KEY_ED25519_MULTICODEC.length + raw.length);
643
+ prefixed.set(DID_KEY_ED25519_MULTICODEC, 0);
644
+ prefixed.set(raw, DID_KEY_ED25519_MULTICODEC.length);
645
+ return `did:key:z${encodeBase58(prefixed)}`;
646
+ }
647
+ async function importLocalPrivateKey(privateKeyJwk) {
648
+ const webCrypto = ensureWebCrypto();
649
+ return await webCrypto.subtle.importKey('jwk', privateKeyJwk, 'Ed25519', true, ['sign']);
650
+ }
651
+ async function loadLocalSessionFromRecord(id, record) {
652
+ if (!record || !isLocalSessionRecord(record) || !record.privateKeyJwk) {
653
+ return null;
654
+ }
655
+ if (isSessionExpired(record.expiresAt)) {
656
+ await deleteSessionRecord(id);
657
+ return null;
658
+ }
659
+ try {
660
+ const privateKey = await importLocalPrivateKey(record.privateKeyJwk);
661
+ return {
662
+ id: record.id || id,
663
+ did: record.did,
664
+ createdAt: record.createdAt,
665
+ expiresAt: record.expiresAt,
666
+ source: 'local',
667
+ privateKey,
668
+ };
669
+ }
670
+ catch {
671
+ return null;
672
+ }
673
+ }
674
+ function shouldKeepRootForSession(root, did, nowMs) {
675
+ if (!root)
676
+ return false;
677
+ if (root.aud && root.aud !== did)
678
+ return false;
679
+ if (isRootExpired(root, nowMs))
680
+ return false;
681
+ return true;
682
+ }
683
+ async function createLocalSession(options, record) {
684
+ const webCrypto = ensureWebCrypto();
685
+ const sessionId = parseSessionId(options);
686
+ if (!options.forceNew) {
687
+ const existing = await loadLocalSessionFromRecord(sessionId, record);
688
+ if (existing)
689
+ return existing;
690
+ }
691
+ const pair = (await webCrypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']));
692
+ const [privateKeyJwk, publicKeyJwk, did] = await Promise.all([
693
+ webCrypto.subtle.exportKey('jwk', pair.privateKey),
694
+ webCrypto.subtle.exportKey('jwk', pair.publicKey),
695
+ buildDidKey(pair.publicKey),
696
+ ]);
697
+ const createdAt = Date.now();
698
+ const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
699
+ const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
700
+ await writeSessionRecord({
701
+ id: sessionId,
702
+ did,
703
+ createdAt,
704
+ expiresAt,
705
+ source: 'local',
706
+ privateKeyJwk,
707
+ publicKeyJwk,
708
+ root,
709
+ });
710
+ return {
711
+ id: sessionId,
712
+ did,
713
+ createdAt,
714
+ expiresAt,
715
+ source: 'local',
716
+ privateKey: pair.privateKey,
717
+ };
718
+ }
536
719
  function openDb() {
537
720
  if (typeof indexedDB === 'undefined') {
538
721
  return Promise.reject(new Error('IndexedDB not available'));
@@ -595,17 +778,19 @@ async function deleteSessionRecord(id) {
595
778
  }
596
779
  }
597
780
  async function getUcanSession(id = DEFAULT_SESSION_ID, provider) {
781
+ const record = await readSessionRecord(id);
598
782
  const walletProvider = provider || (typeof window !== 'undefined'
599
783
  ? await getProvider({ preferYeYing: true })
600
784
  : null);
601
- if (!walletProvider)
602
- return null;
603
- try {
604
- return await requestWalletUcanSession(walletProvider, { id });
605
- }
606
- catch {
607
- return null;
785
+ if (walletProvider) {
786
+ try {
787
+ return await requestWalletUcanSession(walletProvider, { id });
788
+ }
789
+ catch {
790
+ return await loadLocalSessionFromRecord(id, record);
791
+ }
608
792
  }
793
+ return await loadLocalSessionFromRecord(id, record);
609
794
  }
610
795
  async function requestWalletUcanSession(provider, options) {
611
796
  const sessionId = options.id || DEFAULT_SESSION_ID;
@@ -630,6 +815,7 @@ async function requestWalletUcanSession(provider, options) {
630
815
  did: result.did,
631
816
  createdAt,
632
817
  expiresAt,
818
+ source: 'wallet',
633
819
  root: existing?.root,
634
820
  };
635
821
  if (nextRecord.root && nextRecord.root.aud && nextRecord.root.aud !== nextRecord.did) {
@@ -641,6 +827,7 @@ async function requestWalletUcanSession(provider, options) {
641
827
  did: result.did,
642
828
  createdAt,
643
829
  expiresAt,
830
+ source: 'wallet',
644
831
  signer: async (signingInput, payload) => {
645
832
  const signatureResult = (await provider.request({
646
833
  method: 'yeying_ucan_sign',
@@ -663,13 +850,20 @@ async function requestWalletUcanSession(provider, options) {
663
850
  };
664
851
  }
665
852
  async function createUcanSession(options = {}) {
853
+ const sessionId = parseSessionId(options);
854
+ const record = await readSessionRecord(sessionId);
666
855
  const provider = options.provider || (typeof window !== 'undefined'
667
856
  ? await getProvider({ preferYeYing: true })
668
857
  : null);
669
- if (!provider) {
670
- throw new Error('No wallet provider for UCAN session');
858
+ if (provider) {
859
+ try {
860
+ return await requestWalletUcanSession(provider, { ...options, id: sessionId });
861
+ }
862
+ catch {
863
+ // fallback to local ed25519 session
864
+ }
671
865
  }
672
- return await requestWalletUcanSession(provider, options);
866
+ return await createLocalSession({ ...options, id: sessionId }, record);
673
867
  }
674
868
  async function clearUcanSession(id = DEFAULT_SESSION_ID) {
675
869
  await deleteSessionRecord(id);
@@ -680,6 +874,7 @@ async function storeUcanRoot(root, id = DEFAULT_SESSION_ID) {
680
874
  const expiresAt = record?.expiresAt ?? null;
681
875
  const did = record?.did || root.aud;
682
876
  const nextRecord = {
877
+ ...(record || {}),
683
878
  id,
684
879
  did,
685
880
  createdAt,
@@ -693,7 +888,9 @@ async function getStoredUcanRoot(id = DEFAULT_SESSION_ID) {
693
888
  return record?.root || null;
694
889
  }
695
890
  function capsEqual(a, b) {
696
- return JSON.stringify(a || []) === JSON.stringify(b || []);
891
+ const left = normalizeUcanCapabilities(a, { includeLegacyAliases: false });
892
+ const right = normalizeUcanCapabilities(b, { includeLegacyAliases: false });
893
+ return JSON.stringify(left) === JSON.stringify(right);
697
894
  }
698
895
  function isRootExpired(root, nowMs) {
699
896
  return Boolean(root.exp && nowMs > root.exp);
@@ -768,9 +965,13 @@ async function createRootUcan(options) {
768
965
  const nonce = options.nonce || randomNonce(8);
769
966
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
770
967
  const nbf = options.notBeforeMs;
968
+ const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
969
+ if (!normalizedCapabilities.length) {
970
+ throw new Error('Missing UCAN capabilities');
971
+ }
771
972
  const statementPayload = {
772
973
  aud: session.did,
773
- cap: options.capabilities,
974
+ cap: normalizedCapabilities,
774
975
  exp,
775
976
  };
776
977
  if (nbf)
@@ -793,7 +994,7 @@ async function createRootUcan(options) {
793
994
  type: 'siwe',
794
995
  iss: `did:pkh:eth:${address.toLowerCase()}`,
795
996
  aud: session.did,
796
- cap: options.capabilities,
997
+ cap: normalizedCapabilities,
797
998
  exp,
798
999
  nbf,
799
1000
  siwe: {
@@ -842,11 +1043,15 @@ async function createDelegationUcan(options) {
842
1043
  }));
843
1044
  if (!issuer)
844
1045
  throw new Error('Missing UCAN session key');
1046
+ const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1047
+ if (!normalizedCapabilities.length) {
1048
+ throw new Error('Missing UCAN capabilities');
1049
+ }
845
1050
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
846
1051
  const payload = {
847
1052
  iss: issuer.did,
848
1053
  aud: options.audience,
849
- cap: options.capabilities,
1054
+ cap: normalizedCapabilities,
850
1055
  exp,
851
1056
  nbf: options.notBeforeMs,
852
1057
  prf: await resolveProofs(options, issuer),
@@ -860,11 +1065,15 @@ async function createInvocationUcan(options) {
860
1065
  }));
861
1066
  if (!issuer)
862
1067
  throw new Error('Missing UCAN session key');
1068
+ const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1069
+ if (!normalizedCapabilities.length) {
1070
+ throw new Error('Missing UCAN capabilities');
1071
+ }
863
1072
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
864
1073
  const payload = {
865
1074
  iss: issuer.did,
866
1075
  aud: options.audience,
867
- cap: options.capabilities,
1076
+ cap: normalizedCapabilities,
868
1077
  exp,
869
1078
  nbf: options.notBeforeMs,
870
1079
  prf: await resolveProofs(options, issuer),
@@ -897,6 +1106,291 @@ async function authUcanFetch(input, init = {}, options = {}) {
897
1106
  });
898
1107
  }
899
1108
 
1109
+ const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
1110
+ const DEFAULT_ISSUER_PATH = 'issuer';
1111
+ const DEFAULT_SESSION_PATH = 'session';
1112
+ const DEFAULT_ISSUE_PATH = 'issue';
1113
+ const DEFAULT_SESSION_TOKEN_KEY = 'centralUcanSessionToken';
1114
+ let cachedCentralSessionToken = null;
1115
+ function normalizeBaseUrl$1(baseUrl) {
1116
+ return baseUrl.replace(/\/+$/, '');
1117
+ }
1118
+ function joinUrl$1(baseUrl, path) {
1119
+ const trimmed = path.replace(/^\/+/, '');
1120
+ return `${normalizeBaseUrl$1(baseUrl)}/${trimmed}`;
1121
+ }
1122
+ function resolveBaseUrl(options) {
1123
+ return options?.baseUrl || DEFAULT_BASE_URL;
1124
+ }
1125
+ function resolveFetcher(options) {
1126
+ return options?.fetcher || fetch;
1127
+ }
1128
+ function resolveCredentials(options) {
1129
+ return options?.credentials ?? 'include';
1130
+ }
1131
+ function resolveSessionTokenKey(options) {
1132
+ return options?.sessionTokenStorageKey || DEFAULT_SESSION_TOKEN_KEY;
1133
+ }
1134
+ function shouldStoreSessionToken(options) {
1135
+ return options?.storeSessionToken !== false;
1136
+ }
1137
+ function parseObject(value) {
1138
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1139
+ return {};
1140
+ }
1141
+ return value;
1142
+ }
1143
+ function parseEnvelopeData(payload) {
1144
+ const root = parseObject(payload);
1145
+ if (Object.prototype.hasOwnProperty.call(root, 'data')) {
1146
+ return parseObject(root.data);
1147
+ }
1148
+ return root;
1149
+ }
1150
+ function parseStringField(obj, keys) {
1151
+ for (const key of keys) {
1152
+ const value = obj[key];
1153
+ if (typeof value === 'string') {
1154
+ return value;
1155
+ }
1156
+ }
1157
+ return undefined;
1158
+ }
1159
+ function parseNumberField(obj, keys) {
1160
+ for (const key of keys) {
1161
+ const value = obj[key];
1162
+ if (typeof value === 'number' && Number.isFinite(value)) {
1163
+ return value;
1164
+ }
1165
+ }
1166
+ return undefined;
1167
+ }
1168
+ function parseCapabilitiesField(obj, keys) {
1169
+ for (const key of keys) {
1170
+ const value = obj[key];
1171
+ if (!Array.isArray(value))
1172
+ continue;
1173
+ const caps = value
1174
+ .filter(item => item && typeof item === 'object')
1175
+ .map(item => normalizeUcanCapability(item))
1176
+ .filter((cap) => Boolean(cap));
1177
+ return caps;
1178
+ }
1179
+ return undefined;
1180
+ }
1181
+ function readStoredSessionToken(options) {
1182
+ if (!shouldStoreSessionToken(options))
1183
+ return null;
1184
+ if (typeof localStorage === 'undefined')
1185
+ return null;
1186
+ const key = resolveSessionTokenKey(options);
1187
+ return localStorage.getItem(key);
1188
+ }
1189
+ function persistSessionToken(token, options) {
1190
+ cachedCentralSessionToken = token;
1191
+ if (!shouldStoreSessionToken(options))
1192
+ return;
1193
+ if (typeof localStorage === 'undefined')
1194
+ return;
1195
+ const key = resolveSessionTokenKey(options);
1196
+ if (!token) {
1197
+ localStorage.removeItem(key);
1198
+ }
1199
+ else {
1200
+ localStorage.setItem(key, token);
1201
+ }
1202
+ }
1203
+ async function parseJsonBody(response) {
1204
+ const text = await response.text();
1205
+ if (!text)
1206
+ return null;
1207
+ try {
1208
+ return JSON.parse(text);
1209
+ }
1210
+ catch {
1211
+ return { raw: text };
1212
+ }
1213
+ }
1214
+ function getCentralSessionToken(options) {
1215
+ if (cachedCentralSessionToken)
1216
+ return cachedCentralSessionToken;
1217
+ const stored = readStoredSessionToken(options);
1218
+ if (stored) {
1219
+ cachedCentralSessionToken = stored;
1220
+ }
1221
+ return stored;
1222
+ }
1223
+ function setCentralSessionToken(token, options) {
1224
+ persistSessionToken(token, options);
1225
+ }
1226
+ function clearCentralSessionToken(options) {
1227
+ cachedCentralSessionToken = null;
1228
+ if (typeof localStorage === 'undefined')
1229
+ return;
1230
+ const key = resolveSessionTokenKey(options);
1231
+ localStorage.removeItem(key);
1232
+ }
1233
+ async function getCentralIssuerInfo(options = {}) {
1234
+ const fetcher = resolveFetcher(options);
1235
+ const credentials = resolveCredentials(options);
1236
+ const url = joinUrl$1(resolveBaseUrl(options), options.issuerPath || DEFAULT_ISSUER_PATH);
1237
+ const response = await fetcher(url, {
1238
+ method: 'GET',
1239
+ headers: {
1240
+ accept: 'application/json',
1241
+ },
1242
+ credentials,
1243
+ });
1244
+ const payload = await parseJsonBody(response);
1245
+ if (!response.ok) {
1246
+ throw new Error(`Central issuer request failed: ${response.status} ${JSON.stringify(payload)}`);
1247
+ }
1248
+ const data = parseEnvelopeData(payload);
1249
+ return {
1250
+ enabled: typeof data.enabled === 'boolean' ? data.enabled : undefined,
1251
+ issuerDid: parseStringField(data, ['issuerDid']),
1252
+ defaultAudience: parseStringField(data, ['defaultAudience']),
1253
+ defaultCapabilities: parseCapabilitiesField(data, ['defaultCapabilities']),
1254
+ response: payload,
1255
+ };
1256
+ }
1257
+ async function createCentralSession(options) {
1258
+ const subject = String(options?.subject || '').trim();
1259
+ if (!subject) {
1260
+ throw new Error('Missing subject');
1261
+ }
1262
+ const fetcher = resolveFetcher(options);
1263
+ const credentials = resolveCredentials(options);
1264
+ const url = joinUrl$1(resolveBaseUrl(options), options.sessionPath || DEFAULT_SESSION_PATH);
1265
+ const response = await fetcher(url, {
1266
+ method: 'POST',
1267
+ headers: {
1268
+ 'Content-Type': 'application/json',
1269
+ accept: 'application/json',
1270
+ },
1271
+ credentials,
1272
+ body: JSON.stringify({
1273
+ subject,
1274
+ sessionTtlMs: options.sessionTtlMs,
1275
+ }),
1276
+ });
1277
+ const payload = await parseJsonBody(response);
1278
+ if (!response.ok) {
1279
+ throw new Error(`Central session request failed: ${response.status} ${JSON.stringify(payload)}`);
1280
+ }
1281
+ const data = parseEnvelopeData(payload);
1282
+ const sessionToken = parseStringField(data, ['sessionToken']);
1283
+ if (!sessionToken) {
1284
+ throw new Error('Central session response missing sessionToken');
1285
+ }
1286
+ persistSessionToken(sessionToken, options);
1287
+ return {
1288
+ subject: parseStringField(data, ['subject']) || subject,
1289
+ sessionToken,
1290
+ expiresAt: parseNumberField(data, ['expiresAt']),
1291
+ issuerDid: parseStringField(data, ['issuerDid']),
1292
+ response: payload,
1293
+ };
1294
+ }
1295
+ async function issueCentralUcan(options = {}) {
1296
+ const sessionToken = options.sessionToken || getCentralSessionToken(options);
1297
+ if (!sessionToken) {
1298
+ throw new Error('Missing central session token');
1299
+ }
1300
+ const normalizedCapabilities = options.capabilities
1301
+ ? normalizeUcanCapabilities(options.capabilities)
1302
+ : undefined;
1303
+ const fetcher = resolveFetcher(options);
1304
+ const credentials = resolveCredentials(options);
1305
+ const url = joinUrl$1(resolveBaseUrl(options), options.issuePath || DEFAULT_ISSUE_PATH);
1306
+ const response = await fetcher(url, {
1307
+ method: 'POST',
1308
+ headers: {
1309
+ 'Content-Type': 'application/json',
1310
+ accept: 'application/json',
1311
+ Authorization: `Bearer ${sessionToken}`,
1312
+ },
1313
+ credentials,
1314
+ body: JSON.stringify({
1315
+ audience: options.audience,
1316
+ capabilities: normalizedCapabilities,
1317
+ expiresInMs: options.expiresInMs,
1318
+ ttlMs: options.ttlMs,
1319
+ }),
1320
+ });
1321
+ const payload = await parseJsonBody(response);
1322
+ if (!response.ok) {
1323
+ throw new Error(`Central UCAN issue failed: ${response.status} ${JSON.stringify(payload)}`);
1324
+ }
1325
+ const data = parseEnvelopeData(payload);
1326
+ const ucan = parseStringField(data, ['ucan']);
1327
+ if (!ucan) {
1328
+ throw new Error('Central UCAN response missing ucan');
1329
+ }
1330
+ return {
1331
+ ucan,
1332
+ issuerDid: parseStringField(data, ['issuerDid']),
1333
+ subject: parseStringField(data, ['subject']),
1334
+ audience: parseStringField(data, ['audience']),
1335
+ capabilities: parseCapabilitiesField(data, ['capabilities']),
1336
+ exp: parseNumberField(data, ['exp']),
1337
+ nbf: parseNumberField(data, ['nbf']),
1338
+ iat: parseNumberField(data, ['iat']),
1339
+ response: payload,
1340
+ };
1341
+ }
1342
+ async function createAndIssueCentralUcan(options) {
1343
+ const session = await createCentralSession({
1344
+ ...options,
1345
+ subject: options.subject,
1346
+ sessionTtlMs: options.sessionTtlMs,
1347
+ });
1348
+ const issue = await issueCentralUcan({
1349
+ ...options,
1350
+ sessionToken: session.sessionToken,
1351
+ audience: options.audience,
1352
+ capabilities: options.capabilities,
1353
+ expiresInMs: options.expiresInMs,
1354
+ ttlMs: options.ttlMs,
1355
+ });
1356
+ return { session, issue };
1357
+ }
1358
+ async function authCentralUcanFetch(input, init = {}, options = {}) {
1359
+ const fetcher = resolveFetcher(options);
1360
+ const credentials = resolveCredentials(options);
1361
+ let token = options.ucan || null;
1362
+ if (!token) {
1363
+ let sessionToken = options.sessionToken || getCentralSessionToken(options);
1364
+ if (!sessionToken) {
1365
+ if (!options.subject) {
1366
+ throw new Error('Missing central session token or subject');
1367
+ }
1368
+ const session = await createCentralSession({
1369
+ ...options,
1370
+ subject: options.subject,
1371
+ sessionTtlMs: options.sessionTtlMs,
1372
+ });
1373
+ sessionToken = session.sessionToken;
1374
+ }
1375
+ const issued = await issueCentralUcan({
1376
+ ...options,
1377
+ sessionToken,
1378
+ audience: options.audience,
1379
+ capabilities: options.capabilities,
1380
+ expiresInMs: options.expiresInMs,
1381
+ ttlMs: options.ttlMs,
1382
+ });
1383
+ token = issued.ucan;
1384
+ }
1385
+ const headers = new Headers(init.headers || {});
1386
+ headers.set('Authorization', `Bearer ${token}`);
1387
+ return fetcher(input, {
1388
+ ...init,
1389
+ headers,
1390
+ credentials,
1391
+ });
1392
+ }
1393
+
900
1394
  function normalizeBaseUrl(baseUrl) {
901
1395
  return baseUrl.replace(/\/+$/, '');
902
1396
  }
@@ -1113,6 +1607,7 @@ function createWebDavClient(options) {
1113
1607
 
1114
1608
  const tokenCache = new Map();
1115
1609
  const TOKEN_SKEW_MS = 5000;
1610
+ const DEFAULT_APP_ACTION = 'write';
1116
1611
  function normalizeAppDir(path) {
1117
1612
  const trimmed = path.trim();
1118
1613
  if (!trimmed)
@@ -1124,6 +1619,36 @@ function normalizeAppDir(path) {
1124
1619
  function sanitizeAppId(appId) {
1125
1620
  return appId.trim().replace(/[^a-zA-Z0-9._-]/g, '-');
1126
1621
  }
1622
+ function normalizeAction(action) {
1623
+ const trimmed = (action || '').trim();
1624
+ return trimmed ? trimmed : null;
1625
+ }
1626
+ function buildAppCapability(options) {
1627
+ if (!options.appId)
1628
+ return null;
1629
+ const action = normalizeAction(options.appAction) || DEFAULT_APP_ACTION;
1630
+ const resource = `app:all:${sanitizeAppId(options.appId)}`;
1631
+ return {
1632
+ with: resource,
1633
+ can: action,
1634
+ resource,
1635
+ action,
1636
+ };
1637
+ }
1638
+ function hasAppCapability(caps) {
1639
+ return (caps || []).some(cap => getCapabilityResource(cap).startsWith('app:'));
1640
+ }
1641
+ function dedupeCapabilities(caps) {
1642
+ return normalizeUcanCapabilities(caps);
1643
+ }
1644
+ function ensureAppCapability(caps, options) {
1645
+ const appCap = buildAppCapability(options);
1646
+ if (!appCap)
1647
+ return caps || [];
1648
+ if (hasAppCapability(caps || []))
1649
+ return caps || [];
1650
+ return dedupeCapabilities([...(caps || []), appCap]);
1651
+ }
1127
1652
  function resolveAppDir(options) {
1128
1653
  if (options.appDir) {
1129
1654
  return normalizeAppDir(options.appDir);
@@ -1134,11 +1659,25 @@ function resolveAppDir(options) {
1134
1659
  return undefined;
1135
1660
  }
1136
1661
  function buildCapsKey(caps) {
1137
- return JSON.stringify(caps || []);
1662
+ const canonical = (caps || [])
1663
+ .map(cap => ({
1664
+ with: getCapabilityResource(cap),
1665
+ can: getCapabilityAction(cap),
1666
+ }))
1667
+ .filter(cap => Boolean(cap.with && cap.can));
1668
+ return JSON.stringify(canonical);
1138
1669
  }
1139
1670
  function buildTokenCacheKey(issuer, audience, caps) {
1140
1671
  return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
1141
1672
  }
1673
+ function resolveWebdavCaps(options) {
1674
+ const baseCaps = options.capabilities || options.root?.cap || [];
1675
+ return ensureAppCapability(baseCaps, options);
1676
+ }
1677
+ function resolveInvocationCaps(options, fallbackCaps) {
1678
+ const caps = options.invocationCapabilities || fallbackCaps;
1679
+ return ensureAppCapability(caps, options);
1680
+ }
1142
1681
  function isTokenValid(entry, nowMs) {
1143
1682
  if (!entry.exp)
1144
1683
  return false;
@@ -1210,7 +1749,7 @@ async function getCachedInvocationToken(options) {
1210
1749
  return token;
1211
1750
  }
1212
1751
  async function initWebDavStorage(options) {
1213
- const caps = options.capabilities || options.root?.cap;
1752
+ const caps = resolveWebdavCaps(options);
1214
1753
  if (!caps || caps.length === 0) {
1215
1754
  throw new Error('Missing UCAN capabilities for WebDAV');
1216
1755
  }
@@ -1240,7 +1779,7 @@ async function initWebDavStorage(options) {
1240
1779
  expiresInMs: options.rootExpiresInMs,
1241
1780
  });
1242
1781
  }
1243
- const invocationCaps = options.invocationCapabilities || caps;
1782
+ const invocationCaps = resolveInvocationCaps(options, caps);
1244
1783
  const token = await getCachedInvocationToken({
1245
1784
  issuer: session,
1246
1785
  audience: options.audience,
@@ -1303,5 +1842,5 @@ async function initDappSession(options) {
1303
1842
  return result;
1304
1843
  }
1305
1844
 
1306
- export { WebDavClient, authFetch, authUcanFetch, clearAccessToken, clearUcanSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, getAccessToken, getAccounts, getBalance, getChainId, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, initDappSession, initWebDavStorage, isYeYingProvider, loginWithChallenge, logout, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, signMessage, storeUcanRoot, watchAccounts };
1845
+ export { WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, initDappSession, initWebDavStorage, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeUcanCapabilities, normalizeUcanCapability, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts };
1307
1846
  //# sourceMappingURL=web3-bs.esm.js.map