agentlink-sdk 1.0.3 → 1.0.5

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