agentlink-sdk 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +368 -0
- package/dist/index.d.mts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +65 -15
- package/dist/index.mjs +65 -15
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# AgentLink SDK
|
|
2
|
+
|
|
3
|
+
AgentLink 客户端 SDK,用于跨域数据同步,通过 URL hash 传递数据,支持 Token 验证和白名单机制。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
### npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install agentlink-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### yarn
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add agentlink-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### pnpm
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add agentlink-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### CDN
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<script type="module">
|
|
29
|
+
import { AgentLinkClient } from 'https://cdn.jsdelivr.net/npm/agentlink-sdk@latest/dist/index.mjs';
|
|
30
|
+
</script>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 快速开始
|
|
34
|
+
|
|
35
|
+
### 基本使用
|
|
36
|
+
|
|
37
|
+
SDK 默认使用服务端地址 `https://mixlab.top`,可直接创建客户端;如需使用自建或其它服务端,可传入 `serverUrl` 覆盖默认值。
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { AgentLinkClient } from 'agentlink-sdk';
|
|
41
|
+
|
|
42
|
+
// 使用默认地址 https://mixlab.top
|
|
43
|
+
const client = new AgentLinkClient();
|
|
44
|
+
|
|
45
|
+
// 或指定自定义服务端地址
|
|
46
|
+
const client = new AgentLinkClient({
|
|
47
|
+
serverUrl: 'https://your-agentlink-server.com'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 发送数据
|
|
51
|
+
await client.sendData(
|
|
52
|
+
'https://target-domain.com/page',
|
|
53
|
+
{ message: 'Hello from AgentLink' },
|
|
54
|
+
'greeting'
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// 接收数据
|
|
58
|
+
client.receiveData((data, type, senderInfo) => {
|
|
59
|
+
console.log('收到数据:', data);
|
|
60
|
+
console.log('数据类型:', type);
|
|
61
|
+
console.log('发送方信息:', senderInfo);
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API 文档
|
|
66
|
+
|
|
67
|
+
### AgentLinkClient
|
|
68
|
+
|
|
69
|
+
#### 构造函数
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
new AgentLinkClient(options: AgentLinkClientOptions)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**参数:**
|
|
76
|
+
|
|
77
|
+
- `options.serverUrl` (string, 可选): 服务端验证地址。不传时使用默认地址 `https://mixlab.top`;可传入自定义地址如 `'https://your-server.com'`
|
|
78
|
+
|
|
79
|
+
#### 方法
|
|
80
|
+
|
|
81
|
+
##### sendData
|
|
82
|
+
|
|
83
|
+
发送数据到目标应用。
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
async sendData(
|
|
87
|
+
targetUrl: string,
|
|
88
|
+
data: any,
|
|
89
|
+
type: string,
|
|
90
|
+
windowName?: string
|
|
91
|
+
): Promise<void>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**参数:**
|
|
95
|
+
|
|
96
|
+
- `targetUrl` (string, 必需): 目标应用的完整 URL
|
|
97
|
+
- `data` (any, 必需): 要发送的数据对象
|
|
98
|
+
- `type` (string, 必需): 数据类型标识
|
|
99
|
+
- `windowName` (string, 可选): 窗口名称,用于复用窗口,默认为 `'agentlink-window'`
|
|
100
|
+
|
|
101
|
+
**示例:**
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
await client.sendData(
|
|
105
|
+
'https://example.com/receive',
|
|
106
|
+
{
|
|
107
|
+
message: 'Hello',
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
user: { id: 123, name: '张三' }
|
|
110
|
+
},
|
|
111
|
+
'user-message'
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
##### receiveData
|
|
116
|
+
|
|
117
|
+
监听来自 URL hash 的数据。
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
receiveData(
|
|
121
|
+
callback: (
|
|
122
|
+
data: any,
|
|
123
|
+
type: string,
|
|
124
|
+
senderInfo?: SenderInfo,
|
|
125
|
+
verification?: string
|
|
126
|
+
) => void
|
|
127
|
+
): () => void
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**参数:**
|
|
131
|
+
|
|
132
|
+
- `callback` (function, 必需): 接收到数据时的回调函数
|
|
133
|
+
- `data`: 接收到的数据
|
|
134
|
+
- `type`: 数据类型
|
|
135
|
+
- `senderInfo`: 发送方信息(包含 domain 和 description)
|
|
136
|
+
- `verification`: 验证状态信息
|
|
137
|
+
|
|
138
|
+
**返回值:**
|
|
139
|
+
|
|
140
|
+
返回一个取消监听的函数。
|
|
141
|
+
|
|
142
|
+
**示例:**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const unsubscribe = client.receiveData((data, type, senderInfo, verification) => {
|
|
146
|
+
console.log('收到数据:', data);
|
|
147
|
+
console.log('数据类型:', type);
|
|
148
|
+
if (senderInfo) {
|
|
149
|
+
console.log('发送方域名:', senderInfo.domain);
|
|
150
|
+
console.log('发送方描述:', senderInfo.description);
|
|
151
|
+
}
|
|
152
|
+
console.log('验证状态:', verification);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 取消监听
|
|
156
|
+
unsubscribe();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
##### getDataFromUrl
|
|
160
|
+
|
|
161
|
+
从当前 URL 或指定 URL 获取数据并验证发送方。
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
async getDataFromUrl(url?: string): Promise<{
|
|
165
|
+
data: any;
|
|
166
|
+
type: string;
|
|
167
|
+
senderInfo?: SenderInfo;
|
|
168
|
+
verification?: string;
|
|
169
|
+
} | null>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**参数:**
|
|
173
|
+
|
|
174
|
+
- `url` (string, 可选): 要解析的 URL,默认为当前页面的 URL
|
|
175
|
+
|
|
176
|
+
**返回值:**
|
|
177
|
+
|
|
178
|
+
如果 URL 中包含数据,返回包含 `data`、`type`、`senderInfo` 和 `verification` 的对象;否则返回 `null`。
|
|
179
|
+
|
|
180
|
+
**示例:**
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const result = await client.getDataFromUrl();
|
|
184
|
+
if (result) {
|
|
185
|
+
console.log('数据:', result.data);
|
|
186
|
+
console.log('类型:', result.type);
|
|
187
|
+
console.log('发送方:', result.senderInfo);
|
|
188
|
+
console.log('验证状态:', result.verification);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
##### getWhitelistInfo
|
|
193
|
+
|
|
194
|
+
获取白名单信息。
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
async getWhitelistInfo(includeAll?: boolean): Promise<WhitelistResponse>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**参数:**
|
|
201
|
+
|
|
202
|
+
- `includeAll` (boolean, 可选): 是否获取所有白名单信息,默认为 `false`(仅获取当前域名)
|
|
203
|
+
|
|
204
|
+
**返回值:**
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
{
|
|
208
|
+
whitelist: WhitelistInfo | WhitelistInfo[] | null;
|
|
209
|
+
origin: string | null;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**示例:**
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// 获取当前域名的白名单信息
|
|
217
|
+
const info = await client.getWhitelistInfo(false);
|
|
218
|
+
console.log('当前域名白名单:', info.whitelist);
|
|
219
|
+
|
|
220
|
+
// 获取所有白名单信息
|
|
221
|
+
const allInfo = await client.getWhitelistInfo(true);
|
|
222
|
+
console.log('所有白名单:', allInfo.whitelist);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 类型定义
|
|
226
|
+
|
|
227
|
+
### AgentLinkClientOptions
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
interface AgentLinkClientOptions {
|
|
231
|
+
serverUrl?: string; // 可选,默认 'https://mixlab.top'
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### DEFAULT_SERVER_URL
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const DEFAULT_SERVER_URL = 'https://mixlab.top';
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
如需与默认地址保持一致(例如在配置中引用),可从 SDK 导入:`import { DEFAULT_SERVER_URL } from 'agentlink-sdk'`。
|
|
242
|
+
|
|
243
|
+
### SenderInfo
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
interface SenderInfo {
|
|
247
|
+
domain: string;
|
|
248
|
+
description: string | null;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### WhitelistInfo
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
interface WhitelistInfo {
|
|
256
|
+
domain: string;
|
|
257
|
+
description?: string | null;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### WhitelistResponse
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
interface WhitelistResponse {
|
|
265
|
+
whitelist: WhitelistInfo | WhitelistInfo[] | null;
|
|
266
|
+
origin: string | null;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## 特性
|
|
271
|
+
|
|
272
|
+
- ✅ **跨域数据同步**: 通过 URL hash 实现跨域数据传输
|
|
273
|
+
- ✅ **Token 验证**: 自动获取和验证 Token,确保数据来源可信
|
|
274
|
+
- ✅ **白名单机制**: 支持域名白名单验证
|
|
275
|
+
- ✅ **数据压缩**: 自动压缩大型数据,优化 URL 长度
|
|
276
|
+
- ✅ **缓存优化**: Token 和白名单验证结果缓存,减少服务器请求
|
|
277
|
+
- ✅ **TypeScript 支持**: 完整的 TypeScript 类型定义
|
|
278
|
+
- ✅ **窗口复用**: 支持复用窗口,避免频繁打开新窗口
|
|
279
|
+
|
|
280
|
+
## 工作原理
|
|
281
|
+
|
|
282
|
+
1. **发送数据**:
|
|
283
|
+
- SDK 自动从服务器获取当前域名的 Token
|
|
284
|
+
- 将数据编码到 URL hash 中(包含 Token 和验证信息)
|
|
285
|
+
- 通过 `window.open` 打开目标页面并传递数据
|
|
286
|
+
|
|
287
|
+
2. **接收数据**:
|
|
288
|
+
- 监听 `hashchange` 事件
|
|
289
|
+
- 从 URL hash 中解码数据
|
|
290
|
+
- 验证发送方的 Token 和 Origin
|
|
291
|
+
- 返回数据及发送方信息
|
|
292
|
+
|
|
293
|
+
3. **安全机制**:
|
|
294
|
+
- Token 验证:确保数据来自已注册的域名
|
|
295
|
+
- 白名单验证:检查域名是否在白名单中
|
|
296
|
+
- 缓存机制:减少重复验证请求
|
|
297
|
+
|
|
298
|
+
## 示例
|
|
299
|
+
|
|
300
|
+
完整示例请参考 [example.html](./example.html)
|
|
301
|
+
|
|
302
|
+
### 发送数据示例
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// 使用默认地址
|
|
306
|
+
const client = new AgentLinkClient();
|
|
307
|
+
|
|
308
|
+
// 发送复杂数据
|
|
309
|
+
await client.sendData(
|
|
310
|
+
'https://target-app.com/receive',
|
|
311
|
+
{
|
|
312
|
+
action: 'update',
|
|
313
|
+
payload: {
|
|
314
|
+
userId: 123,
|
|
315
|
+
items: ['item1', 'item2', 'item3']
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
'user-action'
|
|
319
|
+
);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 接收数据示例
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// 使用默认地址,也可传入 serverUrl 使用自建服务端
|
|
326
|
+
const client = new AgentLinkClient();
|
|
327
|
+
|
|
328
|
+
// 监听数据
|
|
329
|
+
client.receiveData((data, type, senderInfo, verification) => {
|
|
330
|
+
if (senderInfo) {
|
|
331
|
+
console.log(`来自 ${senderInfo.domain} 的数据`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
switch (type) {
|
|
335
|
+
case 'user-action':
|
|
336
|
+
handleUserAction(data);
|
|
337
|
+
break;
|
|
338
|
+
case 'greeting':
|
|
339
|
+
handleGreeting(data);
|
|
340
|
+
break;
|
|
341
|
+
default:
|
|
342
|
+
console.log('未知数据类型:', type);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## 注意事项
|
|
348
|
+
|
|
349
|
+
1. **弹窗拦截**: 某些浏览器可能会拦截 `window.open`,确保在用户交互事件(如点击)中调用 `sendData`
|
|
350
|
+
2. **URL 长度限制**: 虽然 SDK 会自动压缩数据,但过大的数据仍可能超出浏览器 URL 长度限制
|
|
351
|
+
3. **HTTPS 要求**: 生产环境建议使用 HTTPS,确保数据传输安全
|
|
352
|
+
4. **Token 缓存**: Token 在实例级别缓存,同一实例的多次调用会复用 Token
|
|
353
|
+
|
|
354
|
+
## 浏览器支持
|
|
355
|
+
|
|
356
|
+
- Chrome/Edge (最新版本)
|
|
357
|
+
- Firefox (最新版本)
|
|
358
|
+
- Safari (最新版本)
|
|
359
|
+
|
|
360
|
+
## 许可证
|
|
361
|
+
|
|
362
|
+
MIT
|
|
363
|
+
|
|
364
|
+
## 相关链接
|
|
365
|
+
|
|
366
|
+
- [示例文件](./example.html)
|
|
367
|
+
- [GitHub 仓库](https://github.com/your-org/AgentLink)
|
|
368
|
+
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
interface AgentLinkClientOptions {
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* 例如: 'https://
|
|
3
|
+
* 服务端验证地址,不传则使用默认地址 https://mixlab.top
|
|
4
|
+
* 例如: 'https://mixlab.top' 或自定义 'https://your-server.com'
|
|
5
5
|
*/
|
|
6
|
-
serverUrl
|
|
6
|
+
serverUrl?: string;
|
|
7
7
|
}
|
|
8
|
+
/** 默认服务端地址 */
|
|
9
|
+
declare const DEFAULT_SERVER_URL = "https://mixlab.top";
|
|
8
10
|
interface WhitelistInfo {
|
|
9
11
|
domain: string;
|
|
12
|
+
name?: string | null;
|
|
10
13
|
description?: string | null;
|
|
11
14
|
}
|
|
12
15
|
interface WhitelistResponse {
|
|
@@ -28,7 +31,7 @@ declare class AgentLinkClient {
|
|
|
28
31
|
private static tokenCache;
|
|
29
32
|
private static whitelistCache;
|
|
30
33
|
private static CACHE_TTL;
|
|
31
|
-
constructor(options
|
|
34
|
+
constructor(options?: AgentLinkClientOptions);
|
|
32
35
|
/**
|
|
33
36
|
* 确保有 Token(自动获取)
|
|
34
37
|
*/
|
|
@@ -88,6 +91,7 @@ declare function decompress(compressedData: Uint8Array): Promise<string>;
|
|
|
88
91
|
declare function uint8ArrayToBase64(arr: Uint8Array): string;
|
|
89
92
|
/**
|
|
90
93
|
* 将 URL 安全的 base64 字符串转换为 Uint8Array
|
|
94
|
+
* 如果输入无效,会抛出错误(由调用者处理)
|
|
91
95
|
*/
|
|
92
96
|
declare function base64ToUint8Array(base64: string): Uint8Array;
|
|
93
97
|
|
|
@@ -116,4 +120,4 @@ declare function verifyWhitelist(serverUrl: string, origin?: string): Promise<bo
|
|
|
116
120
|
*/
|
|
117
121
|
declare function fetchWhitelistInfo(serverUrl: string, includeAll?: boolean): Promise<any>;
|
|
118
122
|
|
|
119
|
-
export { AgentLinkClient, type AgentLinkClientOptions, type SenderInfo, type URLData, type WhitelistInfo, type WhitelistResponse, base64ToUint8Array, compress, decodeDataFromUrl, decompress, encodeDataToUrl, fetchWhitelistInfo, uint8ArrayToBase64, verifyWhitelist };
|
|
123
|
+
export { AgentLinkClient, type AgentLinkClientOptions, DEFAULT_SERVER_URL, type SenderInfo, type URLData, type WhitelistInfo, type WhitelistResponse, base64ToUint8Array, compress, decodeDataFromUrl, decompress, encodeDataToUrl, fetchWhitelistInfo, uint8ArrayToBase64, verifyWhitelist };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
interface AgentLinkClientOptions {
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* 例如: 'https://
|
|
3
|
+
* 服务端验证地址,不传则使用默认地址 https://mixlab.top
|
|
4
|
+
* 例如: 'https://mixlab.top' 或自定义 'https://your-server.com'
|
|
5
5
|
*/
|
|
6
|
-
serverUrl
|
|
6
|
+
serverUrl?: string;
|
|
7
7
|
}
|
|
8
|
+
/** 默认服务端地址 */
|
|
9
|
+
declare const DEFAULT_SERVER_URL = "https://mixlab.top";
|
|
8
10
|
interface WhitelistInfo {
|
|
9
11
|
domain: string;
|
|
12
|
+
name?: string | null;
|
|
10
13
|
description?: string | null;
|
|
11
14
|
}
|
|
12
15
|
interface WhitelistResponse {
|
|
@@ -28,7 +31,7 @@ declare class AgentLinkClient {
|
|
|
28
31
|
private static tokenCache;
|
|
29
32
|
private static whitelistCache;
|
|
30
33
|
private static CACHE_TTL;
|
|
31
|
-
constructor(options
|
|
34
|
+
constructor(options?: AgentLinkClientOptions);
|
|
32
35
|
/**
|
|
33
36
|
* 确保有 Token(自动获取)
|
|
34
37
|
*/
|
|
@@ -88,6 +91,7 @@ declare function decompress(compressedData: Uint8Array): Promise<string>;
|
|
|
88
91
|
declare function uint8ArrayToBase64(arr: Uint8Array): string;
|
|
89
92
|
/**
|
|
90
93
|
* 将 URL 安全的 base64 字符串转换为 Uint8Array
|
|
94
|
+
* 如果输入无效,会抛出错误(由调用者处理)
|
|
91
95
|
*/
|
|
92
96
|
declare function base64ToUint8Array(base64: string): Uint8Array;
|
|
93
97
|
|
|
@@ -116,4 +120,4 @@ declare function verifyWhitelist(serverUrl: string, origin?: string): Promise<bo
|
|
|
116
120
|
*/
|
|
117
121
|
declare function fetchWhitelistInfo(serverUrl: string, includeAll?: boolean): Promise<any>;
|
|
118
122
|
|
|
119
|
-
export { AgentLinkClient, type AgentLinkClientOptions, type SenderInfo, type URLData, type WhitelistInfo, type WhitelistResponse, base64ToUint8Array, compress, decodeDataFromUrl, decompress, encodeDataToUrl, fetchWhitelistInfo, uint8ArrayToBase64, verifyWhitelist };
|
|
123
|
+
export { AgentLinkClient, type AgentLinkClientOptions, DEFAULT_SERVER_URL, type SenderInfo, type URLData, type WhitelistInfo, type WhitelistResponse, base64ToUint8Array, compress, decodeDataFromUrl, decompress, encodeDataToUrl, fetchWhitelistInfo, uint8ArrayToBase64, verifyWhitelist };
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AgentLinkClient: () => AgentLinkClient,
|
|
24
|
+
DEFAULT_SERVER_URL: () => DEFAULT_SERVER_URL,
|
|
24
25
|
base64ToUint8Array: () => base64ToUint8Array,
|
|
25
26
|
compress: () => compress,
|
|
26
27
|
decodeDataFromUrl: () => decodeDataFromUrl,
|
|
@@ -55,14 +56,37 @@ function uint8ArrayToBase64(arr) {
|
|
|
55
56
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
56
57
|
}
|
|
57
58
|
function base64ToUint8Array(base64) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
if (!base64 || typeof base64 !== "string") {
|
|
60
|
+
const error = new Error("Invalid base64 string: input must be a non-empty string");
|
|
61
|
+
error.isInvalidBase64 = true;
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
const cleaned = base64.trim().replace(/[\s\n\r]/g, "");
|
|
65
|
+
if (!cleaned) {
|
|
66
|
+
const error = new Error("Invalid base64 string: empty after cleaning");
|
|
67
|
+
error.isInvalidBase64 = true;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const base64Regex = /^[A-Za-z0-9\-_]+$/;
|
|
71
|
+
if (!base64Regex.test(cleaned)) {
|
|
72
|
+
const error = new Error(`Invalid base64 string: contains invalid characters`);
|
|
73
|
+
error.isInvalidBase64 = true;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const padding = "=".repeat((4 - cleaned.length % 4) % 4);
|
|
78
|
+
const standardBase64 = cleaned.replace(/-/g, "+").replace(/_/g, "/") + padding;
|
|
79
|
+
const binary = atob(standardBase64);
|
|
80
|
+
const arr = new Uint8Array(binary.length);
|
|
81
|
+
for (let i = 0; i < binary.length; i++) {
|
|
82
|
+
arr[i] = binary.charCodeAt(i);
|
|
83
|
+
}
|
|
84
|
+
return arr;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const base64Error = error instanceof Error ? error : new Error("Failed to decode base64");
|
|
87
|
+
base64Error.isInvalidBase64 = true;
|
|
88
|
+
throw base64Error;
|
|
64
89
|
}
|
|
65
|
-
return arr;
|
|
66
90
|
}
|
|
67
91
|
|
|
68
92
|
// src/utils/url.ts
|
|
@@ -82,12 +106,24 @@ async function decodeDataFromUrl(urlStr) {
|
|
|
82
106
|
try {
|
|
83
107
|
const url = new URL(urlStr);
|
|
84
108
|
const hash = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
|
|
85
|
-
if (!hash)
|
|
109
|
+
if (!hash || hash.trim().length === 0) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (hash.length < 10) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
86
115
|
const compressed = base64ToUint8Array(hash);
|
|
87
116
|
const jsonString = await decompress(compressed);
|
|
88
117
|
return JSON.parse(jsonString);
|
|
89
118
|
} catch (error) {
|
|
90
|
-
|
|
119
|
+
if (error && typeof error === "object" && error.isInvalidBase64) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (error instanceof Error) {
|
|
123
|
+
if (false) {
|
|
124
|
+
console.debug("[AgentLink] Failed to decode data from URL:", error.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
91
127
|
return null;
|
|
92
128
|
}
|
|
93
129
|
}
|
|
@@ -138,12 +174,14 @@ async function fetchWhitelistInfo(serverUrl, includeAll = false) {
|
|
|
138
174
|
}
|
|
139
175
|
|
|
140
176
|
// src/client.ts
|
|
177
|
+
var DEFAULT_SERVER_URL = "https://mixlab.top";
|
|
141
178
|
var _AgentLinkClient = class _AgentLinkClient {
|
|
142
|
-
constructor(options) {
|
|
179
|
+
constructor(options = {}) {
|
|
143
180
|
// 发送端 Token 缓存(实例级别)
|
|
144
181
|
this.token = null;
|
|
145
182
|
this.tokenPromise = null;
|
|
146
|
-
|
|
183
|
+
const base = options.serverUrl ?? DEFAULT_SERVER_URL;
|
|
184
|
+
this.serverUrl = base.replace(/\/$/, "");
|
|
147
185
|
}
|
|
148
186
|
/**
|
|
149
187
|
* 确保有 Token(自动获取)
|
|
@@ -238,13 +276,25 @@ var _AgentLinkClient = class _AgentLinkClient {
|
|
|
238
276
|
*/
|
|
239
277
|
receiveData(callback) {
|
|
240
278
|
const handleHashChange = async () => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
279
|
+
try {
|
|
280
|
+
const result = await this.getDataFromUrl();
|
|
281
|
+
if (result) {
|
|
282
|
+
callback(result.data, result.type, result.senderInfo, result.verification);
|
|
283
|
+
}
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (false) {
|
|
286
|
+
console.debug("[AgentLink] Error in handleHashChange (silently handled):", error);
|
|
287
|
+
}
|
|
244
288
|
}
|
|
245
289
|
};
|
|
246
290
|
window.addEventListener("hashchange", handleHashChange);
|
|
247
|
-
|
|
291
|
+
try {
|
|
292
|
+
handleHashChange();
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (false) {
|
|
295
|
+
console.debug("[AgentLink] Error in initial hash check (silently handled):", error);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
248
298
|
return () => {
|
|
249
299
|
window.removeEventListener("hashchange", handleHashChange);
|
|
250
300
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -21,14 +21,37 @@ function uint8ArrayToBase64(arr) {
|
|
|
21
21
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
22
22
|
}
|
|
23
23
|
function base64ToUint8Array(base64) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
if (!base64 || typeof base64 !== "string") {
|
|
25
|
+
const error = new Error("Invalid base64 string: input must be a non-empty string");
|
|
26
|
+
error.isInvalidBase64 = true;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const cleaned = base64.trim().replace(/[\s\n\r]/g, "");
|
|
30
|
+
if (!cleaned) {
|
|
31
|
+
const error = new Error("Invalid base64 string: empty after cleaning");
|
|
32
|
+
error.isInvalidBase64 = true;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
const base64Regex = /^[A-Za-z0-9\-_]+$/;
|
|
36
|
+
if (!base64Regex.test(cleaned)) {
|
|
37
|
+
const error = new Error(`Invalid base64 string: contains invalid characters`);
|
|
38
|
+
error.isInvalidBase64 = true;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const padding = "=".repeat((4 - cleaned.length % 4) % 4);
|
|
43
|
+
const standardBase64 = cleaned.replace(/-/g, "+").replace(/_/g, "/") + padding;
|
|
44
|
+
const binary = atob(standardBase64);
|
|
45
|
+
const arr = new Uint8Array(binary.length);
|
|
46
|
+
for (let i = 0; i < binary.length; i++) {
|
|
47
|
+
arr[i] = binary.charCodeAt(i);
|
|
48
|
+
}
|
|
49
|
+
return arr;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const base64Error = error instanceof Error ? error : new Error("Failed to decode base64");
|
|
52
|
+
base64Error.isInvalidBase64 = true;
|
|
53
|
+
throw base64Error;
|
|
30
54
|
}
|
|
31
|
-
return arr;
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
// src/utils/url.ts
|
|
@@ -48,12 +71,24 @@ async function decodeDataFromUrl(urlStr) {
|
|
|
48
71
|
try {
|
|
49
72
|
const url = new URL(urlStr);
|
|
50
73
|
const hash = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
|
|
51
|
-
if (!hash)
|
|
74
|
+
if (!hash || hash.trim().length === 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (hash.length < 10) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
52
80
|
const compressed = base64ToUint8Array(hash);
|
|
53
81
|
const jsonString = await decompress(compressed);
|
|
54
82
|
return JSON.parse(jsonString);
|
|
55
83
|
} catch (error) {
|
|
56
|
-
|
|
84
|
+
if (error && typeof error === "object" && error.isInvalidBase64) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
if (false) {
|
|
89
|
+
console.debug("[AgentLink] Failed to decode data from URL:", error.message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
57
92
|
return null;
|
|
58
93
|
}
|
|
59
94
|
}
|
|
@@ -104,12 +139,14 @@ async function fetchWhitelistInfo(serverUrl, includeAll = false) {
|
|
|
104
139
|
}
|
|
105
140
|
|
|
106
141
|
// src/client.ts
|
|
142
|
+
var DEFAULT_SERVER_URL = "https://mixlab.top";
|
|
107
143
|
var _AgentLinkClient = class _AgentLinkClient {
|
|
108
|
-
constructor(options) {
|
|
144
|
+
constructor(options = {}) {
|
|
109
145
|
// 发送端 Token 缓存(实例级别)
|
|
110
146
|
this.token = null;
|
|
111
147
|
this.tokenPromise = null;
|
|
112
|
-
|
|
148
|
+
const base = options.serverUrl ?? DEFAULT_SERVER_URL;
|
|
149
|
+
this.serverUrl = base.replace(/\/$/, "");
|
|
113
150
|
}
|
|
114
151
|
/**
|
|
115
152
|
* 确保有 Token(自动获取)
|
|
@@ -204,13 +241,25 @@ var _AgentLinkClient = class _AgentLinkClient {
|
|
|
204
241
|
*/
|
|
205
242
|
receiveData(callback) {
|
|
206
243
|
const handleHashChange = async () => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
244
|
+
try {
|
|
245
|
+
const result = await this.getDataFromUrl();
|
|
246
|
+
if (result) {
|
|
247
|
+
callback(result.data, result.type, result.senderInfo, result.verification);
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (false) {
|
|
251
|
+
console.debug("[AgentLink] Error in handleHashChange (silently handled):", error);
|
|
252
|
+
}
|
|
210
253
|
}
|
|
211
254
|
};
|
|
212
255
|
window.addEventListener("hashchange", handleHashChange);
|
|
213
|
-
|
|
256
|
+
try {
|
|
257
|
+
handleHashChange();
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (false) {
|
|
260
|
+
console.debug("[AgentLink] Error in initial hash check (silently handled):", error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
214
263
|
return () => {
|
|
215
264
|
window.removeEventListener("hashchange", handleHashChange);
|
|
216
265
|
};
|
|
@@ -257,6 +306,7 @@ _AgentLinkClient.CACHE_TTL = 60 * 60 * 1e3;
|
|
|
257
306
|
var AgentLinkClient = _AgentLinkClient;
|
|
258
307
|
export {
|
|
259
308
|
AgentLinkClient,
|
|
309
|
+
DEFAULT_SERVER_URL,
|
|
260
310
|
base64ToUint8Array,
|
|
261
311
|
compress,
|
|
262
312
|
decodeDataFromUrl,
|