@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 +167 -161
- package/lib/index.d.ts +1 -2
- package/lib/index.js +1 -2
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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
|
-
| 功能
|
|
476
|
-
|
|
477
|
-
| 在 Background 注册服务
|
|
478
|
-
| 在 Content Script 注册服务 | ❌
|
|
479
|
-
| Background → Content 调用
|
|
480
|
-
| Content → Background 调用
|
|
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
|
-
|
|
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
|
-
|
|
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":";;;AA8BA,IAAM,aAAA,GAAgB;AAAA;AAAA,EAEpB,oBAAA,EAAsB,oBAAA;AAAA;AAAA,EAEtB,sBAAA,EAAwB,sBAAA;AAAA;AAAA,EAExB,gBAAA,EAAkB,gBAAA;AAAA;AAAA,EAElB,cAAA,EAAgB,mBAAA;AAAA;AAAA,EAEhB,kBAAA,EAAoB;AACtB,CAAA;AAKA,IAAM,qBAAN,MAAyB;AAAA,EAAzB,WAAA,GAAA;AAEE;AAAA,IAAA,IAAA,CAAQ,QAAA,uBAAe,GAAA,EAAyB;AAGhD;AAAA,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAgD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAKxE,QAAA,CAAS,OAAe,UAAA,EAA0B;AAChD,IAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA,EAAG;AAClC,MAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAA,kBAAY,IAAI,KAAK,CAAA;AAAA,IACzC;AACA,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA,CAAG,IAAI,KAAK,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,UAAA,CAAW,OAAe,UAAA,EAA0B;AAClD,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AACzC,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AACjB,MAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AACnB,QAAA,IAAA,CAAK,QAAA,CAAS,OAAO,UAAU,CAAA;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,KAAA,EAAqB;AACjC,IAAA,KAAA,MAAW,CAAC,UAAA,EAAY,IAAI,KAAK,IAAA,CAAK,QAAA,CAAS,SAAQ,EAAG;AACxD,MAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AACjB,MAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AACnB,QAAA,IAAA,CAAK,QAAA,CAAS,OAAO,UAAU,CAAA;AAAA,MACjC;AAAA,IACF;AACA,IAAA,IAAA,CAAK,SAAA,CAAU,OAAO,KAAK,CAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,UAAA,EAA8B;AACpC,IAAA,OAAO,KAAA,CAAM,KAAK,IAAA,CAAK,QAAA,CAAS,IAAI,UAAU,CAAA,IAAK,EAAE,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,cAAA,GAAmC;AACjC,IAAA,MAAM,SAA2B,EAAC;AAClC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,IAAA,KAAA,MAAW,CAAC,UAAA,EAAY,IAAI,KAAK,IAAA,CAAK,QAAA,CAAS,SAAQ,EAAG;AACxD,MAAA,KAAA,MAAW,SAAS,IAAA,EAAM;AACxB,QAAA,MAAA,CAAO,IAAA,CAAK;AAAA,UACV,KAAA;AAAA,UACA,UAAA;AAAA,UACA,YAAA,EAAc;AAAA;AAAA,SACf,CAAA;AAAA,MACH;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAA,CAAY,KAAA,EAAe,UAAA,EAAoB,QAAA,EAAuC;AACpF,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,kBAAO,IAAI,KAAK,CAAA;AAAA,IACrC;AACA,IAAA,IAAA,CAAK,UAAU,GAAA,CAAI,KAAK,CAAA,CAAG,GAAA,CAAI,YAAY,QAAQ,CAAA;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,WAAA,CAAY,OAAe,UAAA,EAAyD;AAClF,IAAA,OAAO,KAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,IAAI,UAAU,CAAA;AAAA,EAClD;AACF,CAAA;AAGA,IAAM,QAAA,GAAW,IAAI,kBAAA,EAAmB;AAGxC,IAAI,cAAA,GAAiB,KAAA;AAGrB,IAAI,YAAA;AAMG,SAAS,sBAAsB,MAAA,EAAsC;AAC1E,EAAA,cAAA,GAAiB,IAAA;AAEjB,EAAA,MAAM,SAAS,MAAA,EAAQ,MAAA;AAEvB,EAAA,YAAA,GAAe,MAAA;AAGf,EAAA,OAAA,CAAQ,OAAA,CAAQ,SAAA,CAAU,WAAA,CAAY,CAAC,SAAS,MAAA,KAAW;AACzD,IAAA,QAAQ,QAAQ,IAAA;AAAM,MACpB,KAAK,cAAc,oBAAA,EAAsB;AAEvC,QAAA,IAAI,CAAC,MAAA,CAAO,GAAA,EAAK,EAAA,EAAI;AACnB,UAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,YACb,IAAI,MAAM,2DAA2D;AAAA,WACvE;AAAA,QACF;AACA,QAAA,MAAM,KAAA,GAAQ,OAAO,GAAA,CAAI,EAAA;AACzB,QAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAC3B,QAAA,QAAA,CAAS,QAAA,CAAS,OAAO,UAAU,CAAA;AACnC,QAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,2BAAA,EAA8B,UAAU,CAAA,oBAAA,EAAuB,KAAK,CAAA,CAAE,CAAA;AACpF,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,MAC1C;AAAA,MAEA,KAAK,cAAc,sBAAA,EAAwB;AAEzC,QAAA,IAAI,CAAC,MAAA,CAAO,GAAA,EAAK,EAAA,EAAI;AACnB,UAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,YACb,IAAI,MAAM,6DAA6D;AAAA,WACzE;AAAA,QACF;AACA,QAAA,MAAM,KAAA,GAAQ,OAAO,GAAA,CAAI,EAAA;AACzB,QAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAC3B,QAAA,QAAA,CAAS,UAAA,CAAW,OAAO,UAAU,CAAA;AACrC,QAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,2BAAA,EAA8B,UAAU,CAAA,wBAAA,EAA2B,KAAK,CAAA,CAAE,CAAA;AACxF,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,MAC1C;AAAA,MAEA,KAAK,cAAc,gBAAA,EAAkB;AAEnC,QAAA,MAAM,EAAE,UAAA,EAAY,WAAA,EAAa,OAAA,EAAQ,GAAI,OAAA;AAC7C,QAAA,MAAM,QAAA,GAAW,MAAA,CAAO,GAAA,EAAK,EAAA,IAAM,kBAAA;AACnC,QAAA,MAAA,EAAQ,KAAA;AAAA,UACN,CAAA,gDAAA,EAAmD,UAAU,CAAA,OAAA,EAAU,QAAQ,CAAA;AAAA,SACjF;AAGA,QAAA,MAAM,SAAA,GAAY,CAAA,EAAG,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,SAAA,CAAU,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAG9E,QAAA,MAAM,eAAA,GAAgC;AAAA,UACpC,GAAG,OAAA;AAAA,UACH,cAAA,EAAgB,MAAA;AAAA,UAChB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB;AAAA,SACF;AAGA,QAAA,IAAI,gBAAgB,MAAA,EAAW;AAC7B,UAAA,OAAO,YAAA,CAAa,WAAA,EAAa,UAAA,EAAY,eAAA,EAAiB,MAAM,CAAA;AAAA,QACtE;AAGA,QAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,UAAU,CAAA;AACxC,QAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,UAAA,OAAO,QAAQ,MAAA,CAAO,IAAI,MAAM,CAAA,2BAAA,EAA8B,UAAU,cAAc,CAAC,CAAA;AAAA,QACzF;AAGA,QAAA,OAAO,aAAa,IAAA,CAAK,CAAC,CAAA,EAAG,UAAA,EAAY,iBAAiB,MAAM,CAAA;AAAA,MAClE;AAAA,MAEA,KAAK,cAAc,kBAAA,EAAoB;AAErC,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,QAAA,CAAS,cAAA,EAAgB,CAAA;AAAA,MAClD;AAAA;AACF,EACF,CAAC,CAAA;AAGD,EAAA,OAAA,CAAQ,IAAA,CAAK,SAAA,CAAU,WAAA,CAAY,CAAC,KAAA,KAAU;AAC5C,IAAA,QAAA,CAAS,cAAc,KAAK,CAAA;AAC5B,IAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,qDAAA,EAAwD,KAAK,CAAA,CAAE,CAAA;AAAA,EAC/E,CAAC,CAAA;AAED,EAAA,MAAA,EAAQ,MAAM,uCAAuC,CAAA;AACvD;AAKA,eAAe,YAAA,CACb,KAAA,EACA,UAAA,EACA,OAAA,EACA,MAAA,EACwB;AACxB,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,IAAA,CAAK,YAAY,KAAA,EAAO;AAAA,MACrD,MAAM,aAAA,CAAc,cAAA;AAAA,MACpB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,MACpB,UAAA;AAAA,MACA;AAAA,KACD,CAAA;AACD,IAAA,OAAO,QAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,mDAAA,EAAsD,KAAK,CAAA,CAAA,CAAA,EAAK,KAAK,CAAA;AACnF,IAAA,MAAM,KAAA;AAAA,EACR;AACF;AA2BA,eAAsB,kBAAA,CAGpB,GAAA,EAAQ,WAAA,EAAgB,MAAA,EAAqD;AAC7E,EAAA,MAAM,SAAS,MAAA,EAAQ,MAAA;AAGvB,EAAA,MAAM,OAAA,CAAQ,QAAQ,WAAA,CAAY;AAAA,IAChC,MAAM,aAAA,CAAc,oBAAA;AAAA,IACpB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,IACpB,UAAA,EAAY;AAAA,GACb,CAAA;AAED,EAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,sCAAA,EAAyC,GAAG,CAAA,mBAAA,CAAqB,CAAA;AAG/E,EAAA,MAAM,eAAA,GAAkB,CAAC,OAAA,EAAc,MAAA,KAA0C;AAC/E,IAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,+CAAA,EAAkD,GAAG,CAAA,EAAA,CAAA,EAAM,SAAS,MAAM,CAAA;AAGxF,IAAA,IAAI,QAAQ,IAAA,KAAS,aAAA,CAAc,cAAA,IAAkB,OAAA,CAAQ,eAAe,GAAA,EAAK;AAC/E,MAAA;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,YAAY;AAClB,MAAA,MAAM,UAAwB,OAAA,CAAQ,OAAA;AAEtC,MAAA,IAAI;AAEF,QAAA,MAAM,MAAA,GAAS,QAAQ,IAAA,IAAQ,IAAA,GAAO,cAAc,GAAA,CAAI,WAAA,EAAa,QAAQ,IAAI,CAAA;AAEjF,QAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,UAAA,MAAM,IAAI,MAAM,CAAA,kBAAA,EAAqB,OAAA,CAAQ,MAAM,IAAA,CAAK,GAAG,CAAC,CAAA,CAAE,CAAA;AAAA,QAChE;AAGA,QAAA,MAAM,OAAA,GAA8B;AAAA,UAClC,MAAA;AAAA,UACA,gBAAgB,OAAA,CAAQ,cAAA;AAAA,UACxB,WAAW,OAAA,CAAQ,SAAA;AAAA,UACnB,WAAW,OAAA,CAAQ,SAAA;AAAA,UACnB,UAAU;AAAC,SACb;AAGA,QAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,WAAW,CAAA,CAAE,GAAG,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAC,CAAA;AACvF,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,2CAAA,EAA8C,GAAG,CAAA,EAAA,CAAA,EAAM,KAAK,CAAA;AAC1E,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF,CAAA,GAAG;AAAA,EACL,CAAA;AAEA,EAAA,OAAA,CAAQ,OAAA,CAAQ,SAAA,CAAU,WAAA,CAAY,eAAe,CAAA;AAGrD,EAAA,OAAO,MAAM;AACX,IAAA,OAAA,CAAQ,OAAA,CAAQ,SAAA,CAAU,cAAA,CAAe,eAAe,CAAA;AACxD,IAAA,OAAA,CAAQ,QACL,WAAA,CAAY;AAAA,MACX,MAAM,aAAA,CAAc,sBAAA;AAAA,MACpB,UAAA,EAAY;AAAA,KACb,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,IAEb,CAAC,CAAA;AACH,IAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,wCAAA,EAA2C,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,EACjE,CAAA;AACF;AAoCO,SAAS,qBAAA,CACd,KACA,OAAA,EAIoB;AACpB,EAAA,MAAM,cAAc,OAAA,EAAS,WAAA;AAC7B,EAAA,MAAM,SAAS,OAAA,EAAS,MAAA;AAExB,EAAA,OAAO,WAAA,CAAY,GAAA,EAAK,WAAA,EAAa,MAAM,CAAA;AAC7C;AAMA,SAAS,WAAA,CACP,UAAA,EACA,WAAA,EACA,MAAA,EACA,IAAA,EACoB;AACpB,EAAA,MAAM,WAAW,MAAM;AAAA,EAAC,CAAA,CAAA;AAExB,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,OAAA,EAAS;AAAA,IAC/B,MAAM,KAAA,CAAM,OAAA,EAAS,QAAA,EAAU,IAAA,EAAM;AACnC,MAAA,MAAM,OAAA,GAAwB;AAAA,QAC5B,IAAA;AAAA,QACA;AAAA,OACF;AAGA,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,YAAA;AAGjC,QAAA,MAAM,SAAA,GAAY,CAAA,EAAG,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,SAAA,CAAU,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAE9E,QAAA,MAAM,eAAA,GAAgC;AAAA,UACpC,GAAG,OAAA;AAAA;AAAA,UAEH,cAAA,EAAgB,MAAA;AAAA,UAChB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB;AAAA,SACF;AAGA,QAAA,IAAI,WAAA,GAAc,WAAA;AAClB,QAAA,IAAI,CAAC,WAAA,EAAa;AAEhB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,iDAAiD,UAAU,CAAA,+DAAA;AAAA,WAE7D;AAAA,QACF;AAEA,QAAA,MAAA,EAAQ,KAAA;AAAA,UACN,CAAA,0DAAA,EAA6D,UAAU,CAAA,SAAA,EAAY,WAAW,CAAA;AAAA,SAChG;AACA,QAAA,OAAO,MAAM,YAAA,CAAa,WAAA,EAAa,UAAA,EAAY,iBAAiB,MAAM,CAAA;AAAA,MAC5E;AAGA,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,OAAA,CAAQ,WAAA,CAAY;AAAA,UACjD,MAAM,aAAA,CAAc,gBAAA;AAAA,UACpB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB,UAAA;AAAA,UACA,WAAA;AAAA,UACA;AAAA,SACD,CAAA;AACD,QAAA,OAAO,QAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,MAAA,EAAQ,MAAA,EAAQ,KAAA,CAAM,CAAA,yCAAA,EAA4C,UAAU,MAAM,KAAK,CAAA;AACvF,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF,CAAA;AAAA,IAEA,GAAA,CAAI,MAAA,EAAQ,YAAA,EAAc,QAAA,EAAU;AAElC,MAAA,IAAI,OAAO,iBAAiB,QAAA,EAAU;AACpC,QAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,YAAA,EAAc,QAAQ,CAAA;AAAA,MACnD;AAGA,MAAA,OAAO,WAAA;AAAA,QACL,UAAA;AAAA,QACA,WAAA;AAAA,QACA,MAAA;AAAA,QACA,IAAA,IAAQ,OAAO,CAAC,YAAY,IAAI,IAAA,CAAK,MAAA,CAAO,CAAC,YAAY,CAAC;AAAA,OAC5D;AAAA,IACF;AAAA,GACD,CAAA;AAED,EAAA,OAAO,KAAA;AACT;AAcA,eAAsB,gBAAA,GAA8C;AAClE,EAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,OAAA,CAAQ,WAAA,CAAY;AAAA,IACjD,MAAM,aAAA,CAAc;AAAA,GACrB,CAAA;AACD,EAAA,OAAO,QAAA;AACT;AAKA,SAAS,GAAA,CAAI,KAAU,IAAA,EAAqB;AAC1C,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA,CAAK,OAAO,CAAC,GAAA,EAAK,QAAQ,GAAA,GAAM,GAAG,GAAG,GAAG,CAAA;AAClD;AA+BO,SAAS,qBAAA,CACd,IAAA,EACA,IAAA,EACA,MAAA,EAOA;AACA,EAAA,MAAM,GAAA,GAAM,IAAA;AAEZ,EAAA,OAAO;AAAA;AAAA,IAEL,UAAU,IAAA,KAAqC;AAC7C,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAG,IAAI,CAAA;AAC5B,MAAA,OAAO,MAAM,kBAAA,CAAmB,GAAA,EAAK,OAAA,EAAS,MAAM,CAAA;AAAA,IACtD,CAAA;AAAA;AAAA,IAEA,CAAC,OAAA,KACC,qBAAA,CAAgC,GAAA,EAAK,OAAO;AAAA,GAChD;AACF;AAoBO,SAAS,eAAkB,OAAA,EAAyC;AACzE,EAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAyB;AAAA,IACzC,GAAA,CAAI,GAAG,IAAA,EAAM;AACX,MAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,QAAA,OAAO,MAAA;AAAA,MACT;AACA,MAAA,OAAO,UAAU,IAAA,KAAgB;AAC/B,QAAA,MAAM,WAAW,MAAM,OAAA;AACvB,QAAA,MAAM,MAAA,GAAU,SAAiB,IAAI,CAAA;AACrC,QAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,UAAA,OAAO,MAAA,CAAO,KAAA,CAAM,QAAA,EAAU,IAAI,CAAA;AAAA,QACpC;AACA,QAAA,OAAO,MAAA;AAAA,MACT,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACH","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.
|
|
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",
|