@yeying-community/web3-bs 1.0.10 → 1.0.12
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 +13 -2
- package/dist/auth/provider.d.ts +2 -1
- package/dist/auth/types.d.ts +7 -0
- package/dist/auth/ucan.d.ts +44 -0
- package/dist/dapp.d.ts +1 -0
- package/dist/web3-bs.esm.js +256 -67
- package/dist/web3-bs.esm.js.map +1 -1
- package/dist/web3-bs.umd.js +265 -66
- package/dist/web3-bs.umd.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,12 +22,13 @@ npm install @yeying-community/web3-bs
|
|
|
22
22
|
|
|
23
23
|
- `getProvider` / `requireProvider`
|
|
24
24
|
- `watchProvider`
|
|
25
|
-
- `requestAccounts` / `getAccounts` / `getPreferredAccount` / `watchAccounts`
|
|
25
|
+
- `requestAccounts` / `focusPendingApproval` / `getAccounts` / `getPreferredAccount` / `watchAccounts`
|
|
26
26
|
- `getChainId` / `getBalance`
|
|
27
27
|
- `onAccountsChanged` / `onChainChanged`
|
|
28
28
|
- `classifyWalletError` / `isUserRejectedWalletAction` / `isWalletReconnectError`
|
|
29
29
|
|
|
30
30
|
`requestAccounts` 默认会复用同一 provider 上尚未完成的连接请求,避免用户重复点击时触发多个钱包授权弹窗。
|
|
31
|
+
当钱包已经存在待确认的连接、签名或解锁窗口时,可调用 `focusPendingApproval` 将该窗口重新拉到前台,而不是再发起一次新的请求。
|
|
31
32
|
|
|
32
33
|
### 2) SIWE + JWT
|
|
33
34
|
|
|
@@ -72,7 +73,17 @@ npm install @yeying-community/web3-bs
|
|
|
72
73
|
### 单后端登录(SIWE + JWT)
|
|
73
74
|
|
|
74
75
|
```ts
|
|
75
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
authFetch,
|
|
78
|
+
focusPendingApproval,
|
|
79
|
+
loginWithChallenge,
|
|
80
|
+
requestAccounts,
|
|
81
|
+
} from '@yeying-community/web3-bs';
|
|
82
|
+
|
|
83
|
+
const pending = await focusPendingApproval().catch(() => ({ focused: false }));
|
|
84
|
+
if (!pending.focused) {
|
|
85
|
+
await requestAccounts();
|
|
86
|
+
}
|
|
76
87
|
|
|
77
88
|
await loginWithChallenge({
|
|
78
89
|
baseUrl: 'http://localhost:3203/api/v1/public/auth',
|
package/dist/auth/provider.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Eip1193Provider, ProviderDiscoveryOptions, ProviderInfo, RequestAccountsOptions, AccountSelection, PreferredAccountOptions, WatchAccountsOptions, AccountsChangedHandler, WatchProviderOptions, ProviderChangedHandler, WalletErrorInfo } from './types';
|
|
1
|
+
import { Eip1193Provider, ProviderDiscoveryOptions, ProviderInfo, RequestAccountsOptions, AccountSelection, PreferredAccountOptions, WatchAccountsOptions, AccountsChangedHandler, WatchProviderOptions, ProviderChangedHandler, WalletErrorInfo, FocusPendingApprovalResult } from './types';
|
|
2
2
|
export declare function isYeYingProvider(provider?: Eip1193Provider | null, info?: ProviderInfo): boolean;
|
|
3
3
|
export declare function getProvider(options?: ProviderDiscoveryOptions): Promise<Eip1193Provider | null>;
|
|
4
4
|
export declare function watchProvider(handler: ProviderChangedHandler, options?: WatchProviderOptions): () => void;
|
|
@@ -9,6 +9,7 @@ export declare function isUserRejectedWalletAction(error: unknown): boolean;
|
|
|
9
9
|
export declare function isWalletReconnectError(error: unknown): boolean;
|
|
10
10
|
export declare function requireProvider(options?: ProviderDiscoveryOptions): Promise<Eip1193Provider>;
|
|
11
11
|
export declare function requestAccounts(options?: RequestAccountsOptions): Promise<string[]>;
|
|
12
|
+
export declare function focusPendingApproval(provider?: Eip1193Provider): Promise<FocusPendingApprovalResult>;
|
|
12
13
|
export declare function getAccounts(provider?: Eip1193Provider): Promise<string[]>;
|
|
13
14
|
export declare function getChainId(provider?: Eip1193Provider): Promise<string | null>;
|
|
14
15
|
export declare function getPreferredAccount(options?: PreferredAccountOptions): Promise<AccountSelection>;
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -51,6 +51,13 @@ export interface RequestAccountsOptions {
|
|
|
51
51
|
provider?: Eip1193Provider;
|
|
52
52
|
dedupe?: boolean;
|
|
53
53
|
}
|
|
54
|
+
export type FocusPendingApprovalResult = {
|
|
55
|
+
focused: boolean;
|
|
56
|
+
type: string | null;
|
|
57
|
+
requestId?: string | null;
|
|
58
|
+
origin?: string;
|
|
59
|
+
tabId?: number | null;
|
|
60
|
+
};
|
|
54
61
|
export type AccountSelection = {
|
|
55
62
|
account: string | null;
|
|
56
63
|
accounts: string[];
|
package/dist/auth/ucan.d.ts
CHANGED
|
@@ -77,6 +77,11 @@ export type CreateUcanTokenOptions = {
|
|
|
77
77
|
notBeforeMs?: number;
|
|
78
78
|
proofs?: UcanProof[];
|
|
79
79
|
};
|
|
80
|
+
export type GetOrCreateInvocationUcanOptions = CreateUcanTokenOptions & {
|
|
81
|
+
ucan?: string;
|
|
82
|
+
skewMs?: number;
|
|
83
|
+
nowMs?: number;
|
|
84
|
+
};
|
|
80
85
|
export type UcanFetchOptions = {
|
|
81
86
|
ucan?: string;
|
|
82
87
|
audience?: string;
|
|
@@ -87,8 +92,37 @@ export type UcanFetchOptions = {
|
|
|
87
92
|
proofs?: UcanProof[];
|
|
88
93
|
expiresInMs?: number;
|
|
89
94
|
notBeforeMs?: number;
|
|
95
|
+
skewMs?: number;
|
|
90
96
|
fetcher?: typeof fetch;
|
|
91
97
|
};
|
|
98
|
+
export type UcanTokenTiming = {
|
|
99
|
+
valid: boolean;
|
|
100
|
+
payload: UcanTokenPayload | null;
|
|
101
|
+
exp: number | null;
|
|
102
|
+
nbf: number | null;
|
|
103
|
+
issuedAt: number | null;
|
|
104
|
+
nowMs: number;
|
|
105
|
+
remainingMs: number | null;
|
|
106
|
+
activeInMs: number;
|
|
107
|
+
expired: boolean;
|
|
108
|
+
notBefore: boolean;
|
|
109
|
+
};
|
|
110
|
+
export type UcanTtlPolicy = {
|
|
111
|
+
expiresInMs?: number;
|
|
112
|
+
skewMs?: number;
|
|
113
|
+
};
|
|
114
|
+
export type UcanAuthErrorType = 'expired' | 'not-before' | 'unauthorized' | 'forbidden' | 'invalid-token' | 'unknown';
|
|
115
|
+
export type UcanAuthErrorInfo = {
|
|
116
|
+
type: UcanAuthErrorType;
|
|
117
|
+
message: string;
|
|
118
|
+
retryable: boolean;
|
|
119
|
+
shouldRefresh: boolean;
|
|
120
|
+
status?: number;
|
|
121
|
+
code?: string | number;
|
|
122
|
+
};
|
|
123
|
+
export declare const DEFAULT_UCAN_SESSION_TTL_MS: number;
|
|
124
|
+
export declare const DEFAULT_UCAN_TOKEN_TTL_MS: number;
|
|
125
|
+
export declare const DEFAULT_UCAN_TOKEN_SKEW_MS: number;
|
|
92
126
|
export declare function getCapabilityResource(cap: UcanCapability | null | undefined): string;
|
|
93
127
|
export declare function getCapabilityAction(cap: UcanCapability | null | undefined): string;
|
|
94
128
|
export declare function normalizeUcanCapability(cap: UcanCapability | null | undefined, options?: {
|
|
@@ -97,6 +131,15 @@ export declare function normalizeUcanCapability(cap: UcanCapability | null | und
|
|
|
97
131
|
export declare function normalizeUcanCapabilities(caps: UcanCapability[] | undefined, options?: {
|
|
98
132
|
includeLegacyAliases?: boolean;
|
|
99
133
|
}): UcanCapability[];
|
|
134
|
+
export declare function normalizeUcanExpiry(exp: number | undefined, fallbackMs: number): number;
|
|
135
|
+
export declare function decodeUcanPayload(token: string): UcanTokenPayload | null;
|
|
136
|
+
export declare function getUcanTokenTiming(token: string, options?: {
|
|
137
|
+
nowMs?: number;
|
|
138
|
+
}): UcanTokenTiming;
|
|
139
|
+
export declare function isUcanTokenFresh(tokenOrTiming: string | UcanTokenTiming, options?: UcanTtlPolicy & {
|
|
140
|
+
nowMs?: number;
|
|
141
|
+
}): boolean;
|
|
142
|
+
export declare function classifyUcanAuthError(error: unknown): UcanAuthErrorInfo;
|
|
100
143
|
export declare function getUcanSession(id?: string, provider?: Eip1193Provider): Promise<UcanSessionKey | null>;
|
|
101
144
|
export declare function createUcanSession(options?: CreateUcanSessionOptions): Promise<UcanSessionKey>;
|
|
102
145
|
export declare function clearUcanSession(id?: string): Promise<void>;
|
|
@@ -106,4 +149,5 @@ export declare function getOrCreateUcanRoot(options: CreateRootUcanOptions): Pro
|
|
|
106
149
|
export declare function createRootUcan(options: CreateRootUcanOptions): Promise<UcanRootProof>;
|
|
107
150
|
export declare function createDelegationUcan(options: CreateUcanTokenOptions): Promise<string>;
|
|
108
151
|
export declare function createInvocationUcan(options: CreateUcanTokenOptions): Promise<string>;
|
|
152
|
+
export declare function getOrCreateInvocationUcan(options: GetOrCreateInvocationUcanOptions): Promise<string>;
|
|
109
153
|
export declare function authUcanFetch(input: RequestInfo | URL, init?: RequestInit, options?: UcanFetchOptions): Promise<Response>;
|
package/dist/dapp.d.ts
CHANGED
package/dist/web3-bs.esm.js
CHANGED
|
@@ -294,6 +294,25 @@ async function requestAccounts(options = {}) {
|
|
|
294
294
|
requestAccountsInFlight.delete(provider);
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
+
async function focusPendingApproval(provider) {
|
|
298
|
+
const p = provider || (await requireProvider());
|
|
299
|
+
const result = await p.request({
|
|
300
|
+
method: 'wallet_focusPendingApproval',
|
|
301
|
+
});
|
|
302
|
+
if (!result || typeof result !== 'object') {
|
|
303
|
+
return { focused: false, type: null };
|
|
304
|
+
}
|
|
305
|
+
const payload = result;
|
|
306
|
+
return {
|
|
307
|
+
focused: Boolean(payload.focused),
|
|
308
|
+
type: typeof payload.type === 'string' ? payload.type : null,
|
|
309
|
+
requestId: typeof payload.requestId === 'string' ? payload.requestId : null,
|
|
310
|
+
origin: typeof payload.origin === 'string' ? payload.origin : '',
|
|
311
|
+
tabId: typeof payload.tabId === 'number' && Number.isFinite(payload.tabId)
|
|
312
|
+
? payload.tabId
|
|
313
|
+
: null,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
297
316
|
async function getAccounts(provider) {
|
|
298
317
|
const p = provider || (await requireProvider());
|
|
299
318
|
const accounts = (await p.request({ method: 'eth_accounts' }));
|
|
@@ -669,8 +688,9 @@ async function authFetch(input, init = {}, options = {}) {
|
|
|
669
688
|
}
|
|
670
689
|
|
|
671
690
|
const DEFAULT_SESSION_ID = 'default';
|
|
672
|
-
const
|
|
673
|
-
const
|
|
691
|
+
const DEFAULT_UCAN_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
692
|
+
const DEFAULT_UCAN_TOKEN_TTL_MS = 40 * 60 * 1000;
|
|
693
|
+
const DEFAULT_UCAN_TOKEN_SKEW_MS = 60 * 1000;
|
|
674
694
|
const DB_NAME = 'yeying-web3';
|
|
675
695
|
const DB_STORE = 'ucan-sessions';
|
|
676
696
|
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
@@ -751,6 +771,30 @@ function normalizeUcanCapabilities(caps, options = {}) {
|
|
|
751
771
|
function encodeJson(value) {
|
|
752
772
|
return toBase64Url(textEncoder.encode(JSON.stringify(value)));
|
|
753
773
|
}
|
|
774
|
+
function decodeBase64Url(input) {
|
|
775
|
+
if (!input)
|
|
776
|
+
return null;
|
|
777
|
+
const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
778
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
|
|
779
|
+
try {
|
|
780
|
+
if (typeof atob === 'function') {
|
|
781
|
+
return atob(padded);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch {
|
|
785
|
+
// Try Node-compatible fallback below.
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
const nodeBuffer = globalThis.Buffer;
|
|
789
|
+
if (nodeBuffer) {
|
|
790
|
+
return nodeBuffer.from(padded, 'base64').toString('utf8');
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
754
798
|
function randomNonce(bytes = 16) {
|
|
755
799
|
const buffer = new Uint8Array(bytes);
|
|
756
800
|
crypto.getRandomValues(buffer);
|
|
@@ -759,8 +803,144 @@ function randomNonce(bytes = 16) {
|
|
|
759
803
|
.join('');
|
|
760
804
|
}
|
|
761
805
|
function normalizeExpiry(exp, fallbackMs) {
|
|
806
|
+
if (typeof exp === 'number' && !Number.isNaN(exp))
|
|
807
|
+
return exp;
|
|
762
808
|
return Date.now() + fallbackMs;
|
|
763
809
|
}
|
|
810
|
+
function normalizeUcanExpiry(exp, fallbackMs) {
|
|
811
|
+
return normalizeExpiry(exp, fallbackMs);
|
|
812
|
+
}
|
|
813
|
+
function decodeUcanPayload(token) {
|
|
814
|
+
const parts = String(token || '').split('.');
|
|
815
|
+
if (parts.length < 2)
|
|
816
|
+
return null;
|
|
817
|
+
const decoded = decodeBase64Url(parts[1]);
|
|
818
|
+
if (!decoded)
|
|
819
|
+
return null;
|
|
820
|
+
try {
|
|
821
|
+
const payload = JSON.parse(decoded);
|
|
822
|
+
if (!payload || typeof payload !== 'object')
|
|
823
|
+
return null;
|
|
824
|
+
return payload;
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function getUcanTokenTiming(token, options = {}) {
|
|
831
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
832
|
+
const payload = decodeUcanPayload(token);
|
|
833
|
+
const exp = typeof payload?.exp === 'number' ? payload.exp : null;
|
|
834
|
+
const nbf = typeof payload?.nbf === 'number' ? payload.nbf : null;
|
|
835
|
+
const payloadWithIat = payload;
|
|
836
|
+
const issuedAt = typeof payloadWithIat?.iat === 'number'
|
|
837
|
+
? payloadWithIat.iat
|
|
838
|
+
: null;
|
|
839
|
+
const remainingMs = exp === null ? null : exp - nowMs;
|
|
840
|
+
const activeInMs = nbf === null ? 0 : Math.max(0, nbf - nowMs);
|
|
841
|
+
const expired = exp === null || (remainingMs !== null && remainingMs <= 0);
|
|
842
|
+
const notBefore = activeInMs > 0;
|
|
843
|
+
return {
|
|
844
|
+
valid: Boolean(payload && !expired && !notBefore),
|
|
845
|
+
payload,
|
|
846
|
+
exp,
|
|
847
|
+
nbf,
|
|
848
|
+
issuedAt,
|
|
849
|
+
nowMs,
|
|
850
|
+
remainingMs,
|
|
851
|
+
activeInMs,
|
|
852
|
+
expired,
|
|
853
|
+
notBefore,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function isUcanTokenFresh(tokenOrTiming, options = {}) {
|
|
857
|
+
const timing = typeof tokenOrTiming === 'string'
|
|
858
|
+
? getUcanTokenTiming(tokenOrTiming, { nowMs: options.nowMs })
|
|
859
|
+
: tokenOrTiming;
|
|
860
|
+
if (!timing.valid)
|
|
861
|
+
return false;
|
|
862
|
+
const skewMs = Math.max(0, options.skewMs ?? DEFAULT_UCAN_TOKEN_SKEW_MS);
|
|
863
|
+
return typeof timing.remainingMs === 'number' && timing.remainingMs > skewMs;
|
|
864
|
+
}
|
|
865
|
+
function readErrorField(error, field) {
|
|
866
|
+
if (!error || typeof error !== 'object')
|
|
867
|
+
return undefined;
|
|
868
|
+
const value = error[field];
|
|
869
|
+
if (value !== undefined)
|
|
870
|
+
return value;
|
|
871
|
+
const nestedError = error.error;
|
|
872
|
+
if (nestedError && typeof nestedError === 'object') {
|
|
873
|
+
return nestedError[field];
|
|
874
|
+
}
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
function classifyUcanAuthError(error) {
|
|
878
|
+
const messageValue = readErrorField(error, 'message');
|
|
879
|
+
const codeValue = readErrorField(error, 'code');
|
|
880
|
+
const statusValue = readErrorField(error, 'status') ?? readErrorField(error, 'statusCode');
|
|
881
|
+
const message = typeof messageValue === 'string'
|
|
882
|
+
? messageValue
|
|
883
|
+
: error instanceof Error
|
|
884
|
+
? error.message
|
|
885
|
+
: String(messageValue || error || '');
|
|
886
|
+
const status = typeof statusValue === 'number' ? statusValue : undefined;
|
|
887
|
+
const code = typeof codeValue === 'string' || typeof codeValue === 'number' ? codeValue : undefined;
|
|
888
|
+
const normalized = `${message} ${String(code || '')}`.toLowerCase();
|
|
889
|
+
if (/ucan.*expired|expired.*ucan|token.*expired|jwt.*expired|\bexp\b/.test(normalized)) {
|
|
890
|
+
return { type: 'expired', message, retryable: true, shouldRefresh: true, status, code };
|
|
891
|
+
}
|
|
892
|
+
if (/not.?before|\bnbf\b|not yet valid/.test(normalized)) {
|
|
893
|
+
return { type: 'not-before', message, retryable: true, shouldRefresh: false, status, code };
|
|
894
|
+
}
|
|
895
|
+
if (/invalid.*token|malformed.*token|bad.*ucan|invalid.*ucan/.test(normalized)) {
|
|
896
|
+
return { type: 'invalid-token', message, retryable: true, shouldRefresh: true, status, code };
|
|
897
|
+
}
|
|
898
|
+
if (status === 401 || /unauthori[sz]ed|unauthenticated/.test(normalized)) {
|
|
899
|
+
return { type: 'unauthorized', message, retryable: true, shouldRefresh: true, status, code };
|
|
900
|
+
}
|
|
901
|
+
if (status === 403 || /forbidden|permission denied|capability/.test(normalized)) {
|
|
902
|
+
return { type: 'forbidden', message, retryable: false, shouldRefresh: false, status, code };
|
|
903
|
+
}
|
|
904
|
+
return { type: 'unknown', message, retryable: false, shouldRefresh: false, status, code };
|
|
905
|
+
}
|
|
906
|
+
function isReplayableRequestBody(body) {
|
|
907
|
+
if (body == null)
|
|
908
|
+
return true;
|
|
909
|
+
if (typeof body === 'string')
|
|
910
|
+
return true;
|
|
911
|
+
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams)
|
|
912
|
+
return true;
|
|
913
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData)
|
|
914
|
+
return true;
|
|
915
|
+
if (typeof Blob !== 'undefined' && body instanceof Blob)
|
|
916
|
+
return true;
|
|
917
|
+
if (body instanceof ArrayBuffer)
|
|
918
|
+
return true;
|
|
919
|
+
if (ArrayBuffer.isView(body))
|
|
920
|
+
return true;
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
async function parseResponseJsonBody(response) {
|
|
924
|
+
const text = await response.text();
|
|
925
|
+
if (!text)
|
|
926
|
+
return null;
|
|
927
|
+
try {
|
|
928
|
+
return JSON.parse(text);
|
|
929
|
+
}
|
|
930
|
+
catch {
|
|
931
|
+
return { raw: text };
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function shouldRetryUcanFetch(response, errorInfo) {
|
|
935
|
+
if (errorInfo.type === 'expired' || errorInfo.shouldRefresh) {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
if (response.status === 401)
|
|
939
|
+
return true;
|
|
940
|
+
if (response.status === 403 && errorInfo.type === 'forbidden')
|
|
941
|
+
return false;
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
764
944
|
function isSessionExpired(expiresAt, nowMs = Date.now()) {
|
|
765
945
|
return typeof expiresAt === 'number' && nowMs >= expiresAt;
|
|
766
946
|
}
|
|
@@ -857,7 +1037,7 @@ async function createLocalSession(options, record) {
|
|
|
857
1037
|
buildDidKey(pair.publicKey),
|
|
858
1038
|
]);
|
|
859
1039
|
const createdAt = Date.now();
|
|
860
|
-
const expiresAt = normalizeExpiry(undefined, options.expiresInMs ??
|
|
1040
|
+
const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
|
|
861
1041
|
const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
|
|
862
1042
|
await writeSessionRecord({
|
|
863
1043
|
id: sessionId,
|
|
@@ -1125,7 +1305,7 @@ async function createRootUcan(options) {
|
|
|
1125
1305
|
const domain = options.domain || (typeof window !== 'undefined' ? window.location.host : '127.0.0.1');
|
|
1126
1306
|
const uri = options.uri || (typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1');
|
|
1127
1307
|
const nonce = options.nonce || randomNonce(8);
|
|
1128
|
-
const exp = normalizeExpiry(undefined, options.expiresInMs ??
|
|
1308
|
+
const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
|
|
1129
1309
|
const nbf = options.notBeforeMs;
|
|
1130
1310
|
const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
|
|
1131
1311
|
if (!normalizedCapabilities.length) {
|
|
@@ -1209,7 +1389,7 @@ async function createDelegationUcan(options) {
|
|
|
1209
1389
|
if (!normalizedCapabilities.length) {
|
|
1210
1390
|
throw new Error('Missing UCAN capabilities');
|
|
1211
1391
|
}
|
|
1212
|
-
const exp = normalizeExpiry(undefined, options.expiresInMs ??
|
|
1392
|
+
const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
|
|
1213
1393
|
const payload = {
|
|
1214
1394
|
iss: issuer.did,
|
|
1215
1395
|
aud: options.audience,
|
|
@@ -1231,7 +1411,7 @@ async function createInvocationUcan(options) {
|
|
|
1231
1411
|
if (!normalizedCapabilities.length) {
|
|
1232
1412
|
throw new Error('Missing UCAN capabilities');
|
|
1233
1413
|
}
|
|
1234
|
-
const exp = normalizeExpiry(undefined, options.expiresInMs ??
|
|
1414
|
+
const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
|
|
1235
1415
|
const payload = {
|
|
1236
1416
|
iss: issuer.did,
|
|
1237
1417
|
aud: options.audience,
|
|
@@ -1242,30 +1422,84 @@ async function createInvocationUcan(options) {
|
|
|
1242
1422
|
};
|
|
1243
1423
|
return await signUcanPayload(payload, issuer);
|
|
1244
1424
|
}
|
|
1425
|
+
async function getOrCreateInvocationUcan(options) {
|
|
1426
|
+
if (options.ucan &&
|
|
1427
|
+
isUcanTokenFresh(options.ucan, {
|
|
1428
|
+
nowMs: options.nowMs,
|
|
1429
|
+
skewMs: options.skewMs,
|
|
1430
|
+
})) {
|
|
1431
|
+
return options.ucan;
|
|
1432
|
+
}
|
|
1433
|
+
return await createInvocationUcan({
|
|
1434
|
+
issuer: options.issuer,
|
|
1435
|
+
sessionId: options.sessionId,
|
|
1436
|
+
provider: options.provider,
|
|
1437
|
+
audience: options.audience,
|
|
1438
|
+
capabilities: options.capabilities,
|
|
1439
|
+
expiresInMs: options.expiresInMs,
|
|
1440
|
+
notBeforeMs: options.notBeforeMs,
|
|
1441
|
+
proofs: options.proofs,
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1245
1444
|
async function authUcanFetch(input, init = {}, options = {}) {
|
|
1246
1445
|
const fetcher = options.fetcher || fetch;
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1446
|
+
const audience = options.audience;
|
|
1447
|
+
const capabilities = options.capabilities;
|
|
1448
|
+
const canRefresh = Boolean(audience && capabilities);
|
|
1449
|
+
const canRetry = canRefresh && isReplayableRequestBody(init.body);
|
|
1450
|
+
let token = options.ucan || '';
|
|
1451
|
+
if (!token || (canRefresh && !isUcanTokenFresh(token, { skewMs: options.skewMs }))) {
|
|
1452
|
+
if (!audience || !capabilities) {
|
|
1250
1453
|
throw new Error('Missing UCAN audience or capabilities');
|
|
1251
1454
|
}
|
|
1252
|
-
token = await
|
|
1455
|
+
token = await getOrCreateInvocationUcan({
|
|
1456
|
+
ucan: options.ucan,
|
|
1253
1457
|
issuer: options.issuer,
|
|
1254
1458
|
sessionId: options.sessionId,
|
|
1255
1459
|
provider: options.provider,
|
|
1256
|
-
audience
|
|
1257
|
-
capabilities
|
|
1460
|
+
audience,
|
|
1461
|
+
capabilities,
|
|
1258
1462
|
expiresInMs: options.expiresInMs,
|
|
1259
1463
|
notBeforeMs: options.notBeforeMs,
|
|
1260
1464
|
proofs: options.proofs,
|
|
1465
|
+
skewMs: options.skewMs,
|
|
1261
1466
|
});
|
|
1262
1467
|
}
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1468
|
+
const makeHeaders = (bearer) => {
|
|
1469
|
+
const headers = new Headers(init.headers || {});
|
|
1470
|
+
headers.set('Authorization', `Bearer ${bearer}`);
|
|
1471
|
+
return headers;
|
|
1472
|
+
};
|
|
1473
|
+
let response = await fetcher(input, {
|
|
1266
1474
|
...init,
|
|
1267
|
-
headers,
|
|
1475
|
+
headers: makeHeaders(token),
|
|
1268
1476
|
});
|
|
1477
|
+
if (!canRetry || response.ok) {
|
|
1478
|
+
return response;
|
|
1479
|
+
}
|
|
1480
|
+
const payload = await parseResponseJsonBody(response.clone()).catch(() => null);
|
|
1481
|
+
const errorInfo = classifyUcanAuthError(payload || response.statusText || response);
|
|
1482
|
+
if (!shouldRetryUcanFetch(response, errorInfo)) {
|
|
1483
|
+
return response;
|
|
1484
|
+
}
|
|
1485
|
+
if (!audience || !capabilities) {
|
|
1486
|
+
return response;
|
|
1487
|
+
}
|
|
1488
|
+
const refreshedToken = await createInvocationUcan({
|
|
1489
|
+
issuer: options.issuer,
|
|
1490
|
+
sessionId: options.sessionId,
|
|
1491
|
+
provider: options.provider,
|
|
1492
|
+
audience,
|
|
1493
|
+
capabilities,
|
|
1494
|
+
expiresInMs: options.expiresInMs,
|
|
1495
|
+
notBeforeMs: options.notBeforeMs,
|
|
1496
|
+
proofs: options.proofs,
|
|
1497
|
+
});
|
|
1498
|
+
response = await fetcher(input, {
|
|
1499
|
+
...init,
|
|
1500
|
+
headers: makeHeaders(refreshedToken),
|
|
1501
|
+
});
|
|
1502
|
+
return response;
|
|
1269
1503
|
}
|
|
1270
1504
|
|
|
1271
1505
|
const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
|
|
@@ -1860,7 +2094,6 @@ function createWebDavClient(options) {
|
|
|
1860
2094
|
}
|
|
1861
2095
|
|
|
1862
2096
|
const tokenCache = new Map();
|
|
1863
|
-
const TOKEN_SKEW_MS = 5000;
|
|
1864
2097
|
const DEFAULT_APP_ACTION = 'write';
|
|
1865
2098
|
const LOOPBACK_HOST_ALIASES = new Set([
|
|
1866
2099
|
'localhost',
|
|
@@ -2002,64 +2235,19 @@ function resolveInvocationCaps(options, fallbackCaps) {
|
|
|
2002
2235
|
const caps = options.invocationCapabilities || fallbackCaps;
|
|
2003
2236
|
return ensureAppCapability(caps, options);
|
|
2004
2237
|
}
|
|
2005
|
-
function isTokenValid(entry, nowMs) {
|
|
2006
|
-
if (!entry.exp)
|
|
2007
|
-
return false;
|
|
2008
|
-
if (entry.nbf && nowMs < entry.nbf)
|
|
2009
|
-
return false;
|
|
2010
|
-
return entry.exp - TOKEN_SKEW_MS > nowMs;
|
|
2011
|
-
}
|
|
2012
|
-
function decodeBase64Url(input) {
|
|
2013
|
-
if (!input)
|
|
2014
|
-
return null;
|
|
2015
|
-
const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
2016
|
-
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
|
|
2017
|
-
try {
|
|
2018
|
-
if (typeof atob === 'function') {
|
|
2019
|
-
return atob(padded);
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
catch {
|
|
2023
|
-
// ignore
|
|
2024
|
-
}
|
|
2025
|
-
try {
|
|
2026
|
-
const nodeBuffer = globalThis.Buffer;
|
|
2027
|
-
if (nodeBuffer) {
|
|
2028
|
-
return nodeBuffer.from(padded, 'base64').toString('utf8');
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
catch {
|
|
2032
|
-
return null;
|
|
2033
|
-
}
|
|
2034
|
-
return null;
|
|
2035
|
-
}
|
|
2036
|
-
function decodeUcanPayload(token) {
|
|
2037
|
-
const parts = token.split('.');
|
|
2038
|
-
if (parts.length < 2)
|
|
2039
|
-
return null;
|
|
2040
|
-
const decoded = decodeBase64Url(parts[1]);
|
|
2041
|
-
if (!decoded)
|
|
2042
|
-
return null;
|
|
2043
|
-
try {
|
|
2044
|
-
return JSON.parse(decoded);
|
|
2045
|
-
}
|
|
2046
|
-
catch {
|
|
2047
|
-
return null;
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
2238
|
async function getCachedInvocationToken(options) {
|
|
2051
2239
|
const cacheKey = buildTokenCacheKey(options.issuer, options.audience, options.capabilities);
|
|
2052
2240
|
const cached = tokenCache.get(cacheKey);
|
|
2053
2241
|
const nowMs = Date.now();
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
}
|
|
2057
|
-
const token = await createInvocationUcan({
|
|
2242
|
+
const token = await getOrCreateInvocationUcan({
|
|
2243
|
+
ucan: cached?.token,
|
|
2058
2244
|
issuer: options.issuer,
|
|
2059
2245
|
audience: options.audience,
|
|
2060
2246
|
capabilities: options.capabilities,
|
|
2061
2247
|
proofs: options.proofs,
|
|
2062
2248
|
expiresInMs: options.expiresInMs,
|
|
2249
|
+
skewMs: options.skewMs,
|
|
2250
|
+
nowMs,
|
|
2063
2251
|
notBeforeMs: options.notBeforeMs,
|
|
2064
2252
|
});
|
|
2065
2253
|
const payload = decodeUcanPayload(token);
|
|
@@ -2110,6 +2298,7 @@ async function initWebDavStorage(options) {
|
|
|
2110
2298
|
capabilities: invocationCaps,
|
|
2111
2299
|
proofs: [root],
|
|
2112
2300
|
expiresInMs: options.invocationExpiresInMs,
|
|
2301
|
+
skewMs: options.invocationSkewMs,
|
|
2113
2302
|
notBeforeMs: options.notBeforeMs,
|
|
2114
2303
|
});
|
|
2115
2304
|
const client = createWebDavClient({
|
|
@@ -2166,5 +2355,5 @@ async function initDappSession(options) {
|
|
|
2166
2355
|
return result;
|
|
2167
2356
|
}
|
|
2168
2357
|
|
|
2169
|
-
export { WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, classifyWalletError, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, deriveAppIdFromHost, deriveAppIdFromLocation, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, getWalletErrorCode, getWalletErrorMessage, initDappSession, initWebDavStorage, isUserRejectedWalletAction, isWalletReconnectError, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeAppHostnameForAppId, normalizeUcanCapabilities, normalizeUcanCapability, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts, watchProvider };
|
|
2358
|
+
export { DEFAULT_UCAN_SESSION_TTL_MS, DEFAULT_UCAN_TOKEN_SKEW_MS, DEFAULT_UCAN_TOKEN_TTL_MS, WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, classifyUcanAuthError, classifyWalletError, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, decodeUcanPayload, deriveAppIdFromHost, deriveAppIdFromLocation, focusPendingApproval, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateInvocationUcan, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, getUcanTokenTiming, getWalletErrorCode, getWalletErrorMessage, initDappSession, initWebDavStorage, isUcanTokenFresh, isUserRejectedWalletAction, isWalletReconnectError, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeAppHostnameForAppId, normalizeUcanCapabilities, normalizeUcanCapability, normalizeUcanExpiry, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts, watchProvider };
|
|
2170
2359
|
//# sourceMappingURL=web3-bs.esm.js.map
|