@yeying-community/web3-bs 1.0.6 → 1.0.7

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 CHANGED
@@ -1,15 +1,14 @@
1
1
  # YeYing Browser DApp Access SDK
2
2
 
3
- 浏览器端 DApp 接入 SDK,用于统一封装钱包连接、会话维护、SIWE 登录、UCAN 多后端授权、中心化服务接入和 WebDAV 存储访问。
4
- 它优先面向 EIP-1193 / EIP-6963 钱包生态,可对接 YeYing、MetaMask 等浏览器钱包;也可对接能暴露 EIP-1193 能力的 App 钱包或中心化认证服务。仅支持浏览器环境(依赖 `window` / `localStorage` / `fetch`)。
3
+ `web3-bs` 是浏览器端 DApp 接入 SDK,用于统一封装:
5
4
 
6
- 功能要点:
7
- - 浏览器端 EIP-1193 Provider 辅助库
8
- - 默认优先 YeYing Wallet,也兼容其他 EIP-1193 钱包
9
- - 内置 `signMessage` / `loginWithChallenge` / `refresh` / `logout` 等方法
10
- - 支持 UCAN Session + SIWE Bridge,用于多后端授权
11
- - 支持中心化 UCAN Demo API:`createCentralSession` / `issueCentralUcan` / `authCentralUcanFetch`
12
- - 统一封装前端连接会话、token 会话与 WebDAV 访问会话,降低 DApp 集成成本
5
+ - 钱包连接(EIP-1193 / EIP-6963)
6
+ - SIWE challenge 登录与 JWT 鉴权请求
7
+ - UCAN 多后端授权(钱包 UCAN RPC 优先,失败可回退本地 Ed25519 session)
8
+ - 中心化 UCAN 发行服务接入
9
+ - WebDAV 文件访问与应用目录初始化
10
+
11
+ 仅支持浏览器环境(依赖 `window` / `localStorage` / `fetch`)。
13
12
 
14
13
  ## 安装
15
14
 
@@ -17,82 +16,112 @@
17
16
  npm install @yeying-community/web3-bs
18
17
  ```
19
18
 
20
- 更多集成与流程说明见 `docs/sdk-design.md`。
21
- 推荐先读:
22
- - `docs/README.md`
23
- - `docs/positioning.md`
24
- - `docs/quickstart.md`
25
- - `docs/capability-matrix.md`
19
+ ## 能力概览
26
20
 
27
- ## 接入路线
21
+ ### 1) Provider 与账户
28
22
 
29
- - 钱包插件路线:适合浏览器内已有 YeYing、MetaMask 等插件钱包的 DApp;优先使用 `getProvider`、`loginWithChallenge`,并可扩展到 UCAN 多后端授权(钱包 UCAN RPC 优先,不支持时 SDK 自动回退本地 Ed25519 session)
30
- - App 钱包路线:适合移动端 Web;前提是钱包 App 或桥接层能暴露 EIP-1193 provider,通常使用 `requestAccounts`、`signMessage`、`loginWithChallenge`
31
- - 中心化服务路线:适合无钱包、无插件或更关注接入成本的场景;使用 JWT 或中心化 UCAN,通常组合 `setAccessToken`、`authFetch`、`createCentralSession`、`issueCentralUcan`
23
+ - `getProvider` / `requireProvider`
24
+ - `requestAccounts` / `getAccounts` / `getPreferredAccount` / `watchAccounts`
25
+ - `getChainId` / `getBalance`
26
+ - `onAccountsChanged` / `onChainChanged`
32
27
 
33
- 能力对比见 `docs/capability-matrix.md`,详细接入步骤见 `docs/quickstart.md`。
28
+ ### 2) SIWE + JWT
34
29
 
35
- ## 示例
30
+ - `signMessage`
31
+ - `loginWithChallenge`
32
+ - `authFetch`
33
+ - `refreshAccessToken`
34
+ - `logout`
35
+ - `getAccessToken` / `setAccessToken` / `clearAccessToken`
36
36
 
37
- - Frontend Dapp (HTML): `examples/frontend/dapp.html`
38
- - Frontend Mobile Central UCAN Demo: `examples/frontend/mobile-central-ucan.html`
39
- - Frontend Dapp (TS module): `examples/frontend/main.ts`
40
- - Backend server (Node): `examples/backend/node/server.js`
41
- - Backend server (Go): `examples/backend/go/main.go`
42
- - Backend server (Python): `examples/backend/python/app.py`
43
- - Backend server (Java): `examples/backend/java/src/main/java/com/yeying/demo/AuthServer.java`
37
+ ### 3) UCAN(钱包优先 + 本地回退)
44
38
 
45
- ## 本地验证
39
+ - `createUcanSession` / `getUcanSession`
40
+ - `getOrCreateUcanRoot` / `createRootUcan`
41
+ - `createDelegationUcan` / `createInvocationUcan`
42
+ - `authUcanFetch`
43
+ - `normalizeUcanCapabilities`
46
44
 
47
- 1. 构建 SDK:`npm run build`
48
- 2. 启动后端:`scripts/backend.sh start node`(将 `node` 替换为 `go` / `python` / `java`)
49
- 3. 启动前端:`python3 -m http.server 8001 --bind 127.0.0.1`
50
- 4. 访问:`http://127.0.0.1:8001/examples/frontend/dapp.html`
51
- 5. 确保安装 YeYing 钱包扩展插件
52
- 6. 点击:`Detect Provider` → `Connect Wallet` → `Login`
45
+ 说明:
46
+ - 能调用 `yeying_ucan_session` / `yeying_ucan_sign` 时,优先用钱包侧 UCAN session 签名。
47
+ - 不支持上述钱包 RPC 时,SDK 会回退到浏览器本地 Ed25519 session(IndexedDB 持久化)。
53
48
 
54
- 提示:如果前端来自其他域名,请设置
55
- `COOKIE_SAMESITE=none` 且 `COOKIE_SECURE=true` 并使用 HTTPS,
56
- 以便 `refresh_token` Cookie 能随 `credentials: 'include'` 发送。
49
+ ### 4) 中心化 UCAN
57
50
 
58
- ## 后端脚本用法
51
+ - `getCentralIssuerInfo`
52
+ - `createCentralSession`
53
+ - `issueCentralUcan`
54
+ - `createAndIssueCentralUcan`
55
+ - `authCentralUcanFetch`
56
+ - `getCentralSessionToken` / `setCentralSessionToken` / `clearCentralSessionToken`
59
57
 
60
- 通用格式:
61
- `./scripts/backend.sh <start|stop|restart|status> <node|go|python|java|all> [--setup] [--no-stop]`
58
+ ### 5) WebDAV 与 DApp 会话编排
62
59
 
63
- 示例:
64
- ```bash
65
- # 启动 Node 后端
66
- ./scripts/backend.sh start node
60
+ - `createWebDavClient`
61
+ - `initWebDavStorage`(自动生成/复用 WebDAV Invocation UCAN,可自动创建应用目录)
62
+ - `initDappSession`(SIWE 登录 + WebDAV UCAN 初始化)
63
+ - `deriveAppIdFromLocation` / `deriveAppIdFromHost`
67
64
 
68
- # 启动 Go 后端并安装依赖/构建
69
- ./scripts/backend.sh start go --setup
65
+ ## 常见接入方式
70
66
 
71
- # 查看全部后端状态
72
- ./scripts/backend.sh status all
67
+ ### 单后端登录(SIWE + JWT)
73
68
 
74
- # 重启 Python 后端
75
- ./scripts/backend.sh restart python
69
+ ```ts
70
+ import { loginWithChallenge, authFetch } from '@yeying-community/web3-bs';
76
71
 
77
- # 停止所有后端
78
- ./scripts/backend.sh stop all
72
+ await loginWithChallenge({
73
+ baseUrl: 'http://localhost:3203/api/v1/public/auth',
74
+ storeToken: false,
75
+ });
79
76
 
80
- # 同时启动多个后端(不停止已运行的服务)
81
- ./scripts/backend.sh start all --no-stop
77
+ const res = await authFetch(
78
+ 'http://localhost:3203/api/v1/public/profile',
79
+ { method: 'GET' },
80
+ { baseUrl: 'http://localhost:3203/api/v1/public/auth', storeToken: false }
81
+ );
82
82
  ```
83
83
 
84
- 说明:
85
- - 日志位于 `.tmp/backend-logs`
86
- - 进程 PID 位于 `.tmp/backend-pids`
87
- - 可通过环境变量传递端口/秘钥/TTL 等配置(如 `PORT`、`JWT_SECRET` 等)
84
+ ### 多后端 UCAN + WebDAV
85
+
86
+ ```ts
87
+ import { initWebDavStorage, deriveAppIdFromLocation } from '@yeying-community/web3-bs';
88
+
89
+ const appId = deriveAppIdFromLocation(window.location) || 'localhost-8001';
90
+ const webdav = await initWebDavStorage({
91
+ baseUrl: 'http://localhost:6065',
92
+ audience: 'did:web:localhost:6065',
93
+ appId,
94
+ capabilities: [{ with: `app:all:${appId}`, can: 'write' }],
95
+ });
96
+
97
+ await webdav.client.upload(`${webdav.appDir}/hello.txt`, 'Hello WebDAV');
98
+ ```
99
+
100
+ ## Demo
101
+
102
+ - 前端 Demo(双 Tab:单后端 SIWE / 多后端 UCAN+WebDAV):
103
+ `examples/frontend/dapp.html`
104
+ - 前端 TS 示例:`examples/frontend/main.ts`
105
+ - 多语言后端示例:
106
+ - Node: `examples/backend/node/server.js`
107
+ - Go: `examples/backend/go/main.go`
108
+ - Java: `examples/backend/java/src/main/java/com/yeying/demo/AuthServer.java`
109
+ - Python: `examples/backend/python/app.py`
110
+
111
+ ## 本地联调
88
112
 
89
- ## 多后端联调(不同端口)
113
+ 1. 构建 SDK:`npm run build`
114
+ 2. 启动后端(示例):
115
+ `./scripts/backend.sh start nodejs`
116
+ 3. 启动前端:
117
+ `python3 -m http.server 8001 --bind 127.0.0.1`
118
+ 4. 打开:
119
+ `http://127.0.0.1:8001/examples/frontend/dapp.html`
90
120
 
91
- 可同时启动多语言后端(不同端口),验证 UCAN 多后端授权:
121
+ 多后端联调:
92
122
 
93
123
  ```bash
94
124
  ./scripts/backend.sh start all
95
- ./scripts/backend.sh start all --setup
96
125
  ```
97
126
 
98
127
  默认端口:
@@ -101,56 +130,28 @@ npm install @yeying-community/web3-bs
101
130
  - Node `3203`
102
131
  - Python `3204`
103
132
 
104
- 前端调用不同端口的后端时:
105
- - 将前端 Origin 加入 `CORS_ORIGINS`(例如 `http://127.0.0.1:3203`)
106
- - UCAN 调用的 `audience` 与后端 `UCAN_AUD` 一致(如 `did:web:127.0.0.1:3202`)
107
-
108
- 提示:`examples/frontend/dapp.html` 已内置多后端列表,可在一次 UCAN 授权后依次调用多个服务。
109
- 提示:Demo 的 UCAN 流程按协议链路实现为 `Root(SIWE) -> Delegation -> Invocation`,便于按步骤理解多后端授权。
110
-
111
- ## 常见问题
133
+ ## CORS 注意事项
112
134
 
113
- ### 如何快速验证能力矩阵(钱包 UCAN + 本地回退)
114
-
115
- 执行:
135
+ 若你用 `http://[::]:8001` 打开页面,请确保后端 CORS 白名单包含该 Origin。
136
+ 建议统一加入:
116
137
 
117
138
  ```bash
118
- npm run check:capabilities
139
+ CORS_ORIGINS=http://localhost:8001,http://127.0.0.1:8001,http://[::]:8001
119
140
  ```
120
141
 
121
- 该脚本会自动验证:
122
- - 钱包 UCAN RPC 路径(`createUcanSession` 返回 `source=wallet`)
123
- - UCAN 本地回退路径(钱包不支持 `yeying_ucan_*` 时 `source=local`)
124
- - `createRootUcan` + `createInvocationUcan` 两条链路
125
- - `initWebDavStorage` 的 UCAN 自动初始化
126
- - 中心化 UCAN API 是否已导出(`createCentralSession` / `issueCentralUcan` / `authCentralUcanFetch`)
127
-
128
- ### 刷新token失败
129
-
130
- 清理旧 Cookie 后重新登录:在浏览器 DevTools → Application → Cookies → http://127.0.0.1:8001 删除 refresh_token,再点 Login 后再点 Refresh Token。
142
+ ## 本地软链接(开发)
131
143
 
132
- ### WebDAV 提示 invalid token
144
+ 将本仓库作为本地依赖链接到你的 DApp:
133
145
 
134
- 报错示例:`authentication failed` / `invalid token`(通常来自 WebDAV 服务端鉴权中间件)。
135
-
136
- 排查与解决:
137
- - 确认 `baseUrl` 指向 WebDAV 服务(默认 `http://127.0.0.1:6065`),不要误用 320x 认证后端。
138
- - `baseUrl` 不应包含任何路径;若服务挂载在子路径,请用 `prefix` 指定(例如 `/dav`)。
139
- - `audience` 必须与 WebDAV 服务端 `web3.ucan.audience` 一致(例如 `did:web:127.0.0.1:6065`)。
140
- - `capabilities` 与服务端要求一致(优先 `web3.ucan.required_capabilities`,兼容 `required_resource/required_action`)。
141
- - SDK 推荐使用 `with/can`(兼容 `resource/action`),例如:`{ with: "app:all:<appId>", can: "read,write" }`。
142
- - 推荐资源统一使用 `app:<scope>:<appId>`,常用为 `with=app:all:<appId>`;请确保路径在 `/apps/<appId>/`(或 `app_scope.path_prefix`)下。
143
- - Token 过期或缓存异常时,清理 UCAN 会话并重新登录/生成 Root + Invocation。
144
-
145
- ### UCAN audience mismatch
146
-
147
- 日志示例:
148
- ```
149
- [2026-01-31T05:19:44.478Z] UCAN profile failed { error: 'UCAN audience mismatch' }
146
+ ```bash
147
+ mkdir -p /path/to/your-dapp/node_modules/@yeying-community
148
+ ln -s /path/to/web3-bs /path/to/your-dapp/node_modules/@yeying-community/web3-bs
150
149
  ```
151
150
 
152
- 解决思路:
153
- - 生成 UCAN 时的 `audience` 必须与目标后端的 `UCAN_AUD` 完全一致。
154
- - 例如 Node 后端:`did:web:127.0.0.1:3203`
155
- - 例如 WebDAV 服务:`did:web:127.0.0.1:6065`
156
- - 多后端联调时,确保每个后端使用对应的 `audience` 生成 Invocation UCAN。
151
+ ## 文档导航
152
+
153
+ - [文档导航](./docs/文档导航.md)
154
+ - [快速上手](./docs/快速上手.md)
155
+ - [SDK能力](./docs/SDK能力.md)
156
+ - [移动端认证与授权选型指南](./docs/移动端认证与授权选型指南.md)
157
+ - [接口规范(OpenAPI)](./docs/开放接口规范.yaml)
@@ -5,6 +5,7 @@ export type CentralAuthBaseOptions = AuthBaseOptions & {
5
5
  issuerPath?: string;
6
6
  sessionPath?: string;
7
7
  issuePath?: string;
8
+ accessToken?: string | null;
8
9
  storeSessionToken?: boolean;
9
10
  sessionTokenStorageKey?: string;
10
11
  };
package/dist/dapp.d.ts CHANGED
@@ -45,5 +45,13 @@ export type InitDappSessionResult = {
45
45
  webdavToken?: string;
46
46
  webdavAppDir?: string;
47
47
  };
48
+ export type AppIdLocationLike = {
49
+ host?: string | null;
50
+ hostname?: string | null;
51
+ port?: string | number | null;
52
+ };
53
+ export declare function normalizeAppHostnameForAppId(hostname: string): string;
54
+ export declare function deriveAppIdFromHost(host: string): string;
55
+ export declare function deriveAppIdFromLocation(locationLike?: AppIdLocationLike): string;
48
56
  export declare function initWebDavStorage(options: InitWebDavStorageOptions): Promise<InitWebDavStorageResult>;
49
57
  export declare function initDappSession(options: InitDappSessionOptions): Promise<InitDappSessionResult>;
@@ -1131,6 +1131,16 @@ function resolveCredentials(options) {
1131
1131
  function resolveSessionTokenKey(options) {
1132
1132
  return options?.sessionTokenStorageKey || DEFAULT_SESSION_TOKEN_KEY;
1133
1133
  }
1134
+ function resolveAccessToken(options) {
1135
+ if (typeof options?.accessToken === 'string') {
1136
+ const token = options.accessToken.trim();
1137
+ return token || null;
1138
+ }
1139
+ if (options?.accessToken === null) {
1140
+ return null;
1141
+ }
1142
+ return getAccessToken(options);
1143
+ }
1134
1144
  function shouldStoreSessionToken(options) {
1135
1145
  return options?.storeSessionToken !== false;
1136
1146
  }
@@ -1178,6 +1188,9 @@ function parseCapabilitiesField(obj, keys) {
1178
1188
  }
1179
1189
  return undefined;
1180
1190
  }
1191
+ function parseIssuerDidField(obj) {
1192
+ return parseStringField(obj, ['issuerDid', 'issuer', 'did']);
1193
+ }
1181
1194
  function readStoredSessionToken(options) {
1182
1195
  if (!shouldStoreSessionToken(options))
1183
1196
  return null;
@@ -1234,11 +1247,16 @@ async function getCentralIssuerInfo(options = {}) {
1234
1247
  const fetcher = resolveFetcher(options);
1235
1248
  const credentials = resolveCredentials(options);
1236
1249
  const url = joinUrl$1(resolveBaseUrl(options), options.issuerPath || DEFAULT_ISSUER_PATH);
1250
+ const token = resolveAccessToken(options);
1251
+ const headers = new Headers({
1252
+ accept: 'application/json',
1253
+ });
1254
+ if (token) {
1255
+ headers.set('Authorization', `Bearer ${token}`);
1256
+ }
1237
1257
  const response = await fetcher(url, {
1238
1258
  method: 'GET',
1239
- headers: {
1240
- accept: 'application/json',
1241
- },
1259
+ headers,
1242
1260
  credentials,
1243
1261
  });
1244
1262
  const payload = await parseJsonBody(response);
@@ -1248,7 +1266,7 @@ async function getCentralIssuerInfo(options = {}) {
1248
1266
  const data = parseEnvelopeData(payload);
1249
1267
  return {
1250
1268
  enabled: typeof data.enabled === 'boolean' ? data.enabled : undefined,
1251
- issuerDid: parseStringField(data, ['issuerDid']),
1269
+ issuerDid: parseIssuerDidField(data),
1252
1270
  defaultAudience: parseStringField(data, ['defaultAudience']),
1253
1271
  defaultCapabilities: parseCapabilitiesField(data, ['defaultCapabilities']),
1254
1272
  response: payload,
@@ -1261,13 +1279,18 @@ async function createCentralSession(options) {
1261
1279
  }
1262
1280
  const fetcher = resolveFetcher(options);
1263
1281
  const credentials = resolveCredentials(options);
1282
+ const accessToken = resolveAccessToken(options);
1264
1283
  const url = joinUrl$1(resolveBaseUrl(options), options.sessionPath || DEFAULT_SESSION_PATH);
1284
+ const headers = new Headers({
1285
+ 'Content-Type': 'application/json',
1286
+ accept: 'application/json',
1287
+ });
1288
+ if (accessToken) {
1289
+ headers.set('Authorization', `Bearer ${accessToken}`);
1290
+ }
1265
1291
  const response = await fetcher(url, {
1266
1292
  method: 'POST',
1267
- headers: {
1268
- 'Content-Type': 'application/json',
1269
- accept: 'application/json',
1270
- },
1293
+ headers,
1271
1294
  credentials,
1272
1295
  body: JSON.stringify({
1273
1296
  subject,
@@ -1288,7 +1311,7 @@ async function createCentralSession(options) {
1288
1311
  subject: parseStringField(data, ['subject']) || subject,
1289
1312
  sessionToken,
1290
1313
  expiresAt: parseNumberField(data, ['expiresAt']),
1291
- issuerDid: parseStringField(data, ['issuerDid']),
1314
+ issuerDid: parseIssuerDidField(data),
1292
1315
  response: payload,
1293
1316
  };
1294
1317
  }
@@ -1329,13 +1352,13 @@ async function issueCentralUcan(options = {}) {
1329
1352
  }
1330
1353
  return {
1331
1354
  ucan,
1332
- issuerDid: parseStringField(data, ['issuerDid']),
1355
+ issuerDid: parseIssuerDidField(data),
1333
1356
  subject: parseStringField(data, ['subject']),
1334
1357
  audience: parseStringField(data, ['audience']),
1335
1358
  capabilities: parseCapabilitiesField(data, ['capabilities']),
1336
- exp: parseNumberField(data, ['exp']),
1337
- nbf: parseNumberField(data, ['nbf']),
1338
- iat: parseNumberField(data, ['iat']),
1359
+ exp: parseNumberField(data, ['exp', 'expiresAt']),
1360
+ nbf: parseNumberField(data, ['nbf', 'notBefore']),
1361
+ iat: parseNumberField(data, ['iat', 'issuedAt']),
1339
1362
  response: payload,
1340
1363
  };
1341
1364
  }
@@ -1608,6 +1631,13 @@ function createWebDavClient(options) {
1608
1631
  const tokenCache = new Map();
1609
1632
  const TOKEN_SKEW_MS = 5000;
1610
1633
  const DEFAULT_APP_ACTION = 'write';
1634
+ const LOOPBACK_HOST_ALIASES = new Set([
1635
+ 'localhost',
1636
+ '127.0.0.1',
1637
+ '::1',
1638
+ '0:0:0:0:0:0:0:1',
1639
+ '0.0.0.0',
1640
+ ]);
1611
1641
  function normalizeAppDir(path) {
1612
1642
  const trimmed = path.trim();
1613
1643
  if (!trimmed)
@@ -1619,6 +1649,69 @@ function normalizeAppDir(path) {
1619
1649
  function sanitizeAppId(appId) {
1620
1650
  return appId.trim().replace(/[^a-zA-Z0-9._-]/g, '-');
1621
1651
  }
1652
+ function parseHostPort(rawHost) {
1653
+ const host = rawHost.trim();
1654
+ if (!host)
1655
+ return { hostname: '', port: '' };
1656
+ const bracketMatch = host.match(/^\[([^\]]+)\](?::([0-9]+))?$/);
1657
+ if (bracketMatch) {
1658
+ return {
1659
+ hostname: bracketMatch[1] || '',
1660
+ port: bracketMatch[2] || '',
1661
+ };
1662
+ }
1663
+ const firstColon = host.indexOf(':');
1664
+ const lastColon = host.lastIndexOf(':');
1665
+ if (firstColon > -1 && firstColon === lastColon) {
1666
+ const hostname = host.slice(0, firstColon).trim();
1667
+ const port = host.slice(firstColon + 1).trim();
1668
+ if (/^[0-9]+$/.test(port)) {
1669
+ return { hostname, port };
1670
+ }
1671
+ }
1672
+ return { hostname: host, port: '' };
1673
+ }
1674
+ function normalizeAppHostnameForAppId(hostname) {
1675
+ const normalized = (hostname || '').trim().toLowerCase();
1676
+ if (!normalized)
1677
+ return '';
1678
+ const bare = normalized.replace(/^\[(.*)\]$/, '$1');
1679
+ if (LOOPBACK_HOST_ALIASES.has(normalized) || LOOPBACK_HOST_ALIASES.has(bare)) {
1680
+ return 'localhost';
1681
+ }
1682
+ return bare;
1683
+ }
1684
+ function buildSanitizedAppId(hostname, port) {
1685
+ const normalizedHostname = normalizeAppHostnameForAppId(hostname);
1686
+ if (!normalizedHostname)
1687
+ return '';
1688
+ const normalizedPort = port === undefined || port === null ? '' : String(port).trim();
1689
+ const host = normalizedPort
1690
+ ? `${normalizedHostname}:${normalizedPort}`
1691
+ : normalizedHostname;
1692
+ return sanitizeAppId(host);
1693
+ }
1694
+ function deriveAppIdFromHost(host) {
1695
+ const parsed = parseHostPort(host || '');
1696
+ return buildSanitizedAppId(parsed.hostname, parsed.port);
1697
+ }
1698
+ function deriveAppIdFromLocation(locationLike) {
1699
+ const source = locationLike ||
1700
+ (typeof window !== 'undefined' ? window.location : undefined);
1701
+ if (!source)
1702
+ return '';
1703
+ const hostname = typeof source.hostname === 'string' ? source.hostname : '';
1704
+ const port = source.port;
1705
+ if (hostname) {
1706
+ const appId = buildSanitizedAppId(hostname, port);
1707
+ if (appId)
1708
+ return appId;
1709
+ }
1710
+ if (typeof source.host === 'string') {
1711
+ return deriveAppIdFromHost(source.host);
1712
+ }
1713
+ return '';
1714
+ }
1622
1715
  function normalizeAction(action) {
1623
1716
  const trimmed = (action || '').trim();
1624
1717
  return trimmed ? trimmed : null;
@@ -1842,5 +1935,5 @@ async function initDappSession(options) {
1842
1935
  return result;
1843
1936
  }
1844
1937
 
1845
- export { WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, clearAccessToken, clearCentralSessionToken, clearUcanSession, createAndIssueCentralUcan, createCentralSession, createDelegationUcan, createInvocationUcan, createRootUcan, createUcanSession, createWebDavClient, getAccessToken, getAccounts, getBalance, getCapabilityAction, getCapabilityResource, getCentralIssuerInfo, getCentralSessionToken, getChainId, getOrCreateUcanRoot, getPreferredAccount, getProvider, getStoredUcanRoot, getUcanSession, initDappSession, initWebDavStorage, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeUcanCapabilities, normalizeUcanCapability, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts };
1938
+ export { WebDavClient, authCentralUcanFetch, authFetch, authUcanFetch, 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, initDappSession, initWebDavStorage, isYeYingProvider, issueCentralUcan, loginWithChallenge, logout, normalizeAppHostnameForAppId, normalizeUcanCapabilities, normalizeUcanCapability, onAccountsChanged, onChainChanged, refreshAccessToken, requestAccounts, requireProvider, setAccessToken, setCentralSessionToken, signMessage, storeUcanRoot, watchAccounts };
1846
1939
  //# sourceMappingURL=web3-bs.esm.js.map