@yeying-community/web3-bs 1.0.5 → 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.
@@ -199,12 +199,12 @@
199
199
  return () => provider.removeListener?.('chainChanged', handler);
200
200
  }
201
201
 
202
- function normalizeBaseUrl$1(baseUrl) {
202
+ function normalizeBaseUrl$2(baseUrl) {
203
203
  return baseUrl.replace(/\/+$/, '');
204
204
  }
205
- function joinUrl$1(baseUrl, path) {
205
+ function joinUrl$2(baseUrl, path) {
206
206
  const trimmed = path.replace(/^\/+/, '');
207
- return `${normalizeBaseUrl$1(baseUrl)}/${trimmed}`;
207
+ return `${normalizeBaseUrl$2(baseUrl)}/${trimmed}`;
208
208
  }
209
209
  const DEFAULT_TOKEN_KEY = 'authToken';
210
210
  let cachedAccessToken = null;
@@ -215,10 +215,10 @@
215
215
  function shouldStoreToken(options) {
216
216
  return options?.storeToken !== false;
217
217
  }
218
- function resolveFetcher(options) {
218
+ function resolveFetcher$1(options) {
219
219
  return options?.fetcher || fetch;
220
220
  }
221
- function resolveCredentials(options) {
221
+ function resolveCredentials$1(options) {
222
222
  return options?.credentials ?? 'include';
223
223
  }
224
224
  function readStoredToken(options) {
@@ -358,11 +358,11 @@
358
358
  async function loginWithChallenge(options = {}) {
359
359
  const provider = options.provider || (await requireProvider());
360
360
  const address = await resolveAddress$1(provider, options.address);
361
- const fetcher = resolveFetcher(options);
362
- const credentials = resolveCredentials(options);
361
+ const fetcher = resolveFetcher$1(options);
362
+ const credentials = resolveCredentials$1(options);
363
363
  const baseUrl = options.baseUrl || '/api/v1/public/auth';
364
- const challengeUrl = joinUrl$1(baseUrl, options.challengePath || 'challenge');
365
- const verifyUrl = joinUrl$1(baseUrl, options.verifyPath || 'verify');
364
+ const challengeUrl = joinUrl$2(baseUrl, options.challengePath || 'challenge');
365
+ const verifyUrl = joinUrl$2(baseUrl, options.verifyPath || 'verify');
366
366
  const challengeBody = {
367
367
  address,
368
368
  };
@@ -426,10 +426,10 @@
426
426
  return refreshInFlight;
427
427
  }
428
428
  const task = (async () => {
429
- const fetcher = resolveFetcher(options);
430
- const credentials = resolveCredentials(options);
429
+ const fetcher = resolveFetcher$1(options);
430
+ const credentials = resolveCredentials$1(options);
431
431
  const baseUrl = options.baseUrl || '/api/v1/public/auth';
432
- const refreshUrl = joinUrl$1(baseUrl, options.refreshPath || 'refresh');
432
+ const refreshUrl = joinUrl$2(baseUrl, options.refreshPath || 'refresh');
433
433
  const refreshRes = await fetcher(refreshUrl, {
434
434
  method: 'POST',
435
435
  headers: {
@@ -458,10 +458,10 @@
458
458
  }
459
459
  }
460
460
  async function logout(options = {}) {
461
- const fetcher = resolveFetcher(options);
462
- const credentials = resolveCredentials(options);
461
+ const fetcher = resolveFetcher$1(options);
462
+ const credentials = resolveCredentials$1(options);
463
463
  const baseUrl = options.baseUrl || '/api/v1/public/auth';
464
- const logoutUrl = joinUrl$1(baseUrl, options.logoutPath || 'logout');
464
+ const logoutUrl = joinUrl$2(baseUrl, options.logoutPath || 'logout');
465
465
  const logoutRes = await fetcher(logoutUrl, {
466
466
  method: 'POST',
467
467
  headers: {
@@ -484,8 +484,8 @@
484
484
  return { response: payload };
485
485
  }
486
486
  async function authFetch(input, init = {}, options = {}) {
487
- const fetcher = resolveFetcher(options);
488
- const credentials = resolveCredentials(options);
487
+ const fetcher = resolveFetcher$1(options);
488
+ const credentials = resolveCredentials$1(options);
489
489
  const retryOnUnauthorized = options.retryOnUnauthorized !== false;
490
490
  const performRequest = async (tokenOverride) => {
491
491
  const headers = new Headers(init.headers || {});
@@ -517,6 +517,8 @@
517
517
  const DEFAULT_UCAN_TTL = 5 * 60 * 1000;
518
518
  const DB_NAME = 'yeying-web3';
519
519
  const DB_STORE = 'ucan-sessions';
520
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
521
+ const DID_KEY_ED25519_MULTICODEC = new Uint8Array([0xed, 0x01]);
520
522
  const textEncoder = new TextEncoder();
521
523
  function toBase64Url(data) {
522
524
  const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -526,6 +528,70 @@
526
528
  }
527
529
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
528
530
  }
531
+ function normalizeActionExpression(raw) {
532
+ const normalized = String(raw || '').trim().toLowerCase().replace(/\|/g, ',');
533
+ if (!normalized)
534
+ return '';
535
+ const parts = normalized
536
+ .split(',')
537
+ .map(part => part.trim())
538
+ .filter(Boolean);
539
+ if (!parts.length)
540
+ return '';
541
+ return Array.from(new Set(parts)).join(',');
542
+ }
543
+ function getCapabilityResource(cap) {
544
+ if (!cap || typeof cap !== 'object')
545
+ return '';
546
+ const withValue = typeof cap.with === 'string' ? cap.with.trim() : '';
547
+ if (withValue)
548
+ return withValue;
549
+ return typeof cap.resource === 'string' ? cap.resource.trim() : '';
550
+ }
551
+ function getCapabilityAction(cap) {
552
+ if (!cap || typeof cap !== 'object')
553
+ return '';
554
+ const canValue = typeof cap.can === 'string' ? cap.can.trim() : '';
555
+ if (canValue)
556
+ return normalizeActionExpression(canValue);
557
+ const actionValue = typeof cap.action === 'string' ? cap.action.trim() : '';
558
+ return normalizeActionExpression(actionValue);
559
+ }
560
+ function normalizeUcanCapability(cap, options = {}) {
561
+ const includeLegacyAliases = options.includeLegacyAliases !== false;
562
+ const resource = getCapabilityResource(cap);
563
+ const action = getCapabilityAction(cap);
564
+ if (!resource || !action)
565
+ return null;
566
+ const normalized = {
567
+ with: resource,
568
+ can: action,
569
+ };
570
+ if (includeLegacyAliases) {
571
+ normalized.resource = resource;
572
+ normalized.action = action;
573
+ }
574
+ if (cap && Object.prototype.hasOwnProperty.call(cap, 'nb')) {
575
+ normalized.nb = cap.nb;
576
+ }
577
+ return normalized;
578
+ }
579
+ function normalizeUcanCapabilities(caps, options = {}) {
580
+ const includeLegacyAliases = options.includeLegacyAliases !== false;
581
+ const seen = new Set();
582
+ const result = [];
583
+ for (const cap of caps || []) {
584
+ const normalized = normalizeUcanCapability(cap, { includeLegacyAliases });
585
+ if (!normalized)
586
+ continue;
587
+ const key = `${normalized.with}|${normalized.can}`;
588
+ if (seen.has(key))
589
+ continue;
590
+ seen.add(key);
591
+ result.push(normalized);
592
+ }
593
+ return result;
594
+ }
529
595
  function encodeJson(value) {
530
596
  return toBase64Url(textEncoder.encode(JSON.stringify(value)));
531
597
  }
@@ -539,6 +605,123 @@
539
605
  function normalizeExpiry(exp, fallbackMs) {
540
606
  return Date.now() + fallbackMs;
541
607
  }
608
+ function isSessionExpired(expiresAt, nowMs = Date.now()) {
609
+ return typeof expiresAt === 'number' && nowMs >= expiresAt;
610
+ }
611
+ function encodeBase58(bytes) {
612
+ if (bytes.length === 0)
613
+ return '';
614
+ let value = 0n;
615
+ for (const byte of bytes) {
616
+ value = (value << 8n) + BigInt(byte);
617
+ }
618
+ let encoded = '';
619
+ while (value > 0n) {
620
+ const mod = Number(value % 58n);
621
+ encoded = `${BASE58_ALPHABET[mod]}${encoded}`;
622
+ value /= 58n;
623
+ }
624
+ let leadingZeroCount = 0;
625
+ while (leadingZeroCount < bytes.length && bytes[leadingZeroCount] === 0) {
626
+ leadingZeroCount += 1;
627
+ }
628
+ if (leadingZeroCount > 0) {
629
+ encoded = `${'1'.repeat(leadingZeroCount)}${encoded}`;
630
+ }
631
+ return encoded || '1';
632
+ }
633
+ function ensureWebCrypto() {
634
+ if (typeof crypto === 'undefined' || !crypto.subtle) {
635
+ throw new Error('WebCrypto not available for UCAN session');
636
+ }
637
+ return crypto;
638
+ }
639
+ function parseSessionId(options) {
640
+ return options.id || DEFAULT_SESSION_ID;
641
+ }
642
+ function isLocalSessionRecord(record) {
643
+ return Boolean(record?.source === 'local' || record?.privateKeyJwk);
644
+ }
645
+ async function buildDidKey(publicKey) {
646
+ const webCrypto = ensureWebCrypto();
647
+ const raw = new Uint8Array(await webCrypto.subtle.exportKey('raw', publicKey));
648
+ const prefixed = new Uint8Array(DID_KEY_ED25519_MULTICODEC.length + raw.length);
649
+ prefixed.set(DID_KEY_ED25519_MULTICODEC, 0);
650
+ prefixed.set(raw, DID_KEY_ED25519_MULTICODEC.length);
651
+ return `did:key:z${encodeBase58(prefixed)}`;
652
+ }
653
+ async function importLocalPrivateKey(privateKeyJwk) {
654
+ const webCrypto = ensureWebCrypto();
655
+ return await webCrypto.subtle.importKey('jwk', privateKeyJwk, 'Ed25519', true, ['sign']);
656
+ }
657
+ async function loadLocalSessionFromRecord(id, record) {
658
+ if (!record || !isLocalSessionRecord(record) || !record.privateKeyJwk) {
659
+ return null;
660
+ }
661
+ if (isSessionExpired(record.expiresAt)) {
662
+ await deleteSessionRecord(id);
663
+ return null;
664
+ }
665
+ try {
666
+ const privateKey = await importLocalPrivateKey(record.privateKeyJwk);
667
+ return {
668
+ id: record.id || id,
669
+ did: record.did,
670
+ createdAt: record.createdAt,
671
+ expiresAt: record.expiresAt,
672
+ source: 'local',
673
+ privateKey,
674
+ };
675
+ }
676
+ catch {
677
+ return null;
678
+ }
679
+ }
680
+ function shouldKeepRootForSession(root, did, nowMs) {
681
+ if (!root)
682
+ return false;
683
+ if (root.aud && root.aud !== did)
684
+ return false;
685
+ if (isRootExpired(root, nowMs))
686
+ return false;
687
+ return true;
688
+ }
689
+ async function createLocalSession(options, record) {
690
+ const webCrypto = ensureWebCrypto();
691
+ const sessionId = parseSessionId(options);
692
+ if (!options.forceNew) {
693
+ const existing = await loadLocalSessionFromRecord(sessionId, record);
694
+ if (existing)
695
+ return existing;
696
+ }
697
+ const pair = (await webCrypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']));
698
+ const [privateKeyJwk, publicKeyJwk, did] = await Promise.all([
699
+ webCrypto.subtle.exportKey('jwk', pair.privateKey),
700
+ webCrypto.subtle.exportKey('jwk', pair.publicKey),
701
+ buildDidKey(pair.publicKey),
702
+ ]);
703
+ const createdAt = Date.now();
704
+ const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
705
+ const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
706
+ await writeSessionRecord({
707
+ id: sessionId,
708
+ did,
709
+ createdAt,
710
+ expiresAt,
711
+ source: 'local',
712
+ privateKeyJwk,
713
+ publicKeyJwk,
714
+ root,
715
+ });
716
+ return {
717
+ id: sessionId,
718
+ did,
719
+ createdAt,
720
+ expiresAt,
721
+ source: 'local',
722
+ privateKey: pair.privateKey,
723
+ };
724
+ }
542
725
  function openDb() {
543
726
  if (typeof indexedDB === 'undefined') {
544
727
  return Promise.reject(new Error('IndexedDB not available'));
@@ -601,17 +784,19 @@
601
784
  }
602
785
  }
603
786
  async function getUcanSession(id = DEFAULT_SESSION_ID, provider) {
787
+ const record = await readSessionRecord(id);
604
788
  const walletProvider = provider || (typeof window !== 'undefined'
605
789
  ? await getProvider({ preferYeYing: true })
606
790
  : null);
607
- if (!walletProvider)
608
- return null;
609
- try {
610
- return await requestWalletUcanSession(walletProvider, { id });
611
- }
612
- catch {
613
- return null;
791
+ if (walletProvider) {
792
+ try {
793
+ return await requestWalletUcanSession(walletProvider, { id });
794
+ }
795
+ catch {
796
+ return await loadLocalSessionFromRecord(id, record);
797
+ }
614
798
  }
799
+ return await loadLocalSessionFromRecord(id, record);
615
800
  }
616
801
  async function requestWalletUcanSession(provider, options) {
617
802
  const sessionId = options.id || DEFAULT_SESSION_ID;
@@ -636,6 +821,7 @@
636
821
  did: result.did,
637
822
  createdAt,
638
823
  expiresAt,
824
+ source: 'wallet',
639
825
  root: existing?.root,
640
826
  };
641
827
  if (nextRecord.root && nextRecord.root.aud && nextRecord.root.aud !== nextRecord.did) {
@@ -647,6 +833,7 @@
647
833
  did: result.did,
648
834
  createdAt,
649
835
  expiresAt,
836
+ source: 'wallet',
650
837
  signer: async (signingInput, payload) => {
651
838
  const signatureResult = (await provider.request({
652
839
  method: 'yeying_ucan_sign',
@@ -669,13 +856,20 @@
669
856
  };
670
857
  }
671
858
  async function createUcanSession(options = {}) {
859
+ const sessionId = parseSessionId(options);
860
+ const record = await readSessionRecord(sessionId);
672
861
  const provider = options.provider || (typeof window !== 'undefined'
673
862
  ? await getProvider({ preferYeYing: true })
674
863
  : null);
675
- if (!provider) {
676
- throw new Error('No wallet provider for UCAN session');
864
+ if (provider) {
865
+ try {
866
+ return await requestWalletUcanSession(provider, { ...options, id: sessionId });
867
+ }
868
+ catch {
869
+ // fallback to local ed25519 session
870
+ }
677
871
  }
678
- return await requestWalletUcanSession(provider, options);
872
+ return await createLocalSession({ ...options, id: sessionId }, record);
679
873
  }
680
874
  async function clearUcanSession(id = DEFAULT_SESSION_ID) {
681
875
  await deleteSessionRecord(id);
@@ -686,6 +880,7 @@
686
880
  const expiresAt = record?.expiresAt ?? null;
687
881
  const did = record?.did || root.aud;
688
882
  const nextRecord = {
883
+ ...(record || {}),
689
884
  id,
690
885
  did,
691
886
  createdAt,
@@ -699,7 +894,9 @@
699
894
  return record?.root || null;
700
895
  }
701
896
  function capsEqual(a, b) {
702
- return JSON.stringify(a || []) === JSON.stringify(b || []);
897
+ const left = normalizeUcanCapabilities(a, { includeLegacyAliases: false });
898
+ const right = normalizeUcanCapabilities(b, { includeLegacyAliases: false });
899
+ return JSON.stringify(left) === JSON.stringify(right);
703
900
  }
704
901
  function isRootExpired(root, nowMs) {
705
902
  return Boolean(root.exp && nowMs > root.exp);
@@ -774,9 +971,13 @@
774
971
  const nonce = options.nonce || randomNonce(8);
775
972
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
776
973
  const nbf = options.notBeforeMs;
974
+ const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
975
+ if (!normalizedCapabilities.length) {
976
+ throw new Error('Missing UCAN capabilities');
977
+ }
777
978
  const statementPayload = {
778
979
  aud: session.did,
779
- cap: options.capabilities,
980
+ cap: normalizedCapabilities,
780
981
  exp,
781
982
  };
782
983
  if (nbf)
@@ -799,7 +1000,7 @@
799
1000
  type: 'siwe',
800
1001
  iss: `did:pkh:eth:${address.toLowerCase()}`,
801
1002
  aud: session.did,
802
- cap: options.capabilities,
1003
+ cap: normalizedCapabilities,
803
1004
  exp,
804
1005
  nbf,
805
1006
  siwe: {
@@ -848,11 +1049,15 @@
848
1049
  }));
849
1050
  if (!issuer)
850
1051
  throw new Error('Missing UCAN session key');
1052
+ const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1053
+ if (!normalizedCapabilities.length) {
1054
+ throw new Error('Missing UCAN capabilities');
1055
+ }
851
1056
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
852
1057
  const payload = {
853
1058
  iss: issuer.did,
854
1059
  aud: options.audience,
855
- cap: options.capabilities,
1060
+ cap: normalizedCapabilities,
856
1061
  exp,
857
1062
  nbf: options.notBeforeMs,
858
1063
  prf: await resolveProofs(options, issuer),
@@ -866,11 +1071,15 @@
866
1071
  }));
867
1072
  if (!issuer)
868
1073
  throw new Error('Missing UCAN session key');
1074
+ const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1075
+ if (!normalizedCapabilities.length) {
1076
+ throw new Error('Missing UCAN capabilities');
1077
+ }
869
1078
  const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
870
1079
  const payload = {
871
1080
  iss: issuer.did,
872
1081
  aud: options.audience,
873
- cap: options.capabilities,
1082
+ cap: normalizedCapabilities,
874
1083
  exp,
875
1084
  nbf: options.notBeforeMs,
876
1085
  prf: await resolveProofs(options, issuer),
@@ -903,6 +1112,291 @@
903
1112
  });
904
1113
  }
905
1114
 
1115
+ const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
1116
+ const DEFAULT_ISSUER_PATH = 'issuer';
1117
+ const DEFAULT_SESSION_PATH = 'session';
1118
+ const DEFAULT_ISSUE_PATH = 'issue';
1119
+ const DEFAULT_SESSION_TOKEN_KEY = 'centralUcanSessionToken';
1120
+ let cachedCentralSessionToken = null;
1121
+ function normalizeBaseUrl$1(baseUrl) {
1122
+ return baseUrl.replace(/\/+$/, '');
1123
+ }
1124
+ function joinUrl$1(baseUrl, path) {
1125
+ const trimmed = path.replace(/^\/+/, '');
1126
+ return `${normalizeBaseUrl$1(baseUrl)}/${trimmed}`;
1127
+ }
1128
+ function resolveBaseUrl(options) {
1129
+ return options?.baseUrl || DEFAULT_BASE_URL;
1130
+ }
1131
+ function resolveFetcher(options) {
1132
+ return options?.fetcher || fetch;
1133
+ }
1134
+ function resolveCredentials(options) {
1135
+ return options?.credentials ?? 'include';
1136
+ }
1137
+ function resolveSessionTokenKey(options) {
1138
+ return options?.sessionTokenStorageKey || DEFAULT_SESSION_TOKEN_KEY;
1139
+ }
1140
+ function shouldStoreSessionToken(options) {
1141
+ return options?.storeSessionToken !== false;
1142
+ }
1143
+ function parseObject(value) {
1144
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1145
+ return {};
1146
+ }
1147
+ return value;
1148
+ }
1149
+ function parseEnvelopeData(payload) {
1150
+ const root = parseObject(payload);
1151
+ if (Object.prototype.hasOwnProperty.call(root, 'data')) {
1152
+ return parseObject(root.data);
1153
+ }
1154
+ return root;
1155
+ }
1156
+ function parseStringField(obj, keys) {
1157
+ for (const key of keys) {
1158
+ const value = obj[key];
1159
+ if (typeof value === 'string') {
1160
+ return value;
1161
+ }
1162
+ }
1163
+ return undefined;
1164
+ }
1165
+ function parseNumberField(obj, keys) {
1166
+ for (const key of keys) {
1167
+ const value = obj[key];
1168
+ if (typeof value === 'number' && Number.isFinite(value)) {
1169
+ return value;
1170
+ }
1171
+ }
1172
+ return undefined;
1173
+ }
1174
+ function parseCapabilitiesField(obj, keys) {
1175
+ for (const key of keys) {
1176
+ const value = obj[key];
1177
+ if (!Array.isArray(value))
1178
+ continue;
1179
+ const caps = value
1180
+ .filter(item => item && typeof item === 'object')
1181
+ .map(item => normalizeUcanCapability(item))
1182
+ .filter((cap) => Boolean(cap));
1183
+ return caps;
1184
+ }
1185
+ return undefined;
1186
+ }
1187
+ function readStoredSessionToken(options) {
1188
+ if (!shouldStoreSessionToken(options))
1189
+ return null;
1190
+ if (typeof localStorage === 'undefined')
1191
+ return null;
1192
+ const key = resolveSessionTokenKey(options);
1193
+ return localStorage.getItem(key);
1194
+ }
1195
+ function persistSessionToken(token, options) {
1196
+ cachedCentralSessionToken = token;
1197
+ if (!shouldStoreSessionToken(options))
1198
+ return;
1199
+ if (typeof localStorage === 'undefined')
1200
+ return;
1201
+ const key = resolveSessionTokenKey(options);
1202
+ if (!token) {
1203
+ localStorage.removeItem(key);
1204
+ }
1205
+ else {
1206
+ localStorage.setItem(key, token);
1207
+ }
1208
+ }
1209
+ async function parseJsonBody(response) {
1210
+ const text = await response.text();
1211
+ if (!text)
1212
+ return null;
1213
+ try {
1214
+ return JSON.parse(text);
1215
+ }
1216
+ catch {
1217
+ return { raw: text };
1218
+ }
1219
+ }
1220
+ function getCentralSessionToken(options) {
1221
+ if (cachedCentralSessionToken)
1222
+ return cachedCentralSessionToken;
1223
+ const stored = readStoredSessionToken(options);
1224
+ if (stored) {
1225
+ cachedCentralSessionToken = stored;
1226
+ }
1227
+ return stored;
1228
+ }
1229
+ function setCentralSessionToken(token, options) {
1230
+ persistSessionToken(token, options);
1231
+ }
1232
+ function clearCentralSessionToken(options) {
1233
+ cachedCentralSessionToken = null;
1234
+ if (typeof localStorage === 'undefined')
1235
+ return;
1236
+ const key = resolveSessionTokenKey(options);
1237
+ localStorage.removeItem(key);
1238
+ }
1239
+ async function getCentralIssuerInfo(options = {}) {
1240
+ const fetcher = resolveFetcher(options);
1241
+ const credentials = resolveCredentials(options);
1242
+ const url = joinUrl$1(resolveBaseUrl(options), options.issuerPath || DEFAULT_ISSUER_PATH);
1243
+ const response = await fetcher(url, {
1244
+ method: 'GET',
1245
+ headers: {
1246
+ accept: 'application/json',
1247
+ },
1248
+ credentials,
1249
+ });
1250
+ const payload = await parseJsonBody(response);
1251
+ if (!response.ok) {
1252
+ throw new Error(`Central issuer request failed: ${response.status} ${JSON.stringify(payload)}`);
1253
+ }
1254
+ const data = parseEnvelopeData(payload);
1255
+ return {
1256
+ enabled: typeof data.enabled === 'boolean' ? data.enabled : undefined,
1257
+ issuerDid: parseStringField(data, ['issuerDid']),
1258
+ defaultAudience: parseStringField(data, ['defaultAudience']),
1259
+ defaultCapabilities: parseCapabilitiesField(data, ['defaultCapabilities']),
1260
+ response: payload,
1261
+ };
1262
+ }
1263
+ async function createCentralSession(options) {
1264
+ const subject = String(options?.subject || '').trim();
1265
+ if (!subject) {
1266
+ throw new Error('Missing subject');
1267
+ }
1268
+ const fetcher = resolveFetcher(options);
1269
+ const credentials = resolveCredentials(options);
1270
+ const url = joinUrl$1(resolveBaseUrl(options), options.sessionPath || DEFAULT_SESSION_PATH);
1271
+ const response = await fetcher(url, {
1272
+ method: 'POST',
1273
+ headers: {
1274
+ 'Content-Type': 'application/json',
1275
+ accept: 'application/json',
1276
+ },
1277
+ credentials,
1278
+ body: JSON.stringify({
1279
+ subject,
1280
+ sessionTtlMs: options.sessionTtlMs,
1281
+ }),
1282
+ });
1283
+ const payload = await parseJsonBody(response);
1284
+ if (!response.ok) {
1285
+ throw new Error(`Central session request failed: ${response.status} ${JSON.stringify(payload)}`);
1286
+ }
1287
+ const data = parseEnvelopeData(payload);
1288
+ const sessionToken = parseStringField(data, ['sessionToken']);
1289
+ if (!sessionToken) {
1290
+ throw new Error('Central session response missing sessionToken');
1291
+ }
1292
+ persistSessionToken(sessionToken, options);
1293
+ return {
1294
+ subject: parseStringField(data, ['subject']) || subject,
1295
+ sessionToken,
1296
+ expiresAt: parseNumberField(data, ['expiresAt']),
1297
+ issuerDid: parseStringField(data, ['issuerDid']),
1298
+ response: payload,
1299
+ };
1300
+ }
1301
+ async function issueCentralUcan(options = {}) {
1302
+ const sessionToken = options.sessionToken || getCentralSessionToken(options);
1303
+ if (!sessionToken) {
1304
+ throw new Error('Missing central session token');
1305
+ }
1306
+ const normalizedCapabilities = options.capabilities
1307
+ ? normalizeUcanCapabilities(options.capabilities)
1308
+ : undefined;
1309
+ const fetcher = resolveFetcher(options);
1310
+ const credentials = resolveCredentials(options);
1311
+ const url = joinUrl$1(resolveBaseUrl(options), options.issuePath || DEFAULT_ISSUE_PATH);
1312
+ const response = await fetcher(url, {
1313
+ method: 'POST',
1314
+ headers: {
1315
+ 'Content-Type': 'application/json',
1316
+ accept: 'application/json',
1317
+ Authorization: `Bearer ${sessionToken}`,
1318
+ },
1319
+ credentials,
1320
+ body: JSON.stringify({
1321
+ audience: options.audience,
1322
+ capabilities: normalizedCapabilities,
1323
+ expiresInMs: options.expiresInMs,
1324
+ ttlMs: options.ttlMs,
1325
+ }),
1326
+ });
1327
+ const payload = await parseJsonBody(response);
1328
+ if (!response.ok) {
1329
+ throw new Error(`Central UCAN issue failed: ${response.status} ${JSON.stringify(payload)}`);
1330
+ }
1331
+ const data = parseEnvelopeData(payload);
1332
+ const ucan = parseStringField(data, ['ucan']);
1333
+ if (!ucan) {
1334
+ throw new Error('Central UCAN response missing ucan');
1335
+ }
1336
+ return {
1337
+ ucan,
1338
+ issuerDid: parseStringField(data, ['issuerDid']),
1339
+ subject: parseStringField(data, ['subject']),
1340
+ audience: parseStringField(data, ['audience']),
1341
+ capabilities: parseCapabilitiesField(data, ['capabilities']),
1342
+ exp: parseNumberField(data, ['exp']),
1343
+ nbf: parseNumberField(data, ['nbf']),
1344
+ iat: parseNumberField(data, ['iat']),
1345
+ response: payload,
1346
+ };
1347
+ }
1348
+ async function createAndIssueCentralUcan(options) {
1349
+ const session = await createCentralSession({
1350
+ ...options,
1351
+ subject: options.subject,
1352
+ sessionTtlMs: options.sessionTtlMs,
1353
+ });
1354
+ const issue = await issueCentralUcan({
1355
+ ...options,
1356
+ sessionToken: session.sessionToken,
1357
+ audience: options.audience,
1358
+ capabilities: options.capabilities,
1359
+ expiresInMs: options.expiresInMs,
1360
+ ttlMs: options.ttlMs,
1361
+ });
1362
+ return { session, issue };
1363
+ }
1364
+ async function authCentralUcanFetch(input, init = {}, options = {}) {
1365
+ const fetcher = resolveFetcher(options);
1366
+ const credentials = resolveCredentials(options);
1367
+ let token = options.ucan || null;
1368
+ if (!token) {
1369
+ let sessionToken = options.sessionToken || getCentralSessionToken(options);
1370
+ if (!sessionToken) {
1371
+ if (!options.subject) {
1372
+ throw new Error('Missing central session token or subject');
1373
+ }
1374
+ const session = await createCentralSession({
1375
+ ...options,
1376
+ subject: options.subject,
1377
+ sessionTtlMs: options.sessionTtlMs,
1378
+ });
1379
+ sessionToken = session.sessionToken;
1380
+ }
1381
+ const issued = await issueCentralUcan({
1382
+ ...options,
1383
+ sessionToken,
1384
+ audience: options.audience,
1385
+ capabilities: options.capabilities,
1386
+ expiresInMs: options.expiresInMs,
1387
+ ttlMs: options.ttlMs,
1388
+ });
1389
+ token = issued.ucan;
1390
+ }
1391
+ const headers = new Headers(init.headers || {});
1392
+ headers.set('Authorization', `Bearer ${token}`);
1393
+ return fetcher(input, {
1394
+ ...init,
1395
+ headers,
1396
+ credentials,
1397
+ });
1398
+ }
1399
+
906
1400
  function normalizeBaseUrl(baseUrl) {
907
1401
  return baseUrl.replace(/\/+$/, '');
908
1402
  }
@@ -1139,25 +1633,19 @@
1139
1633
  if (!options.appId)
1140
1634
  return null;
1141
1635
  const action = normalizeAction(options.appAction) || DEFAULT_APP_ACTION;
1636
+ const resource = `app:all:${sanitizeAppId(options.appId)}`;
1142
1637
  return {
1143
- resource: `app:${sanitizeAppId(options.appId)}`,
1638
+ with: resource,
1639
+ can: action,
1640
+ resource,
1144
1641
  action,
1145
1642
  };
1146
1643
  }
1147
1644
  function hasAppCapability(caps) {
1148
- return (caps || []).some(cap => typeof cap?.resource === 'string' && cap.resource.startsWith('app:'));
1645
+ return (caps || []).some(cap => getCapabilityResource(cap).startsWith('app:'));
1149
1646
  }
1150
1647
  function dedupeCapabilities(caps) {
1151
- const seen = new Set();
1152
- return (caps || []).filter(cap => {
1153
- if (!cap || !cap.resource || !cap.action)
1154
- return false;
1155
- const key = `${cap.resource}|${cap.action}`;
1156
- if (seen.has(key))
1157
- return false;
1158
- seen.add(key);
1159
- return true;
1160
- });
1648
+ return normalizeUcanCapabilities(caps);
1161
1649
  }
1162
1650
  function ensureAppCapability(caps, options) {
1163
1651
  const appCap = buildAppCapability(options);
@@ -1177,7 +1665,13 @@
1177
1665
  return undefined;
1178
1666
  }
1179
1667
  function buildCapsKey(caps) {
1180
- return JSON.stringify(caps || []);
1668
+ const canonical = (caps || [])
1669
+ .map(cap => ({
1670
+ with: getCapabilityResource(cap),
1671
+ can: getCapabilityAction(cap),
1672
+ }))
1673
+ .filter(cap => Boolean(cap.with && cap.can));
1674
+ return JSON.stringify(canonical);
1181
1675
  }
1182
1676
  function buildTokenCacheKey(issuer, audience, caps) {
1183
1677
  return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
@@ -1355,10 +1849,14 @@
1355
1849
  }
1356
1850
 
1357
1851
  exports.WebDavClient = WebDavClient;
1852
+ exports.authCentralUcanFetch = authCentralUcanFetch;
1358
1853
  exports.authFetch = authFetch;
1359
1854
  exports.authUcanFetch = authUcanFetch;
1360
1855
  exports.clearAccessToken = clearAccessToken;
1856
+ exports.clearCentralSessionToken = clearCentralSessionToken;
1361
1857
  exports.clearUcanSession = clearUcanSession;
1858
+ exports.createAndIssueCentralUcan = createAndIssueCentralUcan;
1859
+ exports.createCentralSession = createCentralSession;
1362
1860
  exports.createDelegationUcan = createDelegationUcan;
1363
1861
  exports.createInvocationUcan = createInvocationUcan;
1364
1862
  exports.createRootUcan = createRootUcan;
@@ -1367,6 +1865,10 @@
1367
1865
  exports.getAccessToken = getAccessToken;
1368
1866
  exports.getAccounts = getAccounts;
1369
1867
  exports.getBalance = getBalance;
1868
+ exports.getCapabilityAction = getCapabilityAction;
1869
+ exports.getCapabilityResource = getCapabilityResource;
1870
+ exports.getCentralIssuerInfo = getCentralIssuerInfo;
1871
+ exports.getCentralSessionToken = getCentralSessionToken;
1370
1872
  exports.getChainId = getChainId;
1371
1873
  exports.getOrCreateUcanRoot = getOrCreateUcanRoot;
1372
1874
  exports.getPreferredAccount = getPreferredAccount;
@@ -1376,14 +1878,18 @@
1376
1878
  exports.initDappSession = initDappSession;
1377
1879
  exports.initWebDavStorage = initWebDavStorage;
1378
1880
  exports.isYeYingProvider = isYeYingProvider;
1881
+ exports.issueCentralUcan = issueCentralUcan;
1379
1882
  exports.loginWithChallenge = loginWithChallenge;
1380
1883
  exports.logout = logout;
1884
+ exports.normalizeUcanCapabilities = normalizeUcanCapabilities;
1885
+ exports.normalizeUcanCapability = normalizeUcanCapability;
1381
1886
  exports.onAccountsChanged = onAccountsChanged;
1382
1887
  exports.onChainChanged = onChainChanged;
1383
1888
  exports.refreshAccessToken = refreshAccessToken;
1384
1889
  exports.requestAccounts = requestAccounts;
1385
1890
  exports.requireProvider = requireProvider;
1386
1891
  exports.setAccessToken = setAccessToken;
1892
+ exports.setCentralSessionToken = setCentralSessionToken;
1387
1893
  exports.signMessage = signMessage;
1388
1894
  exports.storeUcanRoot = storeUcanRoot;
1389
1895
  exports.watchAccounts = watchAccounts;