@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.
- package/README.md +41 -6
- package/dist/auth/central.d.ts +76 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/ucan.d.ts +18 -2
- package/dist/web3-bs.esm.js +540 -46
- package/dist/web3-bs.esm.js.map +1 -1
- package/dist/web3-bs.umd.js +551 -45
- package/dist/web3-bs.umd.js.map +1 -1
- package/package.json +3 -2
package/dist/web3-bs.esm.js
CHANGED
|
@@ -193,12 +193,12 @@ function onChainChanged(provider, handler) {
|
|
|
193
193
|
return () => provider.removeListener?.('chainChanged', handler);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
function normalizeBaseUrl$
|
|
196
|
+
function normalizeBaseUrl$2(baseUrl) {
|
|
197
197
|
return baseUrl.replace(/\/+$/, '');
|
|
198
198
|
}
|
|
199
|
-
function joinUrl$
|
|
199
|
+
function joinUrl$2(baseUrl, path) {
|
|
200
200
|
const trimmed = path.replace(/^\/+/, '');
|
|
201
|
-
return `${normalizeBaseUrl$
|
|
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$
|
|
359
|
-
const verifyUrl = joinUrl$
|
|
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$
|
|
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$
|
|
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 (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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 (
|
|
670
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
}
|
|
@@ -1133,25 +1627,19 @@ function buildAppCapability(options) {
|
|
|
1133
1627
|
if (!options.appId)
|
|
1134
1628
|
return null;
|
|
1135
1629
|
const action = normalizeAction(options.appAction) || DEFAULT_APP_ACTION;
|
|
1630
|
+
const resource = `app:all:${sanitizeAppId(options.appId)}`;
|
|
1136
1631
|
return {
|
|
1137
|
-
|
|
1632
|
+
with: resource,
|
|
1633
|
+
can: action,
|
|
1634
|
+
resource,
|
|
1138
1635
|
action,
|
|
1139
1636
|
};
|
|
1140
1637
|
}
|
|
1141
1638
|
function hasAppCapability(caps) {
|
|
1142
|
-
return (caps || []).some(cap =>
|
|
1639
|
+
return (caps || []).some(cap => getCapabilityResource(cap).startsWith('app:'));
|
|
1143
1640
|
}
|
|
1144
1641
|
function dedupeCapabilities(caps) {
|
|
1145
|
-
|
|
1146
|
-
return (caps || []).filter(cap => {
|
|
1147
|
-
if (!cap || !cap.resource || !cap.action)
|
|
1148
|
-
return false;
|
|
1149
|
-
const key = `${cap.resource}|${cap.action}`;
|
|
1150
|
-
if (seen.has(key))
|
|
1151
|
-
return false;
|
|
1152
|
-
seen.add(key);
|
|
1153
|
-
return true;
|
|
1154
|
-
});
|
|
1642
|
+
return normalizeUcanCapabilities(caps);
|
|
1155
1643
|
}
|
|
1156
1644
|
function ensureAppCapability(caps, options) {
|
|
1157
1645
|
const appCap = buildAppCapability(options);
|
|
@@ -1171,7 +1659,13 @@ function resolveAppDir(options) {
|
|
|
1171
1659
|
return undefined;
|
|
1172
1660
|
}
|
|
1173
1661
|
function buildCapsKey(caps) {
|
|
1174
|
-
|
|
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);
|
|
1175
1669
|
}
|
|
1176
1670
|
function buildTokenCacheKey(issuer, audience, caps) {
|
|
1177
1671
|
return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
|
|
@@ -1348,5 +1842,5 @@ async function initDappSession(options) {
|
|
|
1348
1842
|
return result;
|
|
1349
1843
|
}
|
|
1350
1844
|
|
|
1351
|
-
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 };
|
|
1352
1846
|
//# sourceMappingURL=web3-bs.esm.js.map
|