@yeying-community/web3-bs 1.0.5 → 1.0.7

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,314 @@
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 resolveAccessToken(options) {
1141
+ if (typeof options?.accessToken === 'string') {
1142
+ const token = options.accessToken.trim();
1143
+ return token || null;
1144
+ }
1145
+ if (options?.accessToken === null) {
1146
+ return null;
1147
+ }
1148
+ return getAccessToken(options);
1149
+ }
1150
+ function shouldStoreSessionToken(options) {
1151
+ return options?.storeSessionToken !== false;
1152
+ }
1153
+ function parseObject(value) {
1154
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1155
+ return {};
1156
+ }
1157
+ return value;
1158
+ }
1159
+ function parseEnvelopeData(payload) {
1160
+ const root = parseObject(payload);
1161
+ if (Object.prototype.hasOwnProperty.call(root, 'data')) {
1162
+ return parseObject(root.data);
1163
+ }
1164
+ return root;
1165
+ }
1166
+ function parseStringField(obj, keys) {
1167
+ for (const key of keys) {
1168
+ const value = obj[key];
1169
+ if (typeof value === 'string') {
1170
+ return value;
1171
+ }
1172
+ }
1173
+ return undefined;
1174
+ }
1175
+ function parseNumberField(obj, keys) {
1176
+ for (const key of keys) {
1177
+ const value = obj[key];
1178
+ if (typeof value === 'number' && Number.isFinite(value)) {
1179
+ return value;
1180
+ }
1181
+ }
1182
+ return undefined;
1183
+ }
1184
+ function parseCapabilitiesField(obj, keys) {
1185
+ for (const key of keys) {
1186
+ const value = obj[key];
1187
+ if (!Array.isArray(value))
1188
+ continue;
1189
+ const caps = value
1190
+ .filter(item => item && typeof item === 'object')
1191
+ .map(item => normalizeUcanCapability(item))
1192
+ .filter((cap) => Boolean(cap));
1193
+ return caps;
1194
+ }
1195
+ return undefined;
1196
+ }
1197
+ function parseIssuerDidField(obj) {
1198
+ return parseStringField(obj, ['issuerDid', 'issuer', 'did']);
1199
+ }
1200
+ function readStoredSessionToken(options) {
1201
+ if (!shouldStoreSessionToken(options))
1202
+ return null;
1203
+ if (typeof localStorage === 'undefined')
1204
+ return null;
1205
+ const key = resolveSessionTokenKey(options);
1206
+ return localStorage.getItem(key);
1207
+ }
1208
+ function persistSessionToken(token, options) {
1209
+ cachedCentralSessionToken = token;
1210
+ if (!shouldStoreSessionToken(options))
1211
+ return;
1212
+ if (typeof localStorage === 'undefined')
1213
+ return;
1214
+ const key = resolveSessionTokenKey(options);
1215
+ if (!token) {
1216
+ localStorage.removeItem(key);
1217
+ }
1218
+ else {
1219
+ localStorage.setItem(key, token);
1220
+ }
1221
+ }
1222
+ async function parseJsonBody(response) {
1223
+ const text = await response.text();
1224
+ if (!text)
1225
+ return null;
1226
+ try {
1227
+ return JSON.parse(text);
1228
+ }
1229
+ catch {
1230
+ return { raw: text };
1231
+ }
1232
+ }
1233
+ function getCentralSessionToken(options) {
1234
+ if (cachedCentralSessionToken)
1235
+ return cachedCentralSessionToken;
1236
+ const stored = readStoredSessionToken(options);
1237
+ if (stored) {
1238
+ cachedCentralSessionToken = stored;
1239
+ }
1240
+ return stored;
1241
+ }
1242
+ function setCentralSessionToken(token, options) {
1243
+ persistSessionToken(token, options);
1244
+ }
1245
+ function clearCentralSessionToken(options) {
1246
+ cachedCentralSessionToken = null;
1247
+ if (typeof localStorage === 'undefined')
1248
+ return;
1249
+ const key = resolveSessionTokenKey(options);
1250
+ localStorage.removeItem(key);
1251
+ }
1252
+ async function getCentralIssuerInfo(options = {}) {
1253
+ const fetcher = resolveFetcher(options);
1254
+ const credentials = resolveCredentials(options);
1255
+ const url = joinUrl$1(resolveBaseUrl(options), options.issuerPath || DEFAULT_ISSUER_PATH);
1256
+ const token = resolveAccessToken(options);
1257
+ const headers = new Headers({
1258
+ accept: 'application/json',
1259
+ });
1260
+ if (token) {
1261
+ headers.set('Authorization', `Bearer ${token}`);
1262
+ }
1263
+ const response = await fetcher(url, {
1264
+ method: 'GET',
1265
+ headers,
1266
+ credentials,
1267
+ });
1268
+ const payload = await parseJsonBody(response);
1269
+ if (!response.ok) {
1270
+ throw new Error(`Central issuer request failed: ${response.status} ${JSON.stringify(payload)}`);
1271
+ }
1272
+ const data = parseEnvelopeData(payload);
1273
+ return {
1274
+ enabled: typeof data.enabled === 'boolean' ? data.enabled : undefined,
1275
+ issuerDid: parseIssuerDidField(data),
1276
+ defaultAudience: parseStringField(data, ['defaultAudience']),
1277
+ defaultCapabilities: parseCapabilitiesField(data, ['defaultCapabilities']),
1278
+ response: payload,
1279
+ };
1280
+ }
1281
+ async function createCentralSession(options) {
1282
+ const subject = String(options?.subject || '').trim();
1283
+ if (!subject) {
1284
+ throw new Error('Missing subject');
1285
+ }
1286
+ const fetcher = resolveFetcher(options);
1287
+ const credentials = resolveCredentials(options);
1288
+ const accessToken = resolveAccessToken(options);
1289
+ const url = joinUrl$1(resolveBaseUrl(options), options.sessionPath || DEFAULT_SESSION_PATH);
1290
+ const headers = new Headers({
1291
+ 'Content-Type': 'application/json',
1292
+ accept: 'application/json',
1293
+ });
1294
+ if (accessToken) {
1295
+ headers.set('Authorization', `Bearer ${accessToken}`);
1296
+ }
1297
+ const response = await fetcher(url, {
1298
+ method: 'POST',
1299
+ headers,
1300
+ credentials,
1301
+ body: JSON.stringify({
1302
+ subject,
1303
+ sessionTtlMs: options.sessionTtlMs,
1304
+ }),
1305
+ });
1306
+ const payload = await parseJsonBody(response);
1307
+ if (!response.ok) {
1308
+ throw new Error(`Central session request failed: ${response.status} ${JSON.stringify(payload)}`);
1309
+ }
1310
+ const data = parseEnvelopeData(payload);
1311
+ const sessionToken = parseStringField(data, ['sessionToken']);
1312
+ if (!sessionToken) {
1313
+ throw new Error('Central session response missing sessionToken');
1314
+ }
1315
+ persistSessionToken(sessionToken, options);
1316
+ return {
1317
+ subject: parseStringField(data, ['subject']) || subject,
1318
+ sessionToken,
1319
+ expiresAt: parseNumberField(data, ['expiresAt']),
1320
+ issuerDid: parseIssuerDidField(data),
1321
+ response: payload,
1322
+ };
1323
+ }
1324
+ async function issueCentralUcan(options = {}) {
1325
+ const sessionToken = options.sessionToken || getCentralSessionToken(options);
1326
+ if (!sessionToken) {
1327
+ throw new Error('Missing central session token');
1328
+ }
1329
+ const normalizedCapabilities = options.capabilities
1330
+ ? normalizeUcanCapabilities(options.capabilities)
1331
+ : undefined;
1332
+ const fetcher = resolveFetcher(options);
1333
+ const credentials = resolveCredentials(options);
1334
+ const url = joinUrl$1(resolveBaseUrl(options), options.issuePath || DEFAULT_ISSUE_PATH);
1335
+ const response = await fetcher(url, {
1336
+ method: 'POST',
1337
+ headers: {
1338
+ 'Content-Type': 'application/json',
1339
+ accept: 'application/json',
1340
+ Authorization: `Bearer ${sessionToken}`,
1341
+ },
1342
+ credentials,
1343
+ body: JSON.stringify({
1344
+ audience: options.audience,
1345
+ capabilities: normalizedCapabilities,
1346
+ expiresInMs: options.expiresInMs,
1347
+ ttlMs: options.ttlMs,
1348
+ }),
1349
+ });
1350
+ const payload = await parseJsonBody(response);
1351
+ if (!response.ok) {
1352
+ throw new Error(`Central UCAN issue failed: ${response.status} ${JSON.stringify(payload)}`);
1353
+ }
1354
+ const data = parseEnvelopeData(payload);
1355
+ const ucan = parseStringField(data, ['ucan']);
1356
+ if (!ucan) {
1357
+ throw new Error('Central UCAN response missing ucan');
1358
+ }
1359
+ return {
1360
+ ucan,
1361
+ issuerDid: parseIssuerDidField(data),
1362
+ subject: parseStringField(data, ['subject']),
1363
+ audience: parseStringField(data, ['audience']),
1364
+ capabilities: parseCapabilitiesField(data, ['capabilities']),
1365
+ exp: parseNumberField(data, ['exp', 'expiresAt']),
1366
+ nbf: parseNumberField(data, ['nbf', 'notBefore']),
1367
+ iat: parseNumberField(data, ['iat', 'issuedAt']),
1368
+ response: payload,
1369
+ };
1370
+ }
1371
+ async function createAndIssueCentralUcan(options) {
1372
+ const session = await createCentralSession({
1373
+ ...options,
1374
+ subject: options.subject,
1375
+ sessionTtlMs: options.sessionTtlMs,
1376
+ });
1377
+ const issue = await issueCentralUcan({
1378
+ ...options,
1379
+ sessionToken: session.sessionToken,
1380
+ audience: options.audience,
1381
+ capabilities: options.capabilities,
1382
+ expiresInMs: options.expiresInMs,
1383
+ ttlMs: options.ttlMs,
1384
+ });
1385
+ return { session, issue };
1386
+ }
1387
+ async function authCentralUcanFetch(input, init = {}, options = {}) {
1388
+ const fetcher = resolveFetcher(options);
1389
+ const credentials = resolveCredentials(options);
1390
+ let token = options.ucan || null;
1391
+ if (!token) {
1392
+ let sessionToken = options.sessionToken || getCentralSessionToken(options);
1393
+ if (!sessionToken) {
1394
+ if (!options.subject) {
1395
+ throw new Error('Missing central session token or subject');
1396
+ }
1397
+ const session = await createCentralSession({
1398
+ ...options,
1399
+ subject: options.subject,
1400
+ sessionTtlMs: options.sessionTtlMs,
1401
+ });
1402
+ sessionToken = session.sessionToken;
1403
+ }
1404
+ const issued = await issueCentralUcan({
1405
+ ...options,
1406
+ sessionToken,
1407
+ audience: options.audience,
1408
+ capabilities: options.capabilities,
1409
+ expiresInMs: options.expiresInMs,
1410
+ ttlMs: options.ttlMs,
1411
+ });
1412
+ token = issued.ucan;
1413
+ }
1414
+ const headers = new Headers(init.headers || {});
1415
+ headers.set('Authorization', `Bearer ${token}`);
1416
+ return fetcher(input, {
1417
+ ...init,
1418
+ headers,
1419
+ credentials,
1420
+ });
1421
+ }
1422
+
906
1423
  function normalizeBaseUrl(baseUrl) {
907
1424
  return baseUrl.replace(/\/+$/, '');
908
1425
  }
@@ -1120,6 +1637,13 @@
1120
1637
  const tokenCache = new Map();
1121
1638
  const TOKEN_SKEW_MS = 5000;
1122
1639
  const DEFAULT_APP_ACTION = 'write';
1640
+ const LOOPBACK_HOST_ALIASES = new Set([
1641
+ 'localhost',
1642
+ '127.0.0.1',
1643
+ '::1',
1644
+ '0:0:0:0:0:0:0:1',
1645
+ '0.0.0.0',
1646
+ ]);
1123
1647
  function normalizeAppDir(path) {
1124
1648
  const trimmed = path.trim();
1125
1649
  if (!trimmed)
@@ -1131,6 +1655,69 @@
1131
1655
  function sanitizeAppId(appId) {
1132
1656
  return appId.trim().replace(/[^a-zA-Z0-9._-]/g, '-');
1133
1657
  }
1658
+ function parseHostPort(rawHost) {
1659
+ const host = rawHost.trim();
1660
+ if (!host)
1661
+ return { hostname: '', port: '' };
1662
+ const bracketMatch = host.match(/^\[([^\]]+)\](?::([0-9]+))?$/);
1663
+ if (bracketMatch) {
1664
+ return {
1665
+ hostname: bracketMatch[1] || '',
1666
+ port: bracketMatch[2] || '',
1667
+ };
1668
+ }
1669
+ const firstColon = host.indexOf(':');
1670
+ const lastColon = host.lastIndexOf(':');
1671
+ if (firstColon > -1 && firstColon === lastColon) {
1672
+ const hostname = host.slice(0, firstColon).trim();
1673
+ const port = host.slice(firstColon + 1).trim();
1674
+ if (/^[0-9]+$/.test(port)) {
1675
+ return { hostname, port };
1676
+ }
1677
+ }
1678
+ return { hostname: host, port: '' };
1679
+ }
1680
+ function normalizeAppHostnameForAppId(hostname) {
1681
+ const normalized = (hostname || '').trim().toLowerCase();
1682
+ if (!normalized)
1683
+ return '';
1684
+ const bare = normalized.replace(/^\[(.*)\]$/, '$1');
1685
+ if (LOOPBACK_HOST_ALIASES.has(normalized) || LOOPBACK_HOST_ALIASES.has(bare)) {
1686
+ return 'localhost';
1687
+ }
1688
+ return bare;
1689
+ }
1690
+ function buildSanitizedAppId(hostname, port) {
1691
+ const normalizedHostname = normalizeAppHostnameForAppId(hostname);
1692
+ if (!normalizedHostname)
1693
+ return '';
1694
+ const normalizedPort = port === undefined || port === null ? '' : String(port).trim();
1695
+ const host = normalizedPort
1696
+ ? `${normalizedHostname}:${normalizedPort}`
1697
+ : normalizedHostname;
1698
+ return sanitizeAppId(host);
1699
+ }
1700
+ function deriveAppIdFromHost(host) {
1701
+ const parsed = parseHostPort(host || '');
1702
+ return buildSanitizedAppId(parsed.hostname, parsed.port);
1703
+ }
1704
+ function deriveAppIdFromLocation(locationLike) {
1705
+ const source = locationLike ||
1706
+ (typeof window !== 'undefined' ? window.location : undefined);
1707
+ if (!source)
1708
+ return '';
1709
+ const hostname = typeof source.hostname === 'string' ? source.hostname : '';
1710
+ const port = source.port;
1711
+ if (hostname) {
1712
+ const appId = buildSanitizedAppId(hostname, port);
1713
+ if (appId)
1714
+ return appId;
1715
+ }
1716
+ if (typeof source.host === 'string') {
1717
+ return deriveAppIdFromHost(source.host);
1718
+ }
1719
+ return '';
1720
+ }
1134
1721
  function normalizeAction(action) {
1135
1722
  const trimmed = (action || '').trim();
1136
1723
  return trimmed ? trimmed : null;
@@ -1139,25 +1726,19 @@
1139
1726
  if (!options.appId)
1140
1727
  return null;
1141
1728
  const action = normalizeAction(options.appAction) || DEFAULT_APP_ACTION;
1729
+ const resource = `app:all:${sanitizeAppId(options.appId)}`;
1142
1730
  return {
1143
- resource: `app:${sanitizeAppId(options.appId)}`,
1731
+ with: resource,
1732
+ can: action,
1733
+ resource,
1144
1734
  action,
1145
1735
  };
1146
1736
  }
1147
1737
  function hasAppCapability(caps) {
1148
- return (caps || []).some(cap => typeof cap?.resource === 'string' && cap.resource.startsWith('app:'));
1738
+ return (caps || []).some(cap => getCapabilityResource(cap).startsWith('app:'));
1149
1739
  }
1150
1740
  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
- });
1741
+ return normalizeUcanCapabilities(caps);
1161
1742
  }
1162
1743
  function ensureAppCapability(caps, options) {
1163
1744
  const appCap = buildAppCapability(options);
@@ -1177,7 +1758,13 @@
1177
1758
  return undefined;
1178
1759
  }
1179
1760
  function buildCapsKey(caps) {
1180
- return JSON.stringify(caps || []);
1761
+ const canonical = (caps || [])
1762
+ .map(cap => ({
1763
+ with: getCapabilityResource(cap),
1764
+ can: getCapabilityAction(cap),
1765
+ }))
1766
+ .filter(cap => Boolean(cap.with && cap.can));
1767
+ return JSON.stringify(canonical);
1181
1768
  }
1182
1769
  function buildTokenCacheKey(issuer, audience, caps) {
1183
1770
  return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
@@ -1355,18 +1942,28 @@
1355
1942
  }
1356
1943
 
1357
1944
  exports.WebDavClient = WebDavClient;
1945
+ exports.authCentralUcanFetch = authCentralUcanFetch;
1358
1946
  exports.authFetch = authFetch;
1359
1947
  exports.authUcanFetch = authUcanFetch;
1360
1948
  exports.clearAccessToken = clearAccessToken;
1949
+ exports.clearCentralSessionToken = clearCentralSessionToken;
1361
1950
  exports.clearUcanSession = clearUcanSession;
1951
+ exports.createAndIssueCentralUcan = createAndIssueCentralUcan;
1952
+ exports.createCentralSession = createCentralSession;
1362
1953
  exports.createDelegationUcan = createDelegationUcan;
1363
1954
  exports.createInvocationUcan = createInvocationUcan;
1364
1955
  exports.createRootUcan = createRootUcan;
1365
1956
  exports.createUcanSession = createUcanSession;
1366
1957
  exports.createWebDavClient = createWebDavClient;
1958
+ exports.deriveAppIdFromHost = deriveAppIdFromHost;
1959
+ exports.deriveAppIdFromLocation = deriveAppIdFromLocation;
1367
1960
  exports.getAccessToken = getAccessToken;
1368
1961
  exports.getAccounts = getAccounts;
1369
1962
  exports.getBalance = getBalance;
1963
+ exports.getCapabilityAction = getCapabilityAction;
1964
+ exports.getCapabilityResource = getCapabilityResource;
1965
+ exports.getCentralIssuerInfo = getCentralIssuerInfo;
1966
+ exports.getCentralSessionToken = getCentralSessionToken;
1370
1967
  exports.getChainId = getChainId;
1371
1968
  exports.getOrCreateUcanRoot = getOrCreateUcanRoot;
1372
1969
  exports.getPreferredAccount = getPreferredAccount;
@@ -1376,14 +1973,19 @@
1376
1973
  exports.initDappSession = initDappSession;
1377
1974
  exports.initWebDavStorage = initWebDavStorage;
1378
1975
  exports.isYeYingProvider = isYeYingProvider;
1976
+ exports.issueCentralUcan = issueCentralUcan;
1379
1977
  exports.loginWithChallenge = loginWithChallenge;
1380
1978
  exports.logout = logout;
1979
+ exports.normalizeAppHostnameForAppId = normalizeAppHostnameForAppId;
1980
+ exports.normalizeUcanCapabilities = normalizeUcanCapabilities;
1981
+ exports.normalizeUcanCapability = normalizeUcanCapability;
1381
1982
  exports.onAccountsChanged = onAccountsChanged;
1382
1983
  exports.onChainChanged = onChainChanged;
1383
1984
  exports.refreshAccessToken = refreshAccessToken;
1384
1985
  exports.requestAccounts = requestAccounts;
1385
1986
  exports.requireProvider = requireProvider;
1386
1987
  exports.setAccessToken = setAccessToken;
1988
+ exports.setCentralSessionToken = setCentralSessionToken;
1387
1989
  exports.signMessage = signMessage;
1388
1990
  exports.storeUcanRoot = storeUcanRoot;
1389
1991
  exports.watchAccounts = watchAccounts;