@yeying-community/web3-bs 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 YeYing Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # YeYing Inject Wallet SDK
2
+
3
+ 轻量级注入钱包 SDK,专注浏览器端 EIP-1193 Provider。默认优先选择 YeYing Wallet(支持 EIP-6963 多钱包发现)。
4
+ 仅支持浏览器环境(依赖 `window` / `localStorage` / `fetch`)。
5
+
6
+ ## 安装
7
+
8
+ ```bash
9
+ npm install @yeying-community/web3-bs
10
+ ```
11
+
12
+ ## 钱包交互 API
13
+
14
+ ### Provider 发现
15
+ - `getProvider(options?)`
16
+ - 自动监听 `eip6963:announceProvider`
17
+ - 默认优先 YeYing(`isYeYing` 或 `rdns: io.github.yeying`)
18
+
19
+ ### 核心方法
20
+ - `requestAccounts({ provider? })`
21
+ - `getAccounts(provider?)`
22
+ - `getChainId(provider?)`
23
+ - `getBalance(provider?, address?, blockTag?)`
24
+ - `signMessage({ provider?, message, address?, method? })`
25
+ - `method` 默认 `personal_sign`
26
+
27
+ ### 事件
28
+ - `onAccountsChanged(provider, handler)`
29
+ - `onChainChanged(provider, handler)`
30
+
31
+ ## 后端交互 API(推荐标准)
32
+ OpenAPI 规范:`docs/openapi.yaml`
33
+
34
+ ### 响应封装(严格)
35
+ 所有响应必须使用以下封装结构:
36
+
37
+ ```json
38
+ {
39
+ "code": 0,
40
+ "message": "ok",
41
+ "data": { "...": "..." },
42
+ "timestamp": 1730000000000
43
+ }
44
+ ```
45
+
46
+ - `code = 0` 表示成功
47
+ - `code != 0` 表示失败;`data` 应为 `null`
48
+
49
+ ### 1) 获取 Challenge
50
+
51
+ `POST /api/v1/public/auth/challenge`
52
+
53
+ 请求
54
+ ```json
55
+ { "address": "0xabc123..." }
56
+ ```
57
+
58
+ 响应
59
+ ```json
60
+ {
61
+ "code": 0,
62
+ "message": "ok",
63
+ "data": {
64
+ "address": "0xabc123...",
65
+ "challenge": "Sign to login...",
66
+ "nonce": "random",
67
+ "issuedAt": 1730000000000,
68
+ "expiresAt": 1730000300000
69
+ },
70
+ "timestamp": 1730000000000
71
+ }
72
+ ```
73
+
74
+ ### 2) 验证签名
75
+
76
+ `POST /api/v1/public/auth/verify`
77
+
78
+ 请求
79
+ ```json
80
+ { "address": "0xabc123...", "signature": "0x..." }
81
+ ```
82
+
83
+ 响应
84
+ ```json
85
+ {
86
+ "code": 0,
87
+ "message": "ok",
88
+ "data": {
89
+ "address": "0xabc123...",
90
+ "token": "access-token",
91
+ "expiresAt": 1730086400000,
92
+ "refreshExpiresAt": 1730686400000
93
+ },
94
+ "timestamp": 1730000000000
95
+ }
96
+ ```
97
+
98
+ 说明
99
+ - `verify` 应设置 httpOnly 的 `refresh_token` Cookie(用于刷新 access token)。
100
+ - 访问受保护接口时,前端使用 `Authorization: Bearer <access-token>`。
101
+
102
+ ### 3) 刷新 Access Token
103
+
104
+ `POST /api/v1/public/auth/refresh`
105
+
106
+ 请求
107
+ - 依赖 httpOnly `refresh_token` Cookie
108
+
109
+ 响应
110
+ ```json
111
+ {
112
+ "code": 0,
113
+ "message": "ok",
114
+ "data": {
115
+ "address": "0xabc123...",
116
+ "token": "new-access-token",
117
+ "expiresAt": 1730086400000,
118
+ "refreshExpiresAt": 1730686400000
119
+ },
120
+ "timestamp": 1730000000000
121
+ }
122
+ ```
123
+
124
+ ### 4) 退出登录
125
+
126
+ `POST /api/v1/public/auth/logout`
127
+
128
+ 响应
129
+ ```json
130
+ {
131
+ "code": 0,
132
+ "message": "ok",
133
+ "data": { "logout": true },
134
+ "timestamp": 1730000000000
135
+ }
136
+ ```
137
+
138
+ ### SDK 绑定
139
+ - `loginWithChallenge` 会从 `data.challenge` 读取 challenge,从 `data.token` 读取 token。
140
+ - `refreshAccessToken` 调用 `/refresh` 并更新 access token(默认 `credentials: 'include'`)。
141
+ - `authFetch` 会自动携带 access token,遇到 401 会尝试刷新再重试一次。
142
+ - `logout` 会清理刷新 Cookie 并清空本地 access token(若设置 `storeToken`)。
143
+
144
+ ## 示例
145
+
146
+ - Frontend Dapp (HTML): `examples/frontend/dapp.html`
147
+ - Frontend Dapp (TS module): `examples/frontend/main.ts`
148
+ - Backend server (Node): `examples/backend/node/server.js`
149
+ - Backend server (Go): `examples/backend/go/main.go`
150
+ - Backend server (Python): `examples/backend/python/app.py`
151
+ - Backend server (Java): `examples/backend/java/src/main/java/com/yeying/demo/AuthServer.java`
152
+
153
+ ## UCAN 授权(SIWE Bridge)
154
+
155
+ SDK 通过 YeYing 钱包生成 UCAN Session Key 并完成签名(由钱包后台隔离私钥)。
156
+ Root UCAN 基于 SIWE 签名,
157
+ 用于多后端统一鉴权。UCAN 以 `Authorization: Bearer <UCAN>` 发送,后端会验证 UCAN 证明链与能力。
158
+
159
+ 新增 API:
160
+ - `createUcanSession(options?)`
161
+ - `createRootUcan(options)`
162
+ - `createDelegationUcan(options)`
163
+ - `createInvocationUcan(options)`
164
+ - `authUcanFetch(url, init?, options?)`
165
+
166
+ 示例:
167
+ ```ts
168
+ const session = await createUcanSession();
169
+ const root = await createRootUcan({
170
+ provider,
171
+ session,
172
+ capabilities: [{ resource: 'profile', action: 'read' }],
173
+ });
174
+
175
+ const ucan = await createInvocationUcan({
176
+ issuer: session,
177
+ audience: 'did:web:localhost:3203',
178
+ capabilities: [{ resource: 'profile', action: 'read' }],
179
+ proofs: [root],
180
+ });
181
+
182
+ const res = await authUcanFetch('http://localhost:3203/api/v1/private/profile', { method: 'GET' }, { ucan });
183
+ console.log(await res.json());
184
+ ```
185
+
186
+ 后端默认要求的能力为 `resource=profile`、`action=read`,可通过环境变量覆盖:
187
+ - `UCAN_AUD`:服务 DID(默认 `did:web:localhost:3203`)
188
+ - `UCAN_RESOURCE`:资源(默认 `profile`)
189
+ - `UCAN_ACTION`:动作(默认 `read`)
190
+
191
+ 提示:如需可转授权,使用 `createDelegationUcan` 创建委任链,再用被委任的 Key 生成 Invocation UCAN。
192
+
193
+ ## WebDAV 存储(Storage)
194
+
195
+ 提供基于 WebDAV 服务的文件操作封装(上传/下载/删除/目录等),适配 `webdav` 项目 API。
196
+
197
+ 示例:
198
+ ```ts
199
+ import { createWebDavClient, loginWithChallenge } from '@yeying-community/web3-bs';
200
+
201
+ const login = await loginWithChallenge({
202
+ provider,
203
+ baseUrl: 'http://localhost:6065/api/v1/public/auth',
204
+ storeToken: false,
205
+ });
206
+
207
+ const client = createWebDavClient({
208
+ baseUrl: 'http://localhost:6065',
209
+ token: login.token,
210
+ prefix: '/',
211
+ });
212
+
213
+ const listingXml = await client.listDirectory('/');
214
+ await client.upload('/docs/hello.txt', 'Hello WebDAV');
215
+ const content = await client.downloadText('/docs/hello.txt');
216
+ ```
217
+
218
+ 常用方法:
219
+ - `listDirectory(path?, depth?)`(PROPFIND,返回 XML 文本)
220
+ - `upload(path, content, contentType?)`
221
+ - `download(path)` / `downloadText(path)` / `downloadArrayBuffer(path)`
222
+ - `createDirectory(path)`(MKCOL)
223
+ - `remove(path)`(DELETE)
224
+ - `move(path, destination, overwrite?)`
225
+ - `copy(path, destination, overwrite?)`
226
+ - `getQuota()` / `listRecycle()` / `recoverRecycle(hash)` / `deleteRecycle(hash)` / `clearRecycle()`
227
+
228
+ ## 本地验证
229
+
230
+ 1. 构建 SDK:`npm run build`
231
+ 2. 启动后端:`node examples/backend/node/server.js`
232
+ 3. 启动前端:`python3 -m http.server 8001`,然后在浏览器输入访问地址:`http://localhost:8001/examples/frontend/dapp.html`
233
+ 4. 确保安装 YeYing 钱包扩展插件
234
+ 5. 点击:`Detect Provider` → `Connect Wallet` → `Login`
235
+
236
+ 提示:如果前端来自其他域名,请设置
237
+ `COOKIE_SAMESITE=none` 且 `COOKIE_SECURE=true` 并使用 HTTPS,
238
+ 以便 `refresh_token` Cookie 能随 `credentials: 'include'` 发送。
239
+
240
+ ## 多后端联调(不同端口)
241
+
242
+ 可同时启动多语言后端(不同端口),验证 UCAN 多后端授权:
243
+
244
+ ```bash
245
+ ./scripts/backend.sh start all
246
+ ./scripts/backend.sh start all --setup
247
+ ```
248
+
249
+ 默认端口:
250
+ - Go `3201`
251
+ - Java `3202`
252
+ - Node `3203`
253
+ - Python `3204`
254
+
255
+ 前端调用不同端口的后端时:
256
+ - 将前端 Origin 加入 `CORS_ORIGINS`(例如 `http://localhost:3203`)
257
+ - UCAN 调用的 `audience` 与后端 `UCAN_AUD` 一致(如 `did:web:localhost:3202`)
258
+
259
+ 提示:`examples/frontend/dapp.html` 已内置多后端列表,可在一次 UCAN 授权后依次调用多个服务。
260
+
261
+ ## 常见问题
262
+
263
+ ### 刷新token失败
264
+
265
+ 清理旧 Cookie 后重新登录:在浏览器 DevTools → Application → Cookies → http://localhost:8001 删除 refresh_token,再点 Login 后再点 Refresh Token。
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './provider';
3
+ export * from './siwe';
4
+ export * from './ucan';
@@ -0,0 +1,10 @@
1
+ import { Eip1193Provider, ProviderDiscoveryOptions, ProviderInfo, RequestAccountsOptions } from './types';
2
+ export declare function isYeYingProvider(provider?: Eip1193Provider | null, info?: ProviderInfo): boolean;
3
+ export declare function getProvider(options?: ProviderDiscoveryOptions): Promise<Eip1193Provider | null>;
4
+ export declare function requireProvider(options?: ProviderDiscoveryOptions): Promise<Eip1193Provider>;
5
+ export declare function requestAccounts(options?: RequestAccountsOptions): Promise<string[]>;
6
+ export declare function getAccounts(provider?: Eip1193Provider): Promise<string[]>;
7
+ export declare function getChainId(provider?: Eip1193Provider): Promise<string | null>;
8
+ export declare function getBalance(provider?: Eip1193Provider, address?: string, blockTag?: string): Promise<string>;
9
+ export declare function onAccountsChanged(provider: Eip1193Provider, handler: (accounts: string[]) => void): () => void;
10
+ export declare function onChainChanged(provider: Eip1193Provider, handler: (chainId: string) => void): () => void;
@@ -0,0 +1,17 @@
1
+ import { AuthBaseOptions, AuthFetchOptions, AuthTokenResult, LoginWithChallengeOptions, LogoutOptions, RefreshAccessTokenOptions, SignMessageOptions } from './types';
2
+ export declare function getAccessToken(options?: AuthBaseOptions): string | null;
3
+ export declare function setAccessToken(token: string | null, options?: AuthBaseOptions): void;
4
+ export declare function clearAccessToken(options?: AuthBaseOptions): void;
5
+ export declare function signMessage(options: SignMessageOptions): Promise<string>;
6
+ export declare function loginWithChallenge(options?: LoginWithChallengeOptions): Promise<{
7
+ token: string;
8
+ address: string;
9
+ signature: string;
10
+ challenge: string;
11
+ response: unknown;
12
+ }>;
13
+ export declare function refreshAccessToken(options?: RefreshAccessTokenOptions): Promise<AuthTokenResult>;
14
+ export declare function logout(options?: LogoutOptions): Promise<{
15
+ response: unknown;
16
+ }>;
17
+ export declare function authFetch(input: RequestInfo | URL, init?: RequestInit, options?: AuthFetchOptions): Promise<Response>;
@@ -0,0 +1,71 @@
1
+ export type JsonRpcRequest = {
2
+ method: string;
3
+ params?: unknown[] | Record<string, unknown>;
4
+ };
5
+ export interface Eip1193Provider {
6
+ request: (args: JsonRpcRequest) => Promise<unknown>;
7
+ on?: {
8
+ (event: 'accountsChanged', listener: (accounts: string[]) => void): void;
9
+ (event: 'chainChanged', listener: (chainId: string) => void): void;
10
+ (event: string, listener: (...args: unknown[]) => void): void;
11
+ };
12
+ removeListener?: {
13
+ (event: 'accountsChanged', listener: (accounts: string[]) => void): void;
14
+ (event: 'chainChanged', listener: (chainId: string) => void): void;
15
+ (event: string, listener: (...args: unknown[]) => void): void;
16
+ };
17
+ isMetaMask?: boolean;
18
+ isYeYing?: boolean;
19
+ }
20
+ export interface ProviderInfo {
21
+ uuid?: string;
22
+ name?: string;
23
+ icon?: string;
24
+ rdns?: string;
25
+ }
26
+ export interface Eip6963ProviderDetail {
27
+ info: ProviderInfo;
28
+ provider: Eip1193Provider;
29
+ }
30
+ export interface ProviderDiscoveryOptions {
31
+ timeoutMs?: number;
32
+ preferYeYing?: boolean;
33
+ }
34
+ export interface RequestAccountsOptions {
35
+ provider?: Eip1193Provider;
36
+ }
37
+ export interface SignMessageOptions {
38
+ provider?: Eip1193Provider;
39
+ message: string;
40
+ address?: string;
41
+ method?: 'personal_sign' | 'eth_sign';
42
+ }
43
+ export interface AuthBaseOptions {
44
+ baseUrl?: string;
45
+ fetcher?: typeof fetch;
46
+ credentials?: RequestCredentials;
47
+ storeToken?: boolean;
48
+ tokenStorageKey?: string;
49
+ }
50
+ export interface LoginWithChallengeOptions extends AuthBaseOptions {
51
+ provider?: Eip1193Provider;
52
+ address?: string;
53
+ challengePath?: string;
54
+ verifyPath?: string;
55
+ signMethod?: 'personal_sign' | 'eth_sign';
56
+ }
57
+ export interface RefreshAccessTokenOptions extends AuthBaseOptions {
58
+ refreshPath?: string;
59
+ }
60
+ export interface LogoutOptions extends AuthBaseOptions {
61
+ logoutPath?: string;
62
+ }
63
+ export interface AuthFetchOptions extends AuthBaseOptions {
64
+ refreshPath?: string;
65
+ accessToken?: string | null;
66
+ retryOnUnauthorized?: boolean;
67
+ }
68
+ export interface AuthTokenResult {
69
+ token: string;
70
+ response: unknown;
71
+ }
@@ -0,0 +1,92 @@
1
+ import { Eip1193Provider } from './types';
2
+ export type UcanCapability = {
3
+ resource: string;
4
+ action: string;
5
+ };
6
+ export type UcanRootProof = {
7
+ type: 'siwe';
8
+ iss: string;
9
+ aud: string;
10
+ cap: UcanCapability[];
11
+ exp: number;
12
+ nbf?: number;
13
+ siwe: {
14
+ message: string;
15
+ signature: string;
16
+ };
17
+ };
18
+ export type UcanProof = UcanRootProof | string;
19
+ export type UcanTokenPayload = {
20
+ iss: string;
21
+ aud: string;
22
+ cap: UcanCapability[];
23
+ exp: number;
24
+ nbf?: number;
25
+ prf: UcanProof[];
26
+ };
27
+ export type UcanSessionRecord = {
28
+ id: string;
29
+ did: string;
30
+ createdAt: number;
31
+ expiresAt: number | null;
32
+ root?: UcanRootProof;
33
+ };
34
+ export type UcanSessionKey = {
35
+ id: string;
36
+ did: string;
37
+ createdAt: number;
38
+ expiresAt: number | null;
39
+ signer?: (signingInput: string, payload: UcanTokenPayload) => Promise<string>;
40
+ privateKey?: CryptoKey;
41
+ };
42
+ export type CreateUcanSessionOptions = {
43
+ id?: string;
44
+ expiresInMs?: number;
45
+ forceNew?: boolean;
46
+ provider?: Eip1193Provider;
47
+ };
48
+ export type CreateRootUcanOptions = {
49
+ provider?: Eip1193Provider;
50
+ address?: string;
51
+ session?: UcanSessionKey;
52
+ sessionId?: string;
53
+ capabilities: UcanCapability[];
54
+ expiresInMs?: number;
55
+ domain?: string;
56
+ uri?: string;
57
+ chainId?: string;
58
+ statement?: string;
59
+ nonce?: string;
60
+ notBeforeMs?: number;
61
+ };
62
+ export type CreateUcanTokenOptions = {
63
+ issuer?: UcanSessionKey;
64
+ sessionId?: string;
65
+ provider?: Eip1193Provider;
66
+ audience: string;
67
+ capabilities: UcanCapability[];
68
+ expiresInMs?: number;
69
+ notBeforeMs?: number;
70
+ proofs?: UcanProof[];
71
+ };
72
+ export type UcanFetchOptions = {
73
+ ucan?: string;
74
+ audience?: string;
75
+ capabilities?: UcanCapability[];
76
+ issuer?: UcanSessionKey;
77
+ sessionId?: string;
78
+ provider?: Eip1193Provider;
79
+ proofs?: UcanProof[];
80
+ expiresInMs?: number;
81
+ notBeforeMs?: number;
82
+ fetcher?: typeof fetch;
83
+ };
84
+ export declare function getUcanSession(id?: string, provider?: Eip1193Provider): Promise<UcanSessionKey | null>;
85
+ export declare function createUcanSession(options?: CreateUcanSessionOptions): Promise<UcanSessionKey>;
86
+ export declare function clearUcanSession(id?: string): Promise<void>;
87
+ export declare function storeUcanRoot(root: UcanRootProof, id?: string): Promise<void>;
88
+ export declare function getStoredUcanRoot(id?: string): Promise<UcanRootProof | null>;
89
+ export declare function createRootUcan(options: CreateRootUcanOptions): Promise<UcanRootProof>;
90
+ export declare function createDelegationUcan(options: CreateUcanTokenOptions): Promise<string>;
91
+ export declare function createInvocationUcan(options: CreateUcanTokenOptions): Promise<string>;
92
+ export declare function authUcanFetch(input: RequestInfo | URL, init?: RequestInit, options?: UcanFetchOptions): Promise<Response>;
@@ -0,0 +1,2 @@
1
+ export * from './auth';
2
+ export * from './storage';
@@ -0,0 +1 @@
1
+ export * from './webdav';
@@ -0,0 +1,54 @@
1
+ export type WebDavAuth = {
2
+ type: 'bearer';
3
+ token: string;
4
+ } | {
5
+ type: 'basic';
6
+ username: string;
7
+ password: string;
8
+ };
9
+ export type WebDavClientOptions = {
10
+ baseUrl: string;
11
+ prefix?: string;
12
+ auth?: WebDavAuth;
13
+ token?: string;
14
+ fetcher?: typeof fetch;
15
+ credentials?: RequestCredentials;
16
+ };
17
+ export type WebDavRequestOptions = {
18
+ headers?: Record<string, string>;
19
+ auth?: WebDavAuth;
20
+ token?: string;
21
+ depth?: number | 'infinity';
22
+ overwrite?: boolean;
23
+ contentType?: string;
24
+ signal?: AbortSignal;
25
+ };
26
+ export declare class WebDavClient {
27
+ private baseUrl;
28
+ private prefix;
29
+ private auth?;
30
+ private token?;
31
+ private fetcher;
32
+ private credentials?;
33
+ constructor(options: WebDavClientOptions);
34
+ setToken(token: string | null): void;
35
+ setAuth(auth?: WebDavAuth): void;
36
+ private buildUrl;
37
+ private buildHeaders;
38
+ request(method: string, path: string, body?: BodyInit | null, options?: WebDavRequestOptions): Promise<Response>;
39
+ listDirectory(path?: string, depth?: number | 'infinity'): Promise<string>;
40
+ download(path: string): Promise<Response>;
41
+ downloadText(path: string): Promise<string>;
42
+ downloadArrayBuffer(path: string): Promise<ArrayBuffer>;
43
+ upload(path: string, content: BodyInit, contentType?: string): Promise<Response>;
44
+ createDirectory(path: string): Promise<Response>;
45
+ remove(path: string): Promise<Response>;
46
+ move(path: string, destination: string, overwrite?: boolean): Promise<Response>;
47
+ copy(path: string, destination: string, overwrite?: boolean): Promise<Response>;
48
+ getQuota(): Promise<unknown>;
49
+ listRecycle(): Promise<unknown>;
50
+ recoverRecycle(hash: string): Promise<unknown>;
51
+ deleteRecycle(hash: string): Promise<unknown>;
52
+ clearRecycle(): Promise<unknown>;
53
+ }
54
+ export declare function createWebDavClient(options: WebDavClientOptions): WebDavClient;