@yeying-community/web3-bs 1.0.11 → 1.0.13

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.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 加密/解密 SDK API
3
+ *
4
+ * 调用钱包插件的 `yeying_encrypt` / `yeying_decrypt` / `yeying_getCipherSuites`
5
+ * EIP-1193 自定义方法。命名安全套件 + 静默执行 + 站点授权模型同 UCAN。
6
+ *
7
+ * 数据密码(password 参数)由 DApp 自行管理与钱包密码独立;
8
+ * plaintext 走 base64 字符串跨 message 边界传输。
9
+ */
10
+ import { isUserRejectedWalletAction } from './provider';
11
+ import type { Eip1193Provider } from './types';
12
+ export interface CipherSuiteInfo {
13
+ name: string;
14
+ description: string;
15
+ mode: 'hash' | 'symmetric';
16
+ }
17
+ export interface EncryptOptions {
18
+ data: string | Uint8Array;
19
+ password: string;
20
+ suite?: string;
21
+ provider?: Eip1193Provider;
22
+ }
23
+ export interface DecryptOptions {
24
+ ciphertext: string;
25
+ password: string;
26
+ provider?: Eip1193Provider;
27
+ }
28
+ export interface GetCipherSuitesOptions {
29
+ provider?: Eip1193Provider;
30
+ }
31
+ export declare class CipherError extends Error {
32
+ readonly type: 'userRejected' | 'disconnected' | 'timeout' | 'notFound' | 'unknown';
33
+ readonly code: number | null;
34
+ readonly originalError: unknown;
35
+ constructor(message: string, type: CipherError['type'], code: number | null, originalError: unknown);
36
+ }
37
+ declare function bytesToBase64(bytes: Uint8Array): string;
38
+ declare function base64ToBytes(b64: string): Uint8Array;
39
+ /**
40
+ * 用指定/默认套件加密数据
41
+ * @returns v1 格式密文 base64 字符串
42
+ */
43
+ export declare function encrypt(options: EncryptOptions): Promise<string>;
44
+ /**
45
+ * 解密 v1 格式密文
46
+ * @returns 明文 Uint8Array
47
+ */
48
+ export declare function decrypt(options: DecryptOptions): Promise<Uint8Array>;
49
+ /**
50
+ * 列出可用的命名安全套件
51
+ */
52
+ export declare function getSupportedCipherSuites(options?: GetCipherSuitesOptions): Promise<CipherSuiteInfo[]>;
53
+ export { bytesToBase64, base64ToBytes };
54
+ export { isUserRejectedWalletAction };
@@ -3,3 +3,4 @@ export * from './provider';
3
3
  export * from './siwe';
4
4
  export * from './ucan';
5
5
  export * from './central';
6
+ export * from './encrypt';
@@ -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
@@ -18,6 +18,7 @@ export type InitWebDavStorageOptions = {
18
18
  root?: UcanRootProof;
19
19
  rootExpiresInMs?: number;
20
20
  invocationExpiresInMs?: number;
21
+ invocationSkewMs?: number;
21
22
  notBeforeMs?: number;
22
23
  fetcher?: typeof fetch;
23
24
  credentials?: RequestCredentials;
@@ -688,8 +688,9 @@ async function authFetch(input, init = {}, options = {}) {
688
688
  }
689
689
 
690
690
  const DEFAULT_SESSION_ID = 'default';
691
- const DEFAULT_SESSION_TTL = 24 * 60 * 60 * 1000;
692
- const DEFAULT_UCAN_TTL = 5 * 60 * 1000;
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;
693
694
  const DB_NAME = 'yeying-web3';
694
695
  const DB_STORE = 'ucan-sessions';
695
696
  const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
@@ -770,6 +771,30 @@ function normalizeUcanCapabilities(caps, options = {}) {
770
771
  function encodeJson(value) {
771
772
  return toBase64Url(textEncoder.encode(JSON.stringify(value)));
772
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
+ }
773
798
  function randomNonce(bytes = 16) {
774
799
  const buffer = new Uint8Array(bytes);
775
800
  crypto.getRandomValues(buffer);
@@ -778,8 +803,144 @@ function randomNonce(bytes = 16) {
778
803
  .join('');
779
804
  }
780
805
  function normalizeExpiry(exp, fallbackMs) {
806
+ if (typeof exp === 'number' && !Number.isNaN(exp))
807
+ return exp;
781
808
  return Date.now() + fallbackMs;
782
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
+ }
783
944
  function isSessionExpired(expiresAt, nowMs = Date.now()) {
784
945
  return typeof expiresAt === 'number' && nowMs >= expiresAt;
785
946
  }
@@ -876,7 +1037,7 @@ async function createLocalSession(options, record) {
876
1037
  buildDidKey(pair.publicKey),
877
1038
  ]);
878
1039
  const createdAt = Date.now();
879
- const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
1040
+ const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
880
1041
  const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
881
1042
  await writeSessionRecord({
882
1043
  id: sessionId,
@@ -1144,7 +1305,7 @@ async function createRootUcan(options) {
1144
1305
  const domain = options.domain || (typeof window !== 'undefined' ? window.location.host : '127.0.0.1');
1145
1306
  const uri = options.uri || (typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1');
1146
1307
  const nonce = options.nonce || randomNonce(8);
1147
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
1308
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_SESSION_TTL_MS);
1148
1309
  const nbf = options.notBeforeMs;
1149
1310
  const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
1150
1311
  if (!normalizedCapabilities.length) {
@@ -1228,7 +1389,7 @@ async function createDelegationUcan(options) {
1228
1389
  if (!normalizedCapabilities.length) {
1229
1390
  throw new Error('Missing UCAN capabilities');
1230
1391
  }
1231
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
1392
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
1232
1393
  const payload = {
1233
1394
  iss: issuer.did,
1234
1395
  aud: options.audience,
@@ -1250,7 +1411,7 @@ async function createInvocationUcan(options) {
1250
1411
  if (!normalizedCapabilities.length) {
1251
1412
  throw new Error('Missing UCAN capabilities');
1252
1413
  }
1253
- const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
1414
+ const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TOKEN_TTL_MS);
1254
1415
  const payload = {
1255
1416
  iss: issuer.did,
1256
1417
  aud: options.audience,
@@ -1261,30 +1422,84 @@ async function createInvocationUcan(options) {
1261
1422
  };
1262
1423
  return await signUcanPayload(payload, issuer);
1263
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
+ }
1264
1444
  async function authUcanFetch(input, init = {}, options = {}) {
1265
1445
  const fetcher = options.fetcher || fetch;
1266
- let token = options.ucan;
1267
- if (!token) {
1268
- if (!options.audience || !options.capabilities) {
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) {
1269
1453
  throw new Error('Missing UCAN audience or capabilities');
1270
1454
  }
1271
- token = await createInvocationUcan({
1455
+ token = await getOrCreateInvocationUcan({
1456
+ ucan: options.ucan,
1272
1457
  issuer: options.issuer,
1273
1458
  sessionId: options.sessionId,
1274
1459
  provider: options.provider,
1275
- audience: options.audience,
1276
- capabilities: options.capabilities,
1460
+ audience,
1461
+ capabilities,
1277
1462
  expiresInMs: options.expiresInMs,
1278
1463
  notBeforeMs: options.notBeforeMs,
1279
1464
  proofs: options.proofs,
1465
+ skewMs: options.skewMs,
1280
1466
  });
1281
1467
  }
1282
- const headers = new Headers(init.headers || {});
1283
- headers.set('Authorization', `Bearer ${token}`);
1284
- return fetcher(input, {
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, {
1285
1474
  ...init,
1286
- headers,
1475
+ headers: makeHeaders(token),
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),
1287
1501
  });
1502
+ return response;
1288
1503
  }
1289
1504
 
1290
1505
  const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
@@ -1595,6 +1810,122 @@ async function authCentralUcanFetch(input, init = {}, options = {}) {
1595
1810
  });
1596
1811
  }
1597
1812
 
1813
+ /**
1814
+ * 加密/解密 SDK API
1815
+ *
1816
+ * 调用钱包插件的 `yeying_encrypt` / `yeying_decrypt` / `yeying_getCipherSuites`
1817
+ * EIP-1193 自定义方法。命名安全套件 + 静默执行 + 站点授权模型同 UCAN。
1818
+ *
1819
+ * 数据密码(password 参数)由 DApp 自行管理与钱包密码独立;
1820
+ * plaintext 走 base64 字符串跨 message 边界传输。
1821
+ */
1822
+ class CipherError extends Error {
1823
+ type;
1824
+ code;
1825
+ originalError;
1826
+ constructor(message, type, code, originalError) {
1827
+ super(message);
1828
+ this.name = 'CipherError';
1829
+ this.type = type;
1830
+ this.code = code;
1831
+ this.originalError = originalError;
1832
+ }
1833
+ }
1834
+ function wrapCipherError(err) {
1835
+ const info = classifyWalletError(err);
1836
+ throw new CipherError(info.message, info.type, info.code, err);
1837
+ }
1838
+ function bytesToBase64(bytes) {
1839
+ if (typeof Buffer !== 'undefined') {
1840
+ return Buffer.from(bytes).toString('base64');
1841
+ }
1842
+ let binary = '';
1843
+ for (let i = 0; i < bytes.length; i += 1) {
1844
+ binary += String.fromCharCode(bytes[i]);
1845
+ }
1846
+ return btoa(binary);
1847
+ }
1848
+ function base64ToBytes(b64) {
1849
+ if (typeof Buffer !== 'undefined') {
1850
+ return new Uint8Array(Buffer.from(b64, 'base64'));
1851
+ }
1852
+ const binary = atob(b64);
1853
+ const out = new Uint8Array(binary.length);
1854
+ for (let i = 0; i < binary.length; i += 1)
1855
+ out[i] = binary.charCodeAt(i);
1856
+ return out;
1857
+ }
1858
+ /**
1859
+ * 用指定/默认套件加密数据
1860
+ * @returns v1 格式密文 base64 字符串
1861
+ */
1862
+ async function encrypt(options) {
1863
+ const provider = options.provider || (await requireProvider());
1864
+ try {
1865
+ const result = await provider.request({
1866
+ method: 'yeying_encrypt',
1867
+ params: [{
1868
+ data: options.data,
1869
+ password: options.password,
1870
+ suite: options.suite
1871
+ }]
1872
+ });
1873
+ if (!result || typeof result !== 'object' || typeof result.ciphertext !== 'string') {
1874
+ throw new Error('Invalid encrypt response: missing ciphertext');
1875
+ }
1876
+ return result.ciphertext;
1877
+ }
1878
+ catch (err) {
1879
+ wrapCipherError(err);
1880
+ }
1881
+ }
1882
+ /**
1883
+ * 解密 v1 格式密文
1884
+ * @returns 明文 Uint8Array
1885
+ */
1886
+ async function decrypt(options) {
1887
+ const provider = options.provider || (await requireProvider());
1888
+ try {
1889
+ const result = await provider.request({
1890
+ method: 'yeying_decrypt',
1891
+ params: [{
1892
+ ciphertext: options.ciphertext,
1893
+ password: options.password
1894
+ }]
1895
+ });
1896
+ if (!result || typeof result !== 'object' || typeof result.plaintext !== 'string') {
1897
+ throw new Error('Invalid decrypt response: missing plaintext');
1898
+ }
1899
+ const { plaintext, encoding } = result;
1900
+ if (encoding && encoding !== 'base64') {
1901
+ throw new Error(`Unsupported plaintext encoding: ${encoding}`);
1902
+ }
1903
+ return base64ToBytes(plaintext);
1904
+ }
1905
+ catch (err) {
1906
+ wrapCipherError(err);
1907
+ }
1908
+ }
1909
+ /**
1910
+ * 列出可用的命名安全套件
1911
+ */
1912
+ async function getSupportedCipherSuites(options = {}) {
1913
+ const provider = options.provider || (await requireProvider());
1914
+ try {
1915
+ const result = await provider.request({
1916
+ method: 'yeying_getCipherSuites',
1917
+ params: []
1918
+ });
1919
+ if (!result || typeof result !== 'object' || !Array.isArray(result.suites)) {
1920
+ throw new Error('Invalid getCipherSuites response: missing suites array');
1921
+ }
1922
+ return result.suites;
1923
+ }
1924
+ catch (err) {
1925
+ wrapCipherError(err);
1926
+ }
1927
+ }
1928
+
1598
1929
  function normalizeBaseUrl(baseUrl) {
1599
1930
  return baseUrl.replace(/\/+$/, '');
1600
1931
  }
@@ -1879,7 +2210,6 @@ function createWebDavClient(options) {
1879
2210
  }
1880
2211
 
1881
2212
  const tokenCache = new Map();
1882
- const TOKEN_SKEW_MS = 5000;
1883
2213
  const DEFAULT_APP_ACTION = 'write';
1884
2214
  const LOOPBACK_HOST_ALIASES = new Set([
1885
2215
  'localhost',
@@ -2021,64 +2351,19 @@ function resolveInvocationCaps(options, fallbackCaps) {
2021
2351
  const caps = options.invocationCapabilities || fallbackCaps;
2022
2352
  return ensureAppCapability(caps, options);
2023
2353
  }
2024
- function isTokenValid(entry, nowMs) {
2025
- if (!entry.exp)
2026
- return false;
2027
- if (entry.nbf && nowMs < entry.nbf)
2028
- return false;
2029
- return entry.exp - TOKEN_SKEW_MS > nowMs;
2030
- }
2031
- function decodeBase64Url(input) {
2032
- if (!input)
2033
- return null;
2034
- const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
2035
- const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
2036
- try {
2037
- if (typeof atob === 'function') {
2038
- return atob(padded);
2039
- }
2040
- }
2041
- catch {
2042
- // ignore
2043
- }
2044
- try {
2045
- const nodeBuffer = globalThis.Buffer;
2046
- if (nodeBuffer) {
2047
- return nodeBuffer.from(padded, 'base64').toString('utf8');
2048
- }
2049
- }
2050
- catch {
2051
- return null;
2052
- }
2053
- return null;
2054
- }
2055
- function decodeUcanPayload(token) {
2056
- const parts = token.split('.');
2057
- if (parts.length < 2)
2058
- return null;
2059
- const decoded = decodeBase64Url(parts[1]);
2060
- if (!decoded)
2061
- return null;
2062
- try {
2063
- return JSON.parse(decoded);
2064
- }
2065
- catch {
2066
- return null;
2067
- }
2068
- }
2069
2354
  async function getCachedInvocationToken(options) {
2070
2355
  const cacheKey = buildTokenCacheKey(options.issuer, options.audience, options.capabilities);
2071
2356
  const cached = tokenCache.get(cacheKey);
2072
2357
  const nowMs = Date.now();
2073
- if (cached && isTokenValid(cached, nowMs)) {
2074
- return cached.token;
2075
- }
2076
- const token = await createInvocationUcan({
2358
+ const token = await getOrCreateInvocationUcan({
2359
+ ucan: cached?.token,
2077
2360
  issuer: options.issuer,
2078
2361
  audience: options.audience,
2079
2362
  capabilities: options.capabilities,
2080
2363
  proofs: options.proofs,
2081
2364
  expiresInMs: options.expiresInMs,
2365
+ skewMs: options.skewMs,
2366
+ nowMs,
2082
2367
  notBeforeMs: options.notBeforeMs,
2083
2368
  });
2084
2369
  const payload = decodeUcanPayload(token);
@@ -2129,6 +2414,7 @@ async function initWebDavStorage(options) {
2129
2414
  capabilities: invocationCaps,
2130
2415
  proofs: [root],
2131
2416
  expiresInMs: options.invocationExpiresInMs,
2417
+ skewMs: options.invocationSkewMs,
2132
2418
  notBeforeMs: options.notBeforeMs,
2133
2419
  });
2134
2420
  const client = createWebDavClient({
@@ -2185,5 +2471,5 @@ async function initDappSession(options) {
2185
2471
  return result;
2186
2472
  }
2187
2473
 
2188
- export { WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, classifyWalletError, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, deriveAppIdFromHost, deriveAppIdFromLocation, focusPendingApproval, 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 };
2474
+ export { CipherError, DEFAULT_UCAN_SESSION_TTL_MS, DEFAULT_UCAN_TOKEN_SKEW_MS, DEFAULT_UCAN_TOKEN_TTL_MS, WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, base64ToBytes, bytesToBase64, classifyUcanAuthError, classifyWalletError, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, decodeUcanPayload, decrypt, deriveAppIdFromHost, deriveAppIdFromLocation, encrypt, focusPendingApproval, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateInvocationUcan, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getSupportedCipherSuites, 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 };
2189
2475
  //# sourceMappingURL=web3-bs.esm.js.map