@yeying-community/web3-bs 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/dapp.d.ts +1 -0
- package/dist/web3-bs.esm.js +575 -36
- package/dist/web3-bs.esm.js.map +1 -1
- package/dist/web3-bs.umd.js +586 -35
- package/dist/web3-bs.umd.js.map +1 -1
- package/package.json +3 -2
package/dist/web3-bs.umd.js
CHANGED
|
@@ -199,12 +199,12 @@
|
|
|
199
199
|
return () => provider.removeListener?.('chainChanged', handler);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
function normalizeBaseUrl$
|
|
202
|
+
function normalizeBaseUrl$2(baseUrl) {
|
|
203
203
|
return baseUrl.replace(/\/+$/, '');
|
|
204
204
|
}
|
|
205
|
-
function joinUrl$
|
|
205
|
+
function joinUrl$2(baseUrl, path) {
|
|
206
206
|
const trimmed = path.replace(/^\/+/, '');
|
|
207
|
-
return `${normalizeBaseUrl$
|
|
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$
|
|
365
|
-
const verifyUrl = joinUrl$
|
|
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$
|
|
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$
|
|
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 (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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 (
|
|
676
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
}
|
|
@@ -1119,6 +1613,7 @@
|
|
|
1119
1613
|
|
|
1120
1614
|
const tokenCache = new Map();
|
|
1121
1615
|
const TOKEN_SKEW_MS = 5000;
|
|
1616
|
+
const DEFAULT_APP_ACTION = 'write';
|
|
1122
1617
|
function normalizeAppDir(path) {
|
|
1123
1618
|
const trimmed = path.trim();
|
|
1124
1619
|
if (!trimmed)
|
|
@@ -1130,6 +1625,36 @@
|
|
|
1130
1625
|
function sanitizeAppId(appId) {
|
|
1131
1626
|
return appId.trim().replace(/[^a-zA-Z0-9._-]/g, '-');
|
|
1132
1627
|
}
|
|
1628
|
+
function normalizeAction(action) {
|
|
1629
|
+
const trimmed = (action || '').trim();
|
|
1630
|
+
return trimmed ? trimmed : null;
|
|
1631
|
+
}
|
|
1632
|
+
function buildAppCapability(options) {
|
|
1633
|
+
if (!options.appId)
|
|
1634
|
+
return null;
|
|
1635
|
+
const action = normalizeAction(options.appAction) || DEFAULT_APP_ACTION;
|
|
1636
|
+
const resource = `app:all:${sanitizeAppId(options.appId)}`;
|
|
1637
|
+
return {
|
|
1638
|
+
with: resource,
|
|
1639
|
+
can: action,
|
|
1640
|
+
resource,
|
|
1641
|
+
action,
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
function hasAppCapability(caps) {
|
|
1645
|
+
return (caps || []).some(cap => getCapabilityResource(cap).startsWith('app:'));
|
|
1646
|
+
}
|
|
1647
|
+
function dedupeCapabilities(caps) {
|
|
1648
|
+
return normalizeUcanCapabilities(caps);
|
|
1649
|
+
}
|
|
1650
|
+
function ensureAppCapability(caps, options) {
|
|
1651
|
+
const appCap = buildAppCapability(options);
|
|
1652
|
+
if (!appCap)
|
|
1653
|
+
return caps || [];
|
|
1654
|
+
if (hasAppCapability(caps || []))
|
|
1655
|
+
return caps || [];
|
|
1656
|
+
return dedupeCapabilities([...(caps || []), appCap]);
|
|
1657
|
+
}
|
|
1133
1658
|
function resolveAppDir(options) {
|
|
1134
1659
|
if (options.appDir) {
|
|
1135
1660
|
return normalizeAppDir(options.appDir);
|
|
@@ -1140,11 +1665,25 @@
|
|
|
1140
1665
|
return undefined;
|
|
1141
1666
|
}
|
|
1142
1667
|
function buildCapsKey(caps) {
|
|
1143
|
-
|
|
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);
|
|
1144
1675
|
}
|
|
1145
1676
|
function buildTokenCacheKey(issuer, audience, caps) {
|
|
1146
1677
|
return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
|
|
1147
1678
|
}
|
|
1679
|
+
function resolveWebdavCaps(options) {
|
|
1680
|
+
const baseCaps = options.capabilities || options.root?.cap || [];
|
|
1681
|
+
return ensureAppCapability(baseCaps, options);
|
|
1682
|
+
}
|
|
1683
|
+
function resolveInvocationCaps(options, fallbackCaps) {
|
|
1684
|
+
const caps = options.invocationCapabilities || fallbackCaps;
|
|
1685
|
+
return ensureAppCapability(caps, options);
|
|
1686
|
+
}
|
|
1148
1687
|
function isTokenValid(entry, nowMs) {
|
|
1149
1688
|
if (!entry.exp)
|
|
1150
1689
|
return false;
|
|
@@ -1216,7 +1755,7 @@
|
|
|
1216
1755
|
return token;
|
|
1217
1756
|
}
|
|
1218
1757
|
async function initWebDavStorage(options) {
|
|
1219
|
-
const caps = options
|
|
1758
|
+
const caps = resolveWebdavCaps(options);
|
|
1220
1759
|
if (!caps || caps.length === 0) {
|
|
1221
1760
|
throw new Error('Missing UCAN capabilities for WebDAV');
|
|
1222
1761
|
}
|
|
@@ -1246,7 +1785,7 @@
|
|
|
1246
1785
|
expiresInMs: options.rootExpiresInMs,
|
|
1247
1786
|
});
|
|
1248
1787
|
}
|
|
1249
|
-
const invocationCaps = options
|
|
1788
|
+
const invocationCaps = resolveInvocationCaps(options, caps);
|
|
1250
1789
|
const token = await getCachedInvocationToken({
|
|
1251
1790
|
issuer: session,
|
|
1252
1791
|
audience: options.audience,
|
|
@@ -1310,10 +1849,14 @@
|
|
|
1310
1849
|
}
|
|
1311
1850
|
|
|
1312
1851
|
exports.WebDavClient = WebDavClient;
|
|
1852
|
+
exports.authCentralUcanFetch = authCentralUcanFetch;
|
|
1313
1853
|
exports.authFetch = authFetch;
|
|
1314
1854
|
exports.authUcanFetch = authUcanFetch;
|
|
1315
1855
|
exports.clearAccessToken = clearAccessToken;
|
|
1856
|
+
exports.clearCentralSessionToken = clearCentralSessionToken;
|
|
1316
1857
|
exports.clearUcanSession = clearUcanSession;
|
|
1858
|
+
exports.createAndIssueCentralUcan = createAndIssueCentralUcan;
|
|
1859
|
+
exports.createCentralSession = createCentralSession;
|
|
1317
1860
|
exports.createDelegationUcan = createDelegationUcan;
|
|
1318
1861
|
exports.createInvocationUcan = createInvocationUcan;
|
|
1319
1862
|
exports.createRootUcan = createRootUcan;
|
|
@@ -1322,6 +1865,10 @@
|
|
|
1322
1865
|
exports.getAccessToken = getAccessToken;
|
|
1323
1866
|
exports.getAccounts = getAccounts;
|
|
1324
1867
|
exports.getBalance = getBalance;
|
|
1868
|
+
exports.getCapabilityAction = getCapabilityAction;
|
|
1869
|
+
exports.getCapabilityResource = getCapabilityResource;
|
|
1870
|
+
exports.getCentralIssuerInfo = getCentralIssuerInfo;
|
|
1871
|
+
exports.getCentralSessionToken = getCentralSessionToken;
|
|
1325
1872
|
exports.getChainId = getChainId;
|
|
1326
1873
|
exports.getOrCreateUcanRoot = getOrCreateUcanRoot;
|
|
1327
1874
|
exports.getPreferredAccount = getPreferredAccount;
|
|
@@ -1331,14 +1878,18 @@
|
|
|
1331
1878
|
exports.initDappSession = initDappSession;
|
|
1332
1879
|
exports.initWebDavStorage = initWebDavStorage;
|
|
1333
1880
|
exports.isYeYingProvider = isYeYingProvider;
|
|
1881
|
+
exports.issueCentralUcan = issueCentralUcan;
|
|
1334
1882
|
exports.loginWithChallenge = loginWithChallenge;
|
|
1335
1883
|
exports.logout = logout;
|
|
1884
|
+
exports.normalizeUcanCapabilities = normalizeUcanCapabilities;
|
|
1885
|
+
exports.normalizeUcanCapability = normalizeUcanCapability;
|
|
1336
1886
|
exports.onAccountsChanged = onAccountsChanged;
|
|
1337
1887
|
exports.onChainChanged = onChainChanged;
|
|
1338
1888
|
exports.refreshAccessToken = refreshAccessToken;
|
|
1339
1889
|
exports.requestAccounts = requestAccounts;
|
|
1340
1890
|
exports.requireProvider = requireProvider;
|
|
1341
1891
|
exports.setAccessToken = setAccessToken;
|
|
1892
|
+
exports.setCentralSessionToken = setCentralSessionToken;
|
|
1342
1893
|
exports.signMessage = signMessage;
|
|
1343
1894
|
exports.storeUcanRoot = storeUcanRoot;
|
|
1344
1895
|
exports.watchAccounts = watchAccounts;
|