@northsea4/tab-proxy-service 0.2.0 → 0.2.1

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
@@ -11,7 +11,7 @@
11
11
  ✅ **自动类型推断** - 使用 `defineTabProxyService` 自动获得类型提示
12
12
  ✅ **嵌套对象支持** - 支持深层嵌套的对象和方法调用
13
13
  ✅ **自动清理** - Tab 关闭时自动注销服务
14
- ✅ **查询功能** - 可查询所有已注册的 tab services
14
+ ✅ **查询功能** - 可查询所有已注册的 tab services
15
15
 
16
16
  ## 安装
17
17
 
@@ -38,19 +38,19 @@ yarn add @northsea4/tab-proxy-service
38
38
 
39
39
  ```ts
40
40
  // services/hello-service.ts
41
- import { defineTabProxyService } from '@northsea4/tab-proxy-service';
41
+ import { defineTabProxyService } from '@northsea4/tab-proxy-service'
42
42
 
43
43
  // 使用 class 定义服务
44
44
  class HelloService {
45
45
  async sayHello(name: string): Promise<string> {
46
- return `Hello, ${name}! From tab: ${document.title}`;
46
+ return `Hello, ${name}! From tab: ${document.title}`
47
47
  }
48
48
 
49
49
  async getTabInfo(): Promise<{ title: string; url: string }> {
50
50
  return {
51
51
  title: document.title,
52
52
  url: window.location.href
53
- };
53
+ }
54
54
  }
55
55
  }
56
56
 
@@ -58,23 +58,20 @@ class HelloService {
58
58
  export const [registerHelloService, getHelloService] = defineTabProxyService(
59
59
  'hello-service',
60
60
  () => new HelloService()
61
- );
61
+ )
62
62
  ```
63
63
 
64
64
  #### 方式 B: 使用对象字面量
65
65
 
66
66
  ```ts
67
- export const [registerPageService, getPageService] = defineTabProxyService(
68
- 'page-service',
69
- () => ({
70
- async getTitle() {
71
- return document.title;
72
- },
73
- async getUrl() {
74
- return window.location.href;
75
- }
76
- })
77
- );
67
+ export const [registerPageService, getPageService] = defineTabProxyService('page-service', () => ({
68
+ async getTitle() {
69
+ return document.title
70
+ },
71
+ async getUrl() {
72
+ return window.location.href
73
+ }
74
+ }))
78
75
  ```
79
76
 
80
77
  #### 方式 C: 带参数的服务
@@ -82,9 +79,9 @@ export const [registerPageService, getPageService] = defineTabProxyService(
82
79
  ```ts
83
80
  class ConfigService {
84
81
  constructor(private apiUrl: string) {}
85
-
82
+
86
83
  async fetchConfig() {
87
- return fetch(`${this.apiUrl}/config`).then(r => r.json());
84
+ return fetch(`${this.apiUrl}/config`).then((r) => r.json())
88
85
  }
89
86
  }
90
87
 
@@ -92,20 +89,20 @@ class ConfigService {
92
89
  export const [registerConfigService, getConfigService] = defineTabProxyService(
93
90
  'config-service',
94
91
  (apiUrl: string) => new ConfigService(apiUrl)
95
- );
92
+ )
96
93
  ```
97
94
 
98
95
  ### 步骤 2: 在 Content Script 中注册服务
99
96
 
100
97
  ```ts
101
98
  // content-script.ts
102
- import { registerHelloService } from './services/hello-service';
99
+ import { registerHelloService } from './services/hello-service'
103
100
 
104
101
  // 注册服务
105
- const cleanup = await registerHelloService();
102
+ const cleanup = await registerHelloService()
106
103
 
107
104
  // 页面卸载时清理
108
- window.addEventListener('beforeunload', cleanup);
105
+ window.addEventListener('beforeunload', cleanup)
109
106
 
110
107
  // 如果服务需要参数
111
108
  // const cleanup = await registerConfigService('https://api.example.com');
@@ -115,14 +112,14 @@ window.addEventListener('beforeunload', cleanup);
115
112
 
116
113
  ```ts
117
114
  // background.ts
118
- import { getHelloService } from './services/hello-service';
115
+ import { getHelloService } from './services/hello-service'
119
116
 
120
117
  // 获取服务 - 自动有完整的类型提示和自动补全! 🚀
121
- const helloService = getHelloService({ targetTabId: 123 });
118
+ const helloService = getHelloService({ targetTabId: 123 })
122
119
 
123
120
  // sayHello 方法会有自动补全和类型检查 ✨
124
- const greeting = await helloService.sayHello('World');
125
- const info = await helloService.getTabInfo();
121
+ const greeting = await helloService.sayHello('World')
122
+ const info = await helloService.getTabInfo()
126
123
  ```
127
124
 
128
125
  ---
@@ -139,110 +136,110 @@ const info = await helloService.getTabInfo();
139
136
 
140
137
  ```ts
141
138
  // background.ts
142
- import { initTabServiceManager } from '@northsea4/tab-proxy-service';
139
+ import { initTabServiceManager } from '@northsea4/tab-proxy-service'
143
140
 
144
141
  // 启动时初始化
145
142
  initTabServiceManager({
146
- logger: console, // 可选:启用日志
147
- });
143
+ logger: console // 可选:启用日志
144
+ })
148
145
  ```
149
146
 
150
147
  ### 2. 在 Content Script 中注册 Service
151
148
 
152
149
  ```ts
153
150
  // content-script.ts
154
- import { registerTabService } from '@northsea4/tab-proxy-service';
151
+ import { registerTabService } from '@northsea4/tab-proxy-service'
155
152
 
156
153
  // 定义你的 service
157
154
  const pageService = {
158
155
  async getPageTitle(): Promise<string> {
159
- return document.title;
156
+ return document.title
160
157
  },
161
-
158
+
162
159
  async getPageUrl(): Promise<string> {
163
- return window.location.href;
160
+ return window.location.href
164
161
  },
165
-
162
+
166
163
  async extractData(): Promise<any[]> {
167
164
  // 提取页面数据
168
- return Array.from(document.querySelectorAll('.item')).map(el => ({
165
+ return Array.from(document.querySelectorAll('.item')).map((el) => ({
169
166
  text: el.textContent,
170
- href: el.getAttribute('href'),
171
- }));
172
- },
173
- };
167
+ href: el.getAttribute('href')
168
+ }))
169
+ }
170
+ }
174
171
 
175
172
  // 注册 service
176
- const cleanup = await registerTabService('page-service', pageService);
173
+ const cleanup = await registerTabService('page-service', pageService)
177
174
 
178
175
  // 页面卸载时清理
179
- window.addEventListener('beforeunload', cleanup);
176
+ window.addEventListener('beforeunload', cleanup)
180
177
  ```
181
178
 
182
179
  ### 3. 从 Background 调用 Tab Service
183
180
 
184
181
  ```ts
185
182
  // background.ts
186
- import { createTabProxyService, type TabProxyServiceKey } from '@northsea4/tab-proxy-service';
183
+ import { createTabProxyService, type TabProxyServiceKey } from '@northsea4/tab-proxy-service'
187
184
 
188
185
  // 定义类型
189
186
  interface PageService {
190
- getPageTitle(): Promise<string>;
191
- getPageUrl(): Promise<string>;
192
- extractData(): Promise<any[]>;
187
+ getPageTitle(): Promise<string>
188
+ getPageUrl(): Promise<string>
189
+ extractData(): Promise<any[]>
193
190
  }
194
191
 
195
192
  // 创建代理(会调用任意一个注册了该 service 的 tab)
196
- const pageService = createTabProxyService<PageService>('page-service');
193
+ const pageService = createTabProxyService<PageService>('page-service')
197
194
 
198
195
  // 使用
199
- const title = await pageService.getPageTitle();
200
- const data = await pageService.extractData();
196
+ const title = await pageService.getPageTitle()
197
+ const data = await pageService.extractData()
201
198
 
202
- console.log('Page title:', title);
203
- console.log('Extracted data:', data);
199
+ console.log('Page title:', title)
200
+ console.log('Extracted data:', data)
204
201
  ```
205
202
 
206
203
  ### 4. 调用特定 Tab 中的 Service
207
204
 
208
205
  ```ts
209
206
  // background.ts 或 另一个 content-script.ts
210
- import { createTabProxyService } from '@northsea4/tab-proxy-service';
207
+ import { createTabProxyService } from '@northsea4/tab-proxy-service'
211
208
 
212
209
  // 指定目标 tab ID
213
210
  const specificTabService = createTabProxyService<PageService>('page-service', {
214
- targetTabId: 123, // 目标 tab 的 ID
215
- });
211
+ targetTabId: 123 // 目标 tab 的 ID
212
+ })
216
213
 
217
- const title = await specificTabService.getPageTitle();
214
+ const title = await specificTabService.getPageTitle()
218
215
  ```
219
216
 
220
217
  ### 5. Tab 间通信示例
221
218
 
222
219
  ```ts
223
220
  // content-script-a.ts (Tab A)
224
- import { registerTabService } from '@northsea4/tab-proxy-service';
221
+ import { registerTabService } from '@northsea4/tab-proxy-service'
225
222
 
226
223
  const dataService = {
227
224
  async shareData() {
228
- return { data: 'from tab A', timestamp: Date.now() };
229
- },
230
- };
225
+ return { data: 'from tab A', timestamp: Date.now() }
226
+ }
227
+ }
231
228
 
232
- await registerTabService('data-service', dataService);
229
+ await registerTabService('data-service', dataService)
233
230
  ```
234
231
 
235
232
  ```ts
236
233
  // content-script-b.ts (Tab B)
237
- import { createTabProxyService } from '@northsea4/tab-proxy-service';
234
+ import { createTabProxyService } from '@northsea4/tab-proxy-service'
238
235
 
239
236
  // Tab B 调用 Tab A 中的服务
240
237
  const dataService = createTabProxyService('data-service', {
241
- targetTabId: tabAId, // Tab A 的 ID
242
- });
238
+ targetTabId: tabAId // Tab A 的 ID
239
+ })
243
240
 
244
- const data = await dataService.shareData();
245
- console.log('Data from Tab A:', data);
241
+ const data = await dataService.shareData()
242
+ console.log('Data from Tab A:', data)
246
243
  ```
247
244
 
248
245
  ## 类型安全最佳实践
@@ -253,17 +250,17 @@ console.log('Data from Tab A:', data);
253
250
 
254
251
  ```ts
255
252
  // services/page-service.ts
256
- import { defineTabProxyService } from '@northsea4/tab-proxy-service';
253
+ import { defineTabProxyService } from '@northsea4/tab-proxy-service'
257
254
 
258
255
  class PageService {
259
256
  async getTitle(): Promise<string> {
260
- return document.title;
257
+ return document.title
261
258
  }
262
-
259
+
263
260
  async extractLinks(): Promise<string[]> {
264
261
  return Array.from(document.querySelectorAll('a'))
265
- .map(a => a.href)
266
- .filter(Boolean);
262
+ .map((a) => a.href)
263
+ .filter(Boolean)
267
264
  }
268
265
  }
269
266
 
@@ -271,19 +268,20 @@ class PageService {
271
268
  export const [registerPageService, getPageService] = defineTabProxyService(
272
269
  'page-service',
273
270
  () => new PageService()
274
- );
271
+ )
275
272
  ```
276
273
 
277
274
  **使用:**
275
+
278
276
  ```ts
279
277
  // content-script.ts
280
- import { registerPageService } from './services/page-service';
281
- await registerPageService();
278
+ import { registerPageService } from './services/page-service'
279
+ await registerPageService()
282
280
 
283
281
  // background.ts
284
- import { getPageService } from './services/page-service';
285
- const pageService = getPageService({ targetTabId: 123 });
286
- const title = await pageService.getTitle(); // ✅ 自动类型提示
282
+ import { getPageService } from './services/page-service'
283
+ const pageService = getPageService({ targetTabId: 123 })
284
+ const title = await pageService.getTitle() // ✅ 自动类型提示
287
285
  ```
288
286
 
289
287
  ### 方式 2: 使用 `TabProxyServiceKey`
@@ -292,39 +290,39 @@ const title = await pageService.getTitle(); // ✅ 自动类型提示
292
290
 
293
291
  ```ts
294
292
  // service-keys.ts
295
- import type { TabProxyServiceKey } from '@northsea4/tab-proxy-service';
296
- import type { PageService } from './page-service';
293
+ import type { TabProxyServiceKey } from '@northsea4/tab-proxy-service'
294
+ import type { PageService } from './page-service'
297
295
 
298
296
  // 使用类型约束的 key
299
- export const PAGE_SERVICE_KEY = 'page-service' as TabProxyServiceKey<PageService>;
297
+ export const PAGE_SERVICE_KEY = 'page-service' as TabProxyServiceKey<PageService>
300
298
  ```
301
299
 
302
300
  ```ts
303
301
  // content-script.ts
304
- import { registerTabService } from '@northsea4/tab-proxy-service';
305
- import { PAGE_SERVICE_KEY } from './service-keys';
306
- import { createPageService } from './page-service';
302
+ import { registerTabService } from '@northsea4/tab-proxy-service'
303
+ import { PAGE_SERVICE_KEY } from './service-keys'
304
+ import { createPageService } from './page-service'
307
305
 
308
- const pageService = createPageService();
309
- await registerTabService(PAGE_SERVICE_KEY, pageService);
306
+ const pageService = createPageService()
307
+ await registerTabService(PAGE_SERVICE_KEY, pageService)
310
308
  ```
311
309
 
312
310
  ```ts
313
311
  // background.ts
314
- import { createTabProxyService } from '@northsea4/tab-proxy-service';
315
- import { PAGE_SERVICE_KEY } from './service-keys';
312
+ import { createTabProxyService } from '@northsea4/tab-proxy-service'
313
+ import { PAGE_SERVICE_KEY } from './service-keys'
316
314
 
317
315
  // 自动推断类型,无需手动指定泛型
318
- const pageService = createTabProxyService(PAGE_SERVICE_KEY);
316
+ const pageService = createTabProxyService(PAGE_SERVICE_KEY)
319
317
  ```
320
318
 
321
319
  ## 查询已注册的 Services
322
320
 
323
321
  ```ts
324
- import { queryTabServices } from '@northsea4/tab-proxy-service';
322
+ import { queryTabServices } from '@northsea4/tab-proxy-service'
325
323
 
326
- const services = await queryTabServices();
327
- console.log('Registered services:', services);
324
+ const services = await queryTabServices()
325
+ console.log('Registered services:', services)
328
326
  // [
329
327
  // { tabId: 123, serviceKey: 'page-service', registeredAt: 1234567890 },
330
328
  // { tabId: 456, serviceKey: 'data-service', registeredAt: 1234567891 },
@@ -339,29 +337,29 @@ console.log('Registered services:', services);
339
337
 
340
338
  ```ts
341
339
  // services/data-service.ts
342
- import { defineTabProxyService } from '@northsea4/tab-proxy-service';
340
+ import { defineTabProxyService } from '@northsea4/tab-proxy-service'
343
341
 
344
342
  class DataService {
345
- private cache = new Map<string, any>();
346
-
343
+ private cache = new Map<string, any>()
344
+
347
345
  constructor(private apiUrl?: string) {}
348
-
346
+
349
347
  async getData(key: string): Promise<any> {
350
348
  if (this.cache.has(key)) {
351
- return this.cache.get(key);
349
+ return this.cache.get(key)
352
350
  }
353
-
354
- const data = await fetch(`${this.apiUrl || ''}/data/${key}`).then(r => r.json());
355
- this.cache.set(key, data);
356
- return data;
351
+
352
+ const data = await fetch(`${this.apiUrl || ''}/data/${key}`).then((r) => r.json())
353
+ this.cache.set(key, data)
354
+ return data
357
355
  }
358
-
356
+
359
357
  async setData(key: string, value: any): Promise<void> {
360
- this.cache.set(key, value);
358
+ this.cache.set(key, value)
361
359
  }
362
-
360
+
363
361
  async clearCache(): Promise<void> {
364
- this.cache.clear();
362
+ this.cache.clear()
365
363
  }
366
364
  }
367
365
 
@@ -369,23 +367,25 @@ class DataService {
369
367
  export const [registerDataService, getDataService] = defineTabProxyService(
370
368
  'data-service',
371
369
  (apiUrl?: string) => new DataService(apiUrl)
372
- );
370
+ )
373
371
  ```
374
372
 
375
373
  **优点:**
374
+
376
375
  - ✅ 支持私有成员和状态管理
377
376
  - ✅ 支持构造函数参数
378
377
  - ✅ 良好的代码组织
379
378
  - ✅ 完整的 IDE 支持
380
379
 
381
380
  **使用:**
381
+
382
382
  ```ts
383
383
  // content-script.ts
384
- const cleanup = await registerDataService('https://api.example.com');
384
+ const cleanup = await registerDataService('https://api.example.com')
385
385
 
386
386
  // background.ts
387
- const dataService = getDataService({ targetTabId: 123 });
388
- const data = await dataService.getData('users');
387
+ const dataService = getDataService({ targetTabId: 123 })
388
+ const data = await dataService.getData('users')
389
389
  ```
390
390
 
391
391
  ### 2. 对象字面量
@@ -393,17 +393,14 @@ const data = await dataService.getData('users');
393
393
  简单场景可以使用对象字面量:
394
394
 
395
395
  ```ts
396
- export const [registerMyService, getMyService] = defineTabProxyService(
397
- 'my-service',
398
- () => ({
399
- async getData(): Promise<Data> {
400
- // ...
401
- },
402
- async setData(data: Data): Promise<void> {
403
- // ...
404
- },
405
- })
406
- );
396
+ export const [registerMyService, getMyService] = defineTabProxyService('my-service', () => ({
397
+ async getData(): Promise<Data> {
398
+ // ...
399
+ },
400
+ async setData(data: Data): Promise<void> {
401
+ // ...
402
+ }
403
+ }))
407
404
  ```
408
405
 
409
406
  ### 3. 函数
@@ -416,12 +413,12 @@ async function myFunction(arg: string): Promise<number> {
416
413
  export const [registerMyFunction, getMyFunction] = defineTabProxyService(
417
414
  'my-function',
418
415
  () => myFunction
419
- );
416
+ )
420
417
  ```
421
418
 
422
419
  ### 4. 嵌套对象
423
420
 
424
- ```ts
421
+ ````ts
425
422
  ### 4. 嵌套对象
426
423
 
427
424
  支持深层嵌套的对象和方法调用:
@@ -440,13 +437,14 @@ export const [registerApiService, getApiService] = defineTabProxyService(
440
437
  },
441
438
  })
442
439
  );
443
- ```
440
+ ````
444
441
 
445
442
  **使用:**
443
+
446
444
  ```ts
447
- const api = getApiService({ targetTabId: 123 });
448
- const users = await api.users.list();
449
- const posts = await api.posts.list();
445
+ const api = getApiService({ targetTabId: 123 })
446
+ const users = await api.users.list()
447
+ const posts = await api.posts.list()
450
448
  ```
451
449
 
452
450
  ## 辅助工具
@@ -456,33 +454,34 @@ const posts = await api.posts.list();
456
454
  用于简化处理 `Promise<Dependency>`:
457
455
 
458
456
  ```ts
459
- import { flattenPromise } from '@northsea4/tab-proxy-service';
457
+ import { flattenPromise } from '@northsea4/tab-proxy-service'
460
458
 
461
459
  function createService(dbPromise: Promise<Database>) {
462
- const db = flattenPromise(dbPromise);
463
-
460
+ const db = flattenPromise(dbPromise)
461
+
464
462
  return {
465
463
  async getData() {
466
464
  // 不需要 await (await dbPromise).query()
467
- return await db.query('SELECT * FROM table');
465
+ return await db.query('SELECT * FROM table')
468
466
  }
469
- };
467
+ }
470
468
  }
471
469
  ```
472
470
 
473
471
  ## 与 @webext-core/proxy-service 的对比
474
472
 
475
- | 功能 | @webext-core/proxy-service | @northsea4/tab-proxy-service |
476
- |------|----------------------------|------------------------------|
477
- | 在 Background 注册服务 | ✅ | ❌ |
478
- | 在 Content Script 注册服务 | ❌ | ✅ |
479
- | Background → Content 调用 | ❌ | ✅ |
480
- | Content → Background 调用 | ✅ | 使用 @webext-core/proxy-service |
481
- | Tab → Tab 调用 | ❌ | ✅ |
482
- | 类型安全 | ✅ | ✅ |
483
- | 嵌套对象支持 | ✅ | ✅ |
473
+ | 功能 | @webext-core/proxy-service | @northsea4/tab-proxy-service |
474
+ | -------------------------- | -------------------------- | ------------------------------- |
475
+ | 在 Background 注册服务 | ✅ | ❌ |
476
+ | 在 Content Script 注册服务 | ❌ | ✅ |
477
+ | Background → Content 调用 | ❌ | ✅ |
478
+ | Content → Background 调用 | ✅ | 使用 @webext-core/proxy-service |
479
+ | Tab → Tab 调用 | ❌ | ✅ |
480
+ | 类型安全 | ✅ | ✅ |
481
+ | 嵌套对象支持 | ✅ | ✅ |
484
482
 
485
483
  **建议**: 两个库可以配合使用:
484
+
486
485
  - 使用 `@webext-core/proxy-service` 在 background 注册服务,供其他上下文调用
487
486
  - 使用 `@northsea4/tab-proxy-service` 在 content script 注册服务,供 background 或其他 tab 调用
488
487
 
@@ -490,58 +489,58 @@ function createService(dbPromise: Promise<Database>) {
490
489
 
491
490
  ```ts
492
491
  // ===== services/page-service.ts =====
493
- import { defineTabProxyService } from '@northsea4/tab-proxy-service';
492
+ import { defineTabProxyService } from '@northsea4/tab-proxy-service'
494
493
 
495
494
  class PageService {
496
495
  async getTitle(): Promise<string> {
497
- return document.title;
496
+ return document.title
498
497
  }
499
-
498
+
500
499
  async extractLinks(): Promise<string[]> {
501
500
  return Array.from(document.querySelectorAll('a'))
502
- .map(a => a.href)
503
- .filter(Boolean);
501
+ .map((a) => a.href)
502
+ .filter(Boolean)
504
503
  }
505
504
  }
506
505
 
507
506
  export const [registerPageService, getPageService] = defineTabProxyService(
508
507
  'page-service',
509
508
  () => new PageService()
510
- );
509
+ )
511
510
 
512
511
  // ===== content-script.ts =====
513
- import { registerPageService } from './services/page-service';
512
+ import { registerPageService } from './services/page-service'
514
513
 
515
514
  // 注册服务
516
- const cleanup = await registerPageService();
515
+ const cleanup = await registerPageService()
517
516
 
518
517
  // 清理
519
- window.addEventListener('beforeunload', cleanup);
518
+ window.addEventListener('beforeunload', cleanup)
520
519
 
521
520
  // ===== background.ts =====
522
- import { initTabServiceManager } from '@northsea4/tab-proxy-service';
523
- import { getPageService } from './services/page-service';
521
+ import { initTabServiceManager } from '@northsea4/tab-proxy-service'
522
+ import { getPageService } from './services/page-service'
524
523
 
525
524
  // 初始化管理器
526
- initTabServiceManager({ logger: console });
525
+ initTabServiceManager({ logger: console })
527
526
 
528
527
  // 监听扩展图标点击
529
528
  browser.action.onClicked.addListener(async (tab) => {
530
- if (!tab.id) return;
531
-
529
+ if (!tab.id) return
530
+
532
531
  // 调用当前 tab 中的服务
533
- const pageService = getPageService({ targetTabId: tab.id });
534
-
532
+ const pageService = getPageService({ targetTabId: tab.id })
533
+
535
534
  try {
536
- const title = await pageService.getTitle();
537
- const links = await pageService.extractLinks();
538
-
539
- console.log('Page title:', title);
540
- console.log('Links found:', links.length);
535
+ const title = await pageService.getTitle()
536
+ const links = await pageService.extractLinks()
537
+
538
+ console.log('Page title:', title)
539
+ console.log('Links found:', links.length)
541
540
  } catch (error) {
542
- console.error('Failed to call service:', error);
541
+ console.error('Failed to call service:', error)
543
542
  }
544
- });
543
+ })
545
544
  ```
546
545
 
547
546
  ## API 参考
@@ -551,20 +550,23 @@ browser.action.onClicked.addListener(async (tab) => {
551
550
  定义一个 tab proxy service 的辅助函数,自动提供类型提示。
552
551
 
553
552
  **参数:**
553
+
554
554
  - `name: string` - Service 的唯一名称
555
555
  - `init: (...args: TArgs) => TService` - 初始化函数,返回服务实例
556
556
  - `config?: TabProxyServiceConfig` - 可选配置
557
557
 
558
558
  **返回:** `[registerService, getService]` - 返回元组
559
+
559
560
  - `registerService: (...args: TArgs) => Promise<() => void>` - 注册服务函数
560
561
  - `getService: (options?) => TabProxyService<TService>` - 获取服务代理函数
561
562
 
562
563
  **示例:**
564
+
563
565
  ```ts
564
566
  export const [registerHelloService, getHelloService] = defineTabProxyService(
565
567
  'hello-service',
566
568
  () => new HelloService()
567
- );
569
+ )
568
570
  ```
569
571
 
570
572
  ### `initTabServiceManager(config?)`
@@ -572,6 +574,7 @@ export const [registerHelloService, getHelloService] = defineTabProxyService(
572
574
  在 background 中初始化 tab service 管理器。必须在使用其他功能前调用。
573
575
 
574
576
  **参数:**
577
+
575
578
  - `config?: TabProxyServiceConfig` - 可选配置
576
579
  - `logger?: Console` - 日志记录器
577
580
  - `timeout?: number` - 消息超时时间(毫秒)
@@ -581,6 +584,7 @@ export const [registerHelloService, getHelloService] = defineTabProxyService(
581
584
  在 content script 中注册一个服务。
582
585
 
583
586
  **参数:**
587
+
584
588
  - `key: string | TabProxyServiceKey<T>` - Service key
585
589
  - `service: T` - 服务实例
586
590
  - `config?: TabProxyServiceConfig` - 可选配置
@@ -592,6 +596,7 @@ export const [registerHelloService, getHelloService] = defineTabProxyService(
592
596
  创建一个 tab service 的代理。
593
597
 
594
598
  **参数:**
599
+
595
600
  - `key: string | TabProxyServiceKey<T>` - Service key
596
601
  - `options?`:
597
602
  - `targetTabId?: number` - 目标 tab ID(可选)
@@ -610,6 +615,7 @@ export const [registerHelloService, getHelloService] = defineTabProxyService(
610
615
  扁平化 Promise,用于简化处理 `Promise<Dependency>`。
611
616
 
612
617
  **参数:**
618
+
613
619
  - `promise: Promise<T>` - 要扁平化的 Promise
614
620
 
615
621
  **返回:** `TabProxyService<T>` - 扁平化后的代理
package/lib/index.d.ts CHANGED
@@ -24,8 +24,7 @@ type TabProxyService<T> = T extends DeepAsync<T> ? T : DeepAsync<T>;
24
24
  /**
25
25
  * 约束 service key 的类型
26
26
  */
27
- interface TabProxyServiceConstraint<_> {
28
- }
27
+ type TabProxyServiceConstraint<_> = Record<string, unknown>;
29
28
  /**
30
29
  * 带有类型约束的 service key,用于确保类型安全
31
30
  *
package/lib/index.js CHANGED
@@ -111,7 +111,6 @@ function initTabServiceManager(config) {
111
111
  const tabId = sender.tab.id;
112
112
  const serviceKey = message.serviceKey;
113
113
  registry.register(tabId, serviceKey);
114
- logger?.debug(`[TabProxyService] Service "${serviceKey}" registered in tab ${tabId}`);
115
114
  return Promise.resolve({ success: true });
116
115
  }
117
116
  case MESSAGE_TYPES.UNREGISTER_TAB_SERVICE: {
@@ -243,7 +242,7 @@ function createProxy(serviceKey, targetTabId, config, path) {
243
242
  timestamp: Date.now(),
244
243
  requestId
245
244
  };
246
- let actualTabId = targetTabId;
245
+ const actualTabId = targetTabId;
247
246
  if (!actualTabId) {
248
247
  throw new Error(
249
248
  `targetTabId is required when calling service "${serviceKey}" from background. Please specify the target tab ID explicitly.`
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import Browser from 'webextension-polyfill'\n\nimport type {\n ProxyMessage,\n ProxyResponse,\n Service,\n ServiceCallContext,\n TabProxyService,\n TabProxyServiceConfig,\n TabProxyServiceKey,\n TabServiceInfo\n} from './types'\n\n// 重新导出类型,供外部使用\nexport type {\n Service,\n ServiceCallContext,\n TabProxyService,\n TabProxyServiceKey,\n TabProxyServiceConfig,\n TabServiceInfo,\n ProxyMessage,\n ProxyResponse,\n Promisify,\n DeepAsync\n} from './types'\n\n/**\n * 消息类型枚举\n */\nconst MESSAGE_TYPES = {\n /** Content script 向 background 注册 service */\n REGISTER_TAB_SERVICE: 'tab-proxy:register',\n /** Content script 向 background 注销 service */\n UNREGISTER_TAB_SERVICE: 'tab-proxy:unregister',\n /** 调用 tab 中的 service */\n CALL_TAB_SERVICE: 'tab-proxy:call',\n /** Background 转发消息到目标 tab */\n FORWARD_TO_TAB: 'tab-proxy:forward',\n /** 查询已注册的 tab services */\n QUERY_TAB_SERVICES: 'tab-proxy:query'\n} as const\n\n/**\n * 全局状态管理\n */\nclass TabServiceRegistry {\n /** 存储所有已注册的 tab services: serviceKey -> Set<tabId> */\n private services = new Map<string, Set<number>>()\n\n /** 存储 service 监听器: tabId -> Map<serviceKey, listener> */\n private listeners = new Map<number, Map<string, (message: any) => any>>()\n\n /**\n * 注册一个 tab service\n */\n register(tabId: number, serviceKey: string): void {\n if (!this.services.has(serviceKey)) {\n this.services.set(serviceKey, new Set())\n }\n this.services.get(serviceKey)!.add(tabId)\n }\n\n /**\n * 注销一个 tab service\n */\n unregister(tabId: number, serviceKey: string): void {\n const tabs = this.services.get(serviceKey)\n if (tabs) {\n tabs.delete(tabId)\n if (tabs.size === 0) {\n this.services.delete(serviceKey)\n }\n }\n }\n\n /**\n * 注销某个 tab 的所有 services\n */\n unregisterAll(tabId: number): void {\n for (const [serviceKey, tabs] of this.services.entries()) {\n tabs.delete(tabId)\n if (tabs.size === 0) {\n this.services.delete(serviceKey)\n }\n }\n this.listeners.delete(tabId)\n }\n\n /**\n * 获取某个 service 的所有 tab IDs\n */\n getTabs(serviceKey: string): number[] {\n return Array.from(this.services.get(serviceKey) || [])\n }\n\n /**\n * 获取所有已注册的 services 信息\n */\n getAllServices(): TabServiceInfo[] {\n const result: TabServiceInfo[] = []\n const now = Date.now()\n\n for (const [serviceKey, tabs] of this.services.entries()) {\n for (const tabId of tabs) {\n result.push({\n tabId,\n serviceKey,\n registeredAt: now // 简化处理,实际可以记录真实注册时间\n })\n }\n }\n\n return result\n }\n\n /**\n * 存储监听器\n */\n setListener(tabId: number, serviceKey: string, listener: (message: any) => any): void {\n if (!this.listeners.has(tabId)) {\n this.listeners.set(tabId, new Map())\n }\n this.listeners.get(tabId)!.set(serviceKey, listener)\n }\n\n /**\n * 获取监听器\n */\n getListener(tabId: number, serviceKey: string): ((message: any) => any) | undefined {\n return this.listeners.get(tabId)?.get(serviceKey)\n }\n}\n\n// 全局 registry 实例(仅在 background 中使用)\nconst registry = new TabServiceRegistry()\n\n// 标记当前是否在 background 环境中\nlet isInBackground = false\n\n// 全局 logger 实例\nlet globalLogger: Pick<Console, 'log' | 'warn' | 'error' | 'debug'> | undefined\n\n/**\n * 在 background 中初始化 tab service 管理器\n * 必须在 background script 启动时调用\n */\nexport function initTabServiceManager(config?: TabProxyServiceConfig): void {\n isInBackground = true\n\n const logger = config?.logger\n\n globalLogger = logger\n\n // 监听来自 content scripts 的注册/注销消息\n Browser.runtime.onMessage.addListener((message, sender) => {\n switch (message.type) {\n case MESSAGE_TYPES.REGISTER_TAB_SERVICE: {\n // 注册操作必须来自 content script(需要 tab.id)\n if (!sender.tab?.id) {\n return Promise.reject(\n new Error('REGISTER_TAB_SERVICE must be called from a content script')\n )\n }\n const tabId = sender.tab.id\n const serviceKey = message.serviceKey\n registry.register(tabId, serviceKey)\n logger?.debug(`[TabProxyService] Service \"${serviceKey}\" registered in tab ${tabId}`)\n return Promise.resolve({ success: true })\n }\n\n case MESSAGE_TYPES.UNREGISTER_TAB_SERVICE: {\n // 注销操作必须来自 content script(需要 tab.id)\n if (!sender.tab?.id) {\n return Promise.reject(\n new Error('UNREGISTER_TAB_SERVICE must be called from a content script')\n )\n }\n const tabId = sender.tab.id\n const serviceKey = message.serviceKey\n registry.unregister(tabId, serviceKey)\n logger?.debug(`[TabProxyService] Service \"${serviceKey}\" unregistered from tab ${tabId}`)\n return Promise.resolve({ success: true })\n }\n\n case MESSAGE_TYPES.CALL_TAB_SERVICE: {\n // 调用操作可以来自任何地方(popup、background、content script)\n const { serviceKey, targetTabId, payload } = message\n const callerId = sender.tab?.id ?? 'popup/background'\n logger?.debug(\n `[TabProxyService] CALL_TAB_SERVICE for service \"${serviceKey}\" from ${callerId}`\n )\n\n // 生成请求 ID 用于追踪\n const requestId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n\n // 添加原始 sender 信息到 payload\n const enrichedPayload: ProxyMessage = {\n ...payload,\n originalSender: sender,\n timestamp: Date.now(),\n requestId\n }\n\n // 如果指定了目标 tab,直接转发\n if (targetTabId !== undefined) {\n return forwardToTab(targetTabId, serviceKey, enrichedPayload, logger)\n }\n\n // 否则选择任意一个可用的 tab\n const tabs = registry.getTabs(serviceKey)\n if (tabs.length === 0) {\n return Promise.reject(new Error(`No tab found with service \"${serviceKey}\" registered`))\n }\n\n // 使用第一个可用的 tab\n return forwardToTab(tabs[0], serviceKey, enrichedPayload, logger)\n }\n\n case MESSAGE_TYPES.QUERY_TAB_SERVICES: {\n // 查询操作可以来自任何地方\n return Promise.resolve(registry.getAllServices())\n }\n }\n })\n\n // 监听 tab 关闭事件,清理注册信息\n Browser.tabs.onRemoved.addListener((tabId) => {\n registry.unregisterAll(tabId)\n logger?.debug(`[TabProxyService] All services unregistered from tab ${tabId}`)\n })\n\n logger?.debug('[TabProxyService] Manager initialized')\n}\n\n/**\n * 转发消息到指定 tab\n */\nasync function forwardToTab(\n tabId: number,\n serviceKey: string,\n payload: ProxyMessage,\n logger?: Pick<Console, 'log' | 'warn' | 'error' | 'debug'>\n): Promise<ProxyResponse> {\n try {\n const response = await Browser.tabs.sendMessage(tabId, {\n type: MESSAGE_TYPES.FORWARD_TO_TAB,\n timestamp: Date.now(),\n serviceKey,\n payload\n })\n return response\n } catch (error) {\n logger?.error(`[TabProxyService] Failed to forward message to tab ${tabId}:`, error)\n throw error\n }\n}\n\n/**\n * 在 content script 中注册一个 service\n *\n * @param key Service key\n * @param realService 实际的 service 实例\n * @param config 配置选项\n * @returns 返回清理函数,调用后注销 service\n *\n * @example\n * ```ts\n * // content-script.ts\n * import { registerTabService } from '@northsea4/tab-proxy-service';\n *\n * const myService = {\n * async getData() {\n * return [1, 2, 3];\n * }\n * };\n *\n * const cleanup = await registerTabService('my-service', myService);\n *\n * // 页面卸载时清理\n * window.addEventListener('beforeunload', cleanup);\n * ```\n */\nexport async function registerTabService<\n T extends Service,\n K extends string = TabProxyServiceKey<T> | string\n>(key: K, realService: T, config?: TabProxyServiceConfig): Promise<() => void> {\n const logger = config?.logger\n\n // 向 background 注册\n await Browser.runtime.sendMessage({\n type: MESSAGE_TYPES.REGISTER_TAB_SERVICE,\n timestamp: Date.now(),\n serviceKey: key\n })\n\n logger?.debug(`[TabProxyService] Registered service \"${key}\" in content script`)\n\n // 监听来自 background 的转发消息\n const messageListener = (message: any, sender: Browser.Runtime.MessageSender) => {\n logger?.debug(`[TabProxyService] Received message in service \"${key}\":`, message, sender)\n\n // 先检查消息类型,如果不匹配,立即返回 undefined(不是 Promise)\n if (message.type !== MESSAGE_TYPES.FORWARD_TO_TAB || message.serviceKey !== key) {\n return // 返回 undefined,不是 Promise<undefined>\n }\n\n return (async () => {\n const payload: ProxyMessage = message.payload\n\n try {\n // 获取要调用的方法\n const method = payload.path == null ? realService : get(realService, payload.path)\n\n if (typeof method !== 'function') {\n throw new Error(`Method not found: ${payload.path?.join('.')}`)\n }\n\n // 构造 ServiceCallContext 对象\n const context: ServiceCallContext = {\n sender,\n originalSender: payload.originalSender,\n timestamp: payload.timestamp,\n requestId: payload.requestId,\n metadata: {}\n }\n\n // 调用方法并返回结果,将 context 作为最后一个参数传递\n const result = await Promise.resolve(method.bind(realService)(...payload.args, context))\n return result\n } catch (error) {\n logger?.error(`[TabProxyService] Error executing service \"${key}\":`, error)\n throw error\n }\n })()\n }\n\n Browser.runtime.onMessage.addListener(messageListener)\n\n // 返回清理函数\n return () => {\n Browser.runtime.onMessage.removeListener(messageListener)\n Browser.runtime\n .sendMessage({\n type: MESSAGE_TYPES.UNREGISTER_TAB_SERVICE,\n serviceKey: key\n })\n .catch(() => {\n // 忽略错误,可能 background 已经关闭\n })\n logger?.debug(`[TabProxyService] Unregistered service \"${key}\"`)\n }\n}\n\n/**\n * 创建一个 tab service 的代理\n * 可以在 background、popup 或其他 content script 中调用\n *\n * @param key Service key\n * @param options 可选参数:targetTabId 指定目标 tab,config 配置\n * @returns 返回 service 代理\n *\n * @example\n * ```ts\n * // background.ts - 调用任意 tab 中的 service\n * import { createTabProxyService } from '@northsea4/tab-proxy-service';\n *\n * const myService = createTabProxyService<MyService>('my-service');\n * const data = await myService.getData();\n *\n * // 或者指定特定的 tab\n * const myServiceInTab = createTabProxyService<MyService>('my-service', { targetTabId: 123 });\n * ```\n *\n * @example\n * ```ts\n * // popup.ts - 从 popup 调用 tab 中的 service\n * const myService = createTabProxyService<MyService>('my-service', { targetTabId: 123 });\n * const result = await myService.someMethod();\n * ```\n *\n * @example\n * ```ts\n * // content-script-a.ts - 调用另一个 tab 中的 service\n * const serviceInOtherTab = createTabProxyService<OtherService>('other-service', { targetTabId: 456 });\n * await serviceInOtherTab.doSomething();\n * ```\n */\nexport function createTabProxyService<T extends Service>(\n key: TabProxyServiceKey<T> | string,\n options?: {\n targetTabId?: number\n config?: TabProxyServiceConfig\n }\n): TabProxyService<T> {\n const targetTabId = options?.targetTabId\n const config = options?.config\n\n return createProxy(key, targetTabId, config)\n}\n\n/**\n * 创建深层代理对象\n * 所有属性访问都返回新的代理,函数调用时发送消息到 background\n */\nfunction createProxy<T>(\n serviceKey: string,\n targetTabId: number | undefined,\n config: TabProxyServiceConfig | undefined,\n path?: string[]\n): TabProxyService<T> {\n const wrapped = (() => {}) as TabProxyService<T>\n\n const proxy = new Proxy(wrapped, {\n async apply(_target, _thisArg, args) {\n const payload: ProxyMessage = {\n path,\n args\n }\n\n // 如果在 background 环境中,直接调用内部转发函数,不需要通过消息传递\n if (isInBackground) {\n const logger = config?.logger || globalLogger\n\n // 生成请求 ID 用于追踪\n const requestId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n\n const enrichedPayload: ProxyMessage = {\n ...payload,\n // 在 background 中直接调用时,originalSender 为 undefined\n originalSender: undefined,\n timestamp: Date.now(),\n requestId\n }\n\n // 确定目标 tab\n let actualTabId = targetTabId\n if (!actualTabId) {\n // 在 background 中必须明确指定 targetTabId,不能随机选择\n throw new Error(\n `targetTabId is required when calling service \"${serviceKey}\" from background. ` +\n `Please specify the target tab ID explicitly.`\n )\n }\n\n logger?.debug(\n `[TabProxyService] Direct call from background to service \"${serviceKey}\" in tab ${actualTabId}`\n )\n return await forwardToTab(actualTabId, serviceKey, enrichedPayload, logger)\n }\n\n // 发送消息到 background,由 background 转发到对应的 tab\n try {\n const response = await Browser.runtime.sendMessage({\n type: MESSAGE_TYPES.CALL_TAB_SERVICE,\n timestamp: Date.now(),\n serviceKey,\n targetTabId,\n payload\n })\n return response\n } catch (error) {\n config?.logger?.error(`[TabProxyService] Error calling service \"${serviceKey}\":`, error)\n throw error\n }\n },\n\n get(target, propertyName, receiver) {\n // 返回 symbol 属性的值\n if (typeof propertyName === 'symbol') {\n return Reflect.get(target, propertyName, receiver)\n }\n\n // 为常规属性返回新的代理\n return createProxy(\n serviceKey,\n targetTabId,\n config,\n path == null ? [propertyName] : path.concat([propertyName])\n )\n }\n })\n\n return proxy\n}\n\n/**\n * 查询所有已注册的 tab services\n *\n * @returns 返回所有已注册的 service 信息\n *\n * @example\n * ```ts\n * const services = await queryTabServices();\n * console.log('Registered services:', services);\n * // [{ tabId: 123, serviceKey: 'my-service', registeredAt: 1234567890 }]\n * ```\n */\nexport async function queryTabServices(): Promise<TabServiceInfo[]> {\n const response = await Browser.runtime.sendMessage({\n type: MESSAGE_TYPES.QUERY_TAB_SERVICES\n })\n return response\n}\n\n/**\n * 从对象中按路径获取值\n */\nfunction get(obj: any, path: string[]): any {\n if (path.length === 0) {\n return obj\n }\n return path.reduce((acc, key) => acc?.[key], obj)\n}\n\n/**\n * 定义一个 tab proxy service 的辅助函数\n * 类似于 @northsea4/proxy-service 的 defineProxyService\n *\n * @param name Service 的唯一名称\n * @param init 初始化函数,返回实际的 service 实例\n * @param config 配置选项\n * @returns 返回 [registerService, getService] 元组\n *\n * @example\n * ```ts\n * // hello-service.ts\n * export const [registerHelloService, getHelloService] = defineTabProxyService(\n * 'hello-service',\n * () => ({\n * async sayHello(name: string) {\n * return `Hello, ${name}!`;\n * }\n * })\n * );\n *\n * // content-script.ts - 注册服务\n * const cleanup = await registerHelloService();\n *\n * // background.ts - 使用服务(自动有类型提示)\n * const helloService = getHelloService({ targetTabId: 123 });\n * const greeting = await helloService.sayHello('World'); // 类型安全!\n * ```\n */\nexport function defineTabProxyService<TService extends Service, TArgs extends any[]>(\n name: string,\n init: (...args: TArgs) => TService,\n config?: TabProxyServiceConfig\n): [\n registerService: (...args: TArgs) => Promise<() => void>,\n getService: (options?: {\n targetTabId?: number\n config?: TabProxyServiceConfig\n }) => TabProxyService<TService>\n] {\n const key = name as TabProxyServiceKey<TService>\n\n return [\n // registerService\n async (...args: TArgs): Promise<() => void> => {\n const service = init(...args)\n return await registerTabService(key, service, config)\n },\n // getService\n (options?: { targetTabId?: number; config?: TabProxyServiceConfig }) =>\n createTabProxyService<TService>(key, options)\n ]\n}\n\n/**\n * 辅助函数:扁平化 Promise\n * 用于简化处理 Promise<Dependency>\n *\n * @example\n * ```ts\n * function createService(dependencyPromise: Promise<SomeDependency>) {\n * const dependency = flattenPromise(dependencyPromise);\n *\n * return {\n * async doSomething() {\n * await dependency.someAsyncWork();\n * // 而不是 await (await dependencyPromise).someAsyncWork();\n * }\n * }\n * }\n * ```\n */\nexport function flattenPromise<T>(promise: Promise<T>): TabProxyService<T> {\n return new Proxy({} as TabProxyService<T>, {\n get(_, prop) {\n if (typeof prop === 'symbol') {\n return undefined\n }\n return async (...args: any[]) => {\n const resolved = await promise\n const method = (resolved as any)[prop]\n if (typeof method === 'function') {\n return method.apply(resolved, args)\n }\n return method\n }\n }\n })\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import Browser from 'webextension-polyfill'\n\nimport type {\n ProxyMessage,\n ProxyResponse,\n Service,\n ServiceCallContext,\n TabProxyService,\n TabProxyServiceConfig,\n TabProxyServiceKey,\n TabServiceInfo\n} from './types'\n\n// 重新导出类型,供外部使用\nexport type {\n Service,\n ServiceCallContext,\n TabProxyService,\n TabProxyServiceKey,\n TabProxyServiceConfig,\n TabServiceInfo,\n ProxyMessage,\n ProxyResponse,\n Promisify,\n DeepAsync\n} from './types'\n\n/**\n * 消息类型枚举\n */\nconst MESSAGE_TYPES = {\n /** Content script 向 background 注册 service */\n REGISTER_TAB_SERVICE: 'tab-proxy:register',\n /** Content script 向 background 注销 service */\n UNREGISTER_TAB_SERVICE: 'tab-proxy:unregister',\n /** 调用 tab 中的 service */\n CALL_TAB_SERVICE: 'tab-proxy:call',\n /** Background 转发消息到目标 tab */\n FORWARD_TO_TAB: 'tab-proxy:forward',\n /** 查询已注册的 tab services */\n QUERY_TAB_SERVICES: 'tab-proxy:query'\n} as const\n\n/**\n * 全局状态管理\n */\nclass TabServiceRegistry {\n /** 存储所有已注册的 tab services: serviceKey -> Set<tabId> */\n private services = new Map<string, Set<number>>()\n\n /** 存储 service 监听器: tabId -> Map<serviceKey, listener> */\n private listeners = new Map<number, Map<string, (message: any) => any>>()\n\n /**\n * 注册一个 tab service\n */\n register(tabId: number, serviceKey: string): void {\n if (!this.services.has(serviceKey)) {\n this.services.set(serviceKey, new Set())\n }\n this.services.get(serviceKey)!.add(tabId)\n }\n\n /**\n * 注销一个 tab service\n */\n unregister(tabId: number, serviceKey: string): void {\n const tabs = this.services.get(serviceKey)\n if (tabs) {\n tabs.delete(tabId)\n if (tabs.size === 0) {\n this.services.delete(serviceKey)\n }\n }\n }\n\n /**\n * 注销某个 tab 的所有 services\n */\n unregisterAll(tabId: number): void {\n for (const [serviceKey, tabs] of this.services.entries()) {\n tabs.delete(tabId)\n if (tabs.size === 0) {\n this.services.delete(serviceKey)\n }\n }\n this.listeners.delete(tabId)\n }\n\n /**\n * 获取某个 service 的所有 tab IDs\n */\n getTabs(serviceKey: string): number[] {\n return Array.from(this.services.get(serviceKey) || [])\n }\n\n /**\n * 获取所有已注册的 services 信息\n */\n getAllServices(): TabServiceInfo[] {\n const result: TabServiceInfo[] = []\n const now = Date.now()\n\n for (const [serviceKey, tabs] of this.services.entries()) {\n for (const tabId of tabs) {\n result.push({\n tabId,\n serviceKey,\n registeredAt: now // 简化处理,实际可以记录真实注册时间\n })\n }\n }\n\n return result\n }\n\n /**\n * 存储监听器\n */\n setListener(tabId: number, serviceKey: string, listener: (message: any) => any): void {\n if (!this.listeners.has(tabId)) {\n this.listeners.set(tabId, new Map())\n }\n this.listeners.get(tabId)!.set(serviceKey, listener)\n }\n\n /**\n * 获取监听器\n */\n getListener(tabId: number, serviceKey: string): ((message: any) => any) | undefined {\n return this.listeners.get(tabId)?.get(serviceKey)\n }\n}\n\n// 全局 registry 实例(仅在 background 中使用)\nconst registry = new TabServiceRegistry()\n\n// 标记当前是否在 background 环境中\nlet isInBackground = false\n\n// 全局 logger 实例\nlet globalLogger: Pick<Console, 'log' | 'warn' | 'error' | 'debug'> | undefined\n\n/**\n * 在 background 中初始化 tab service 管理器\n * 必须在 background script 启动时调用\n */\nexport function initTabServiceManager(config?: TabProxyServiceConfig): void {\n isInBackground = true\n\n const logger = config?.logger\n\n globalLogger = logger\n\n // 监听来自 content scripts 的注册/注销消息\n Browser.runtime.onMessage.addListener((message, sender) => {\n switch (message.type) {\n case MESSAGE_TYPES.REGISTER_TAB_SERVICE: {\n // 注册操作必须来自 content script(需要 tab.id)\n if (!sender.tab?.id) {\n return Promise.reject(\n new Error('REGISTER_TAB_SERVICE must be called from a content script')\n )\n }\n const tabId = sender.tab.id\n const serviceKey = message.serviceKey\n registry.register(tabId, serviceKey)\n // logger?.debug(`[TabProxyService] Service \"${serviceKey}\" registered in tab ${tabId}`)\n return Promise.resolve({ success: true })\n }\n\n case MESSAGE_TYPES.UNREGISTER_TAB_SERVICE: {\n // 注销操作必须来自 content script(需要 tab.id)\n if (!sender.tab?.id) {\n return Promise.reject(\n new Error('UNREGISTER_TAB_SERVICE must be called from a content script')\n )\n }\n const tabId = sender.tab.id\n const serviceKey = message.serviceKey\n registry.unregister(tabId, serviceKey)\n logger?.debug(`[TabProxyService] Service \"${serviceKey}\" unregistered from tab ${tabId}`)\n return Promise.resolve({ success: true })\n }\n\n case MESSAGE_TYPES.CALL_TAB_SERVICE: {\n // 调用操作可以来自任何地方(popup、background、content script)\n const { serviceKey, targetTabId, payload } = message\n const callerId = sender.tab?.id ?? 'popup/background'\n logger?.debug(\n `[TabProxyService] CALL_TAB_SERVICE for service \"${serviceKey}\" from ${callerId}`\n )\n\n // 生成请求 ID 用于追踪\n const requestId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n\n // 添加原始 sender 信息到 payload\n const enrichedPayload: ProxyMessage = {\n ...payload,\n originalSender: sender,\n timestamp: Date.now(),\n requestId\n }\n\n // 如果指定了目标 tab,直接转发\n if (targetTabId !== undefined) {\n return forwardToTab(targetTabId, serviceKey, enrichedPayload, logger)\n }\n\n // 否则选择任意一个可用的 tab\n const tabs = registry.getTabs(serviceKey)\n if (tabs.length === 0) {\n return Promise.reject(new Error(`No tab found with service \"${serviceKey}\" registered`))\n }\n\n // 使用第一个可用的 tab\n return forwardToTab(tabs[0], serviceKey, enrichedPayload, logger)\n }\n\n case MESSAGE_TYPES.QUERY_TAB_SERVICES: {\n // 查询操作可以来自任何地方\n return Promise.resolve(registry.getAllServices())\n }\n }\n })\n\n // 监听 tab 关闭事件,清理注册信息\n Browser.tabs.onRemoved.addListener((tabId) => {\n registry.unregisterAll(tabId)\n logger?.debug(`[TabProxyService] All services unregistered from tab ${tabId}`)\n })\n\n logger?.debug('[TabProxyService] Manager initialized')\n}\n\n/**\n * 转发消息到指定 tab\n */\nasync function forwardToTab(\n tabId: number,\n serviceKey: string,\n payload: ProxyMessage,\n logger?: Pick<Console, 'log' | 'warn' | 'error' | 'debug'>\n): Promise<ProxyResponse> {\n try {\n const response = await Browser.tabs.sendMessage(tabId, {\n type: MESSAGE_TYPES.FORWARD_TO_TAB,\n timestamp: Date.now(),\n serviceKey,\n payload\n })\n return response\n } catch (error) {\n logger?.error(`[TabProxyService] Failed to forward message to tab ${tabId}:`, error)\n throw error\n }\n}\n\n/**\n * 在 content script 中注册一个 service\n *\n * @param key Service key\n * @param realService 实际的 service 实例\n * @param config 配置选项\n * @returns 返回清理函数,调用后注销 service\n *\n * @example\n * ```ts\n * // content-script.ts\n * import { registerTabService } from '@northsea4/tab-proxy-service';\n *\n * const myService = {\n * async getData() {\n * return [1, 2, 3];\n * }\n * };\n *\n * const cleanup = await registerTabService('my-service', myService);\n *\n * // 页面卸载时清理\n * window.addEventListener('beforeunload', cleanup);\n * ```\n */\nexport async function registerTabService<\n T extends Service,\n K extends string = TabProxyServiceKey<T> | string\n>(key: K, realService: T, config?: TabProxyServiceConfig): Promise<() => void> {\n const logger = config?.logger\n\n // 向 background 注册\n await Browser.runtime.sendMessage({\n type: MESSAGE_TYPES.REGISTER_TAB_SERVICE,\n timestamp: Date.now(),\n serviceKey: key\n })\n\n logger?.debug(`[TabProxyService] Registered service \"${key}\" in content script`)\n\n // 监听来自 background 的转发消息\n const messageListener = (message: any, sender: Browser.Runtime.MessageSender) => {\n logger?.debug(`[TabProxyService] Received message in service \"${key}\":`, message, sender)\n\n // 先检查消息类型,如果不匹配,立即返回 undefined(不是 Promise)\n if (message.type !== MESSAGE_TYPES.FORWARD_TO_TAB || message.serviceKey !== key) {\n return // 返回 undefined,不是 Promise<undefined>\n }\n\n return (async () => {\n const payload: ProxyMessage = message.payload\n\n try {\n // 获取要调用的方法\n const method = payload.path == null ? realService : get(realService, payload.path)\n\n if (typeof method !== 'function') {\n throw new Error(`Method not found: ${payload.path?.join('.')}`)\n }\n\n // 构造 ServiceCallContext 对象\n const context: ServiceCallContext = {\n sender,\n originalSender: payload.originalSender,\n timestamp: payload.timestamp,\n requestId: payload.requestId,\n metadata: {}\n }\n\n // 调用方法并返回结果,将 context 作为最后一个参数传递\n const result = await Promise.resolve(method.bind(realService)(...payload.args, context))\n return result\n } catch (error) {\n logger?.error(`[TabProxyService] Error executing service \"${key}\":`, error)\n throw error\n }\n })()\n }\n\n Browser.runtime.onMessage.addListener(messageListener)\n\n // 返回清理函数\n return () => {\n Browser.runtime.onMessage.removeListener(messageListener)\n Browser.runtime\n .sendMessage({\n type: MESSAGE_TYPES.UNREGISTER_TAB_SERVICE,\n serviceKey: key\n })\n .catch(() => {\n // 忽略错误,可能 background 已经关闭\n })\n logger?.debug(`[TabProxyService] Unregistered service \"${key}\"`)\n }\n}\n\n/**\n * 创建一个 tab service 的代理\n * 可以在 background、popup 或其他 content script 中调用\n *\n * @param key Service key\n * @param options 可选参数:targetTabId 指定目标 tab,config 配置\n * @returns 返回 service 代理\n *\n * @example\n * ```ts\n * // background.ts - 调用任意 tab 中的 service\n * import { createTabProxyService } from '@northsea4/tab-proxy-service';\n *\n * const myService = createTabProxyService<MyService>('my-service');\n * const data = await myService.getData();\n *\n * // 或者指定特定的 tab\n * const myServiceInTab = createTabProxyService<MyService>('my-service', { targetTabId: 123 });\n * ```\n *\n * @example\n * ```ts\n * // popup.ts - 从 popup 调用 tab 中的 service\n * const myService = createTabProxyService<MyService>('my-service', { targetTabId: 123 });\n * const result = await myService.someMethod();\n * ```\n *\n * @example\n * ```ts\n * // content-script-a.ts - 调用另一个 tab 中的 service\n * const serviceInOtherTab = createTabProxyService<OtherService>('other-service', { targetTabId: 456 });\n * await serviceInOtherTab.doSomething();\n * ```\n */\nexport function createTabProxyService<T extends Service>(\n key: TabProxyServiceKey<T> | string,\n options?: {\n targetTabId?: number\n config?: TabProxyServiceConfig\n }\n): TabProxyService<T> {\n const targetTabId = options?.targetTabId\n const config = options?.config\n\n return createProxy(key, targetTabId, config)\n}\n\n/**\n * 创建深层代理对象\n * 所有属性访问都返回新的代理,函数调用时发送消息到 background\n */\nfunction createProxy<T>(\n serviceKey: string,\n targetTabId: number | undefined,\n config: TabProxyServiceConfig | undefined,\n path?: string[]\n): TabProxyService<T> {\n const wrapped = (() => {}) as TabProxyService<T>\n\n const proxy = new Proxy(wrapped, {\n async apply(_target, _thisArg, args) {\n const payload: ProxyMessage = {\n path,\n args\n }\n\n // 如果在 background 环境中,直接调用内部转发函数,不需要通过消息传递\n if (isInBackground) {\n const logger = config?.logger || globalLogger\n\n // 生成请求 ID 用于追踪\n const requestId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n\n const enrichedPayload: ProxyMessage = {\n ...payload,\n // 在 background 中直接调用时,originalSender 为 undefined\n originalSender: undefined,\n timestamp: Date.now(),\n requestId\n }\n\n // 确定目标 tab\n const actualTabId = targetTabId\n if (!actualTabId) {\n // 在 background 中必须明确指定 targetTabId,不能随机选择\n throw new Error(\n `targetTabId is required when calling service \"${serviceKey}\" from background. ` +\n `Please specify the target tab ID explicitly.`\n )\n }\n\n logger?.debug(\n `[TabProxyService] Direct call from background to service \"${serviceKey}\" in tab ${actualTabId}`\n )\n return await forwardToTab(actualTabId, serviceKey, enrichedPayload, logger)\n }\n\n // 发送消息到 background,由 background 转发到对应的 tab\n try {\n const response = await Browser.runtime.sendMessage({\n type: MESSAGE_TYPES.CALL_TAB_SERVICE,\n timestamp: Date.now(),\n serviceKey,\n targetTabId,\n payload\n })\n return response\n } catch (error) {\n config?.logger?.error(`[TabProxyService] Error calling service \"${serviceKey}\":`, error)\n throw error\n }\n },\n\n get(target, propertyName, receiver) {\n // 返回 symbol 属性的值\n if (typeof propertyName === 'symbol') {\n return Reflect.get(target, propertyName, receiver)\n }\n\n // 为常规属性返回新的代理\n return createProxy(\n serviceKey,\n targetTabId,\n config,\n path == null ? [propertyName] : path.concat([propertyName])\n )\n }\n })\n\n return proxy\n}\n\n/**\n * 查询所有已注册的 tab services\n *\n * @returns 返回所有已注册的 service 信息\n *\n * @example\n * ```ts\n * const services = await queryTabServices();\n * console.log('Registered services:', services);\n * // [{ tabId: 123, serviceKey: 'my-service', registeredAt: 1234567890 }]\n * ```\n */\nexport async function queryTabServices(): Promise<TabServiceInfo[]> {\n const response = await Browser.runtime.sendMessage({\n type: MESSAGE_TYPES.QUERY_TAB_SERVICES\n })\n return response\n}\n\n/**\n * 从对象中按路径获取值\n */\nfunction get(obj: any, path: string[]): any {\n if (path.length === 0) {\n return obj\n }\n return path.reduce((acc, key) => acc?.[key], obj)\n}\n\n/**\n * 定义一个 tab proxy service 的辅助函数\n * 类似于 @northsea4/proxy-service 的 defineProxyService\n *\n * @param name Service 的唯一名称\n * @param init 初始化函数,返回实际的 service 实例\n * @param config 配置选项\n * @returns 返回 [registerService, getService] 元组\n *\n * @example\n * ```ts\n * // hello-service.ts\n * export const [registerHelloService, getHelloService] = defineTabProxyService(\n * 'hello-service',\n * () => ({\n * async sayHello(name: string) {\n * return `Hello, ${name}!`;\n * }\n * })\n * );\n *\n * // content-script.ts - 注册服务\n * const cleanup = await registerHelloService();\n *\n * // background.ts - 使用服务(自动有类型提示)\n * const helloService = getHelloService({ targetTabId: 123 });\n * const greeting = await helloService.sayHello('World'); // 类型安全!\n * ```\n */\nexport function defineTabProxyService<TService extends Service, TArgs extends any[]>(\n name: string,\n init: (...args: TArgs) => TService,\n config?: TabProxyServiceConfig\n): [\n registerService: (...args: TArgs) => Promise<() => void>,\n getService: (options?: {\n targetTabId?: number\n config?: TabProxyServiceConfig\n }) => TabProxyService<TService>\n] {\n const key = name as TabProxyServiceKey<TService>\n\n return [\n // registerService\n async (...args: TArgs): Promise<() => void> => {\n const service = init(...args)\n return await registerTabService(key, service, config)\n },\n // getService\n (options?: { targetTabId?: number; config?: TabProxyServiceConfig }) =>\n createTabProxyService<TService>(key, options)\n ]\n}\n\n/**\n * 辅助函数:扁平化 Promise\n * 用于简化处理 Promise<Dependency>\n *\n * @example\n * ```ts\n * function createService(dependencyPromise: Promise<SomeDependency>) {\n * const dependency = flattenPromise(dependencyPromise);\n *\n * return {\n * async doSomething() {\n * await dependency.someAsyncWork();\n * // 而不是 await (await dependencyPromise).someAsyncWork();\n * }\n * }\n * }\n * ```\n */\nexport function flattenPromise<T>(promise: Promise<T>): TabProxyService<T> {\n return new Proxy({} as TabProxyService<T>, {\n get(_, prop) {\n if (typeof prop === 'symbol') {\n return undefined\n }\n return async (...args: any[]) => {\n const resolved = await promise\n const method = (resolved as any)[prop]\n if (typeof method === 'function') {\n return method.apply(resolved, args)\n }\n return method\n }\n }\n })\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@northsea4/tab-proxy-service",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A type-safe wrapper for calling services registered in content scripts from background or other tabs, with automatic type inference",
5
5
  "author": "nornorlunn",
6
6
  "license": "MIT",