@northsea4/proxy-service 1.0.0 → 1.0.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 +107 -180
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
# @northsea4/proxy-service
|
|
2
2
|
|
|
3
|
-
基于 [@webext-core/proxy-service](https://github.com/aklinker1/webext-core/tree/main/packages/proxy-service) 的二次开发版本,添加了 `sender` 参数支持。
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
基于 [@webext-core/proxy-service](https://github.com/aklinker1/webext-core/tree/main/packages/proxy-service) 的二次开发版本,支持 ServiceCallContext 上下文对象,提供更丰富的调用上下文信息。
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
类型安全的浏览器扩展消息传递 API 封装,允许你在任何地方调用函数,但在后台脚本中执行。支持所有主流浏览器(Chrome、Firefox、Safari 等)。
|
|
8
7
|
|
|
9
|
-
相比原版 `@webext-core/proxy-service`,本包的主要修改:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
## 主要特性
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// 原版行为
|
|
17
|
-
registerService(key, service);
|
|
18
|
-
// 调用 service.method(arg1, arg2)
|
|
19
|
-
|
|
20
|
-
// 修改后的行为
|
|
21
|
-
registerService(key, service);
|
|
22
|
-
// 调用 service.method(arg1, arg2, sender)
|
|
23
|
-
```
|
|
11
|
+
- 支持 ServiceCallContext:服务方法可获取完整的调用上下文(sender、requestId、timestamp、metadata 等)
|
|
12
|
+
- 类型安全的服务注册与调用
|
|
13
|
+
- 自动注入 context,无需手动传递
|
|
24
14
|
|
|
25
15
|
## 安装
|
|
26
16
|
|
|
@@ -30,270 +20,206 @@ pnpm add @northsea4/proxy-service
|
|
|
30
20
|
|
|
31
21
|
## 快速开始
|
|
32
22
|
|
|
23
|
+
|
|
33
24
|
### 1. 定义服务
|
|
34
25
|
|
|
35
26
|
```ts
|
|
36
27
|
// services/tab-service.ts
|
|
37
|
-
import type
|
|
28
|
+
import { defineProxyService, type ServiceCallContext } from '@northsea4/proxy-service'
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
async getCurrentTabInfo(sender: Runtime.MessageSender) {
|
|
30
|
+
class TabService {
|
|
31
|
+
async getCurrentTabInfo(context?: ServiceCallContext) {
|
|
42
32
|
return {
|
|
43
|
-
tabId: sender
|
|
44
|
-
url: sender
|
|
45
|
-
title: sender
|
|
46
|
-
|
|
33
|
+
tabId: context?.sender?.tab?.id,
|
|
34
|
+
url: context?.sender?.tab?.url,
|
|
35
|
+
title: context?.sender?.tab?.title,
|
|
36
|
+
requestId: context?.requestId,
|
|
37
|
+
timestamp: context?.timestamp,
|
|
38
|
+
}
|
|
47
39
|
}
|
|
48
40
|
|
|
49
|
-
async closeTab(tabId: number,
|
|
50
|
-
console.log('closeTab called from:', sender
|
|
51
|
-
await browser.tabs.remove(tabId)
|
|
41
|
+
async closeTab(tabId: number, context?: ServiceCallContext) {
|
|
42
|
+
console.log('closeTab called from:', context?.sender?.tab?.url)
|
|
43
|
+
await browser.tabs.remove(tabId)
|
|
52
44
|
}
|
|
53
45
|
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 2. 创建类型安全的服务键
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
export const TAB_SERVICE_KEY = 'tab-service' as ProxyServiceKey<TabService>;
|
|
47
|
+
export const [registerTabService, getTabService] = defineProxyService(
|
|
48
|
+
'tab-service',
|
|
49
|
+
() => new TabService()
|
|
50
|
+
)
|
|
64
51
|
```
|
|
65
52
|
|
|
66
|
-
|
|
53
|
+
|
|
54
|
+
### 2. 注册服务(Background)
|
|
67
55
|
|
|
68
56
|
```ts
|
|
69
|
-
// background.ts
|
|
70
|
-
import {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// 注册服务
|
|
75
|
-
const tabService = new TabService();
|
|
76
|
-
registerService(TAB_SERVICE_KEY, tabService);
|
|
57
|
+
// background/index.ts
|
|
58
|
+
import { registerTabService } from '@/services/tab-service'
|
|
59
|
+
|
|
60
|
+
registerTabService()
|
|
77
61
|
```
|
|
78
62
|
|
|
79
|
-
###
|
|
63
|
+
### 3. 使用服务(任何地方)
|
|
80
64
|
|
|
81
65
|
```ts
|
|
82
66
|
// content-script.ts 或 popup.ts
|
|
83
|
-
import {
|
|
84
|
-
import { TAB_SERVICE_KEY } from './services/keys';
|
|
67
|
+
import { getTabService } from '@/services/tab-service'
|
|
85
68
|
|
|
86
|
-
const tabService =
|
|
69
|
+
const tabService = getTabService()
|
|
87
70
|
|
|
88
|
-
//
|
|
89
|
-
const tabInfo = await tabService.getCurrentTabInfo()
|
|
90
|
-
console.log('Current tab:', tabInfo)
|
|
71
|
+
// context 会自动注入
|
|
72
|
+
const tabInfo = await tabService.getCurrentTabInfo()
|
|
73
|
+
console.log('Current tab:', tabInfo)
|
|
91
74
|
|
|
92
|
-
|
|
93
|
-
await tabService.closeTab(tabInfo.tabId);
|
|
75
|
+
await tabService.closeTab(tabInfo.tabId)
|
|
94
76
|
```
|
|
95
77
|
|
|
96
|
-
## 服务定义方式
|
|
97
78
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
### 类
|
|
79
|
+
## ServiceCallContext 类型
|
|
101
80
|
|
|
102
81
|
```ts
|
|
103
|
-
|
|
104
|
-
add(x: number, y: number, sender: Runtime.MessageSender): number {
|
|
105
|
-
console.log('add called from:', sender.tab?.url);
|
|
106
|
-
return x + y;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### 对象
|
|
82
|
+
import type { ServiceCallContext } from '@northsea4/proxy-service'
|
|
112
83
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return x + y;
|
|
119
|
-
},
|
|
120
|
-
};
|
|
84
|
+
interface ServiceCallContext {
|
|
85
|
+
sender: Browser.Runtime.MessageSender
|
|
86
|
+
timestamp?: number
|
|
87
|
+
requestId?: string
|
|
88
|
+
metadata?: Record<string, any>
|
|
121
89
|
}
|
|
122
90
|
```
|
|
123
91
|
|
|
124
|
-
|
|
92
|
+
服务方法的最后一个参数应为 `context?: ServiceCallContext`,并通过 `context?.sender` 获取调用方信息。
|
|
125
93
|
|
|
126
|
-
```ts
|
|
127
|
-
export type SayHello = (name: string, sender: Runtime.MessageSender) => Promise<string>;
|
|
128
94
|
|
|
129
|
-
|
|
130
|
-
console.log('sayHello called from:', sender.tab?.url);
|
|
131
|
-
return `Hello, ${name}!`;
|
|
132
|
-
};
|
|
133
|
-
```
|
|
95
|
+
## 常见场景示例
|
|
134
96
|
|
|
135
|
-
###
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
export function createApi() {
|
|
139
|
-
return {
|
|
140
|
-
math: {
|
|
141
|
-
add(x: number, y: number, sender: Runtime.MessageSender): number {
|
|
142
|
-
return x + y;
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
string: {
|
|
146
|
-
uppercase(str: string, sender: Runtime.MessageSender): string {
|
|
147
|
-
return str.toUpperCase();
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 使用
|
|
154
|
-
registerService('api', createApi());
|
|
155
|
-
const api = createProxyService('api');
|
|
156
|
-
await api.math.add(1, 2);
|
|
157
|
-
await api.string.uppercase('hello');
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## Sender 参数详解
|
|
161
|
-
|
|
162
|
-
`sender` 参数是一个 `Runtime.MessageSender` 对象,包含以下信息:
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
interface MessageSender {
|
|
166
|
-
tab?: Tab; // 发送消息的标签页信息
|
|
167
|
-
frameId?: number; // 发送消息的框架 ID
|
|
168
|
-
id?: string; // 发送消息的扩展 ID
|
|
169
|
-
url?: string; // 发送消息的页面 URL
|
|
170
|
-
tlsChannelId?: string;
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### 使用场景示例
|
|
175
|
-
|
|
176
|
-
#### 1. 获取调用者的标签页信息
|
|
97
|
+
### 1. 获取调用者信息
|
|
177
98
|
|
|
178
99
|
```ts
|
|
179
100
|
class ContextService {
|
|
180
|
-
async getCallerInfo(
|
|
101
|
+
async getCallerInfo(context?: ServiceCallContext) {
|
|
181
102
|
return {
|
|
182
|
-
fromTab: sender
|
|
183
|
-
fromUrl: sender
|
|
184
|
-
fromFrame: sender
|
|
185
|
-
|
|
103
|
+
fromTab: context?.sender?.tab?.id,
|
|
104
|
+
fromUrl: context?.sender?.tab?.url,
|
|
105
|
+
fromFrame: context?.sender?.frameId,
|
|
106
|
+
requestId: context?.requestId,
|
|
107
|
+
timestamp: context?.timestamp,
|
|
108
|
+
}
|
|
186
109
|
}
|
|
187
110
|
}
|
|
188
111
|
```
|
|
189
112
|
|
|
190
|
-
|
|
113
|
+
### 2. 权限检查
|
|
191
114
|
|
|
192
115
|
```ts
|
|
193
116
|
class PermissionService {
|
|
194
|
-
async checkPermission(action: string,
|
|
195
|
-
const url = sender
|
|
117
|
+
async checkPermission(action: string, context?: ServiceCallContext) {
|
|
118
|
+
const url = context?.sender?.tab?.url
|
|
196
119
|
if (url?.startsWith('https://trusted-domain.com')) {
|
|
197
|
-
return true
|
|
120
|
+
return true
|
|
198
121
|
}
|
|
199
|
-
return false
|
|
122
|
+
return false
|
|
200
123
|
}
|
|
201
124
|
}
|
|
202
125
|
```
|
|
203
126
|
|
|
204
|
-
|
|
127
|
+
### 3. 日志追踪
|
|
205
128
|
|
|
206
129
|
```ts
|
|
207
|
-
class
|
|
208
|
-
async
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await browser.tabs.reload(sender.tab.id);
|
|
217
|
-
}
|
|
130
|
+
class LoggingService {
|
|
131
|
+
async logAction(action: string, data: any, context?: ServiceCallContext) {
|
|
132
|
+
console.log({
|
|
133
|
+
requestId: context?.requestId,
|
|
134
|
+
timestamp: context?.timestamp,
|
|
135
|
+
caller: context?.sender?.tab?.title,
|
|
136
|
+
action,
|
|
137
|
+
data
|
|
138
|
+
})
|
|
218
139
|
}
|
|
219
140
|
}
|
|
220
141
|
```
|
|
221
142
|
|
|
143
|
+
|
|
222
144
|
## TypeScript 类型支持
|
|
223
145
|
|
|
224
|
-
|
|
146
|
+
服务方法签名应为:
|
|
225
147
|
|
|
226
148
|
```ts
|
|
227
|
-
import type {
|
|
149
|
+
import type { ServiceCallContext } from '@northsea4/proxy-service'
|
|
228
150
|
|
|
229
|
-
// 正确的类型定义
|
|
230
151
|
interface MyService {
|
|
231
|
-
method1(arg1: string,
|
|
232
|
-
method2(arg1: number, arg2: string,
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// 错误的类型定义(缺少 sender 参数)
|
|
236
|
-
interface MyServiceWrong {
|
|
237
|
-
method1(arg1: string): Promise<void>; // ❌ 运行时会出错
|
|
152
|
+
method1(arg1: string, context?: ServiceCallContext): Promise<void>
|
|
153
|
+
method2(arg1: number, arg2: string, context?: ServiceCallContext): Promise<number>
|
|
238
154
|
}
|
|
239
155
|
```
|
|
240
156
|
|
|
241
157
|
## 高级功能
|
|
242
158
|
|
|
159
|
+
|
|
243
160
|
### flattenPromise
|
|
244
161
|
|
|
245
162
|
简化异步依赖的处理:
|
|
246
163
|
|
|
247
164
|
```ts
|
|
248
|
-
import { flattenPromise } from '@northsea4/proxy-service'
|
|
165
|
+
import { flattenPromise } from '@northsea4/proxy-service'
|
|
249
166
|
|
|
250
167
|
function createService(dbPromise: Promise<IDBDatabase>) {
|
|
251
|
-
const db = flattenPromise(dbPromise)
|
|
168
|
+
const db = flattenPromise(dbPromise)
|
|
252
169
|
|
|
253
170
|
return {
|
|
254
|
-
async getData(
|
|
255
|
-
|
|
256
|
-
return await db.get('store', 'key');
|
|
257
|
-
// 而不是: await (await dbPromise).get('store', 'key');
|
|
171
|
+
async getData(context?: ServiceCallContext) {
|
|
172
|
+
return await db.get('store', 'key')
|
|
258
173
|
},
|
|
259
|
-
}
|
|
174
|
+
}
|
|
260
175
|
}
|
|
261
176
|
```
|
|
262
177
|
|
|
178
|
+
|
|
263
179
|
### defineProxyService
|
|
264
180
|
|
|
265
|
-
|
|
181
|
+
简化服务定义和类型推导:
|
|
266
182
|
|
|
267
183
|
```ts
|
|
268
|
-
// service.ts
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
184
|
+
// services/my-service.ts
|
|
185
|
+
import { defineProxyService, type ServiceCallContext } from '@northsea4/proxy-service'
|
|
186
|
+
|
|
187
|
+
class MyService {
|
|
188
|
+
async add(x: number, y: number, context?: ServiceCallContext) {
|
|
189
|
+
return x + y
|
|
190
|
+
}
|
|
191
|
+
}
|
|
273
192
|
|
|
274
|
-
|
|
275
|
-
|
|
193
|
+
export const [registerMyService, getMyService] = defineProxyService(
|
|
194
|
+
'my-service',
|
|
195
|
+
() => new MyService()
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
// background/index.ts
|
|
199
|
+
registerMyService()
|
|
276
200
|
|
|
277
201
|
// content-script.ts
|
|
278
|
-
const
|
|
279
|
-
await
|
|
202
|
+
const myService = getMyService()
|
|
203
|
+
await myService.add(1, 2)
|
|
280
204
|
```
|
|
281
205
|
|
|
206
|
+
|
|
282
207
|
## 配置选项
|
|
283
208
|
|
|
284
|
-
支持 `@webext-core/messaging`
|
|
209
|
+
支持 `@webext-core/messaging` 的所有配置项:
|
|
285
210
|
|
|
286
211
|
```ts
|
|
287
|
-
import { registerService, createProxyService } from '@northsea4/proxy-service'
|
|
212
|
+
import { registerService, createProxyService } from '@northsea4/proxy-service'
|
|
288
213
|
|
|
289
214
|
const config = {
|
|
290
|
-
logger: console,
|
|
291
|
-
}
|
|
215
|
+
logger: console, // 自定义日志记录器
|
|
216
|
+
}
|
|
292
217
|
|
|
293
|
-
registerService('my-service', myService, config)
|
|
294
|
-
const proxy = createProxyService('my-service', config)
|
|
218
|
+
registerService('my-service', myService, config)
|
|
219
|
+
const proxy = createProxyService('my-service', config)
|
|
295
220
|
```
|
|
296
221
|
|
|
222
|
+
|
|
297
223
|
## 开发
|
|
298
224
|
|
|
299
225
|
```bash
|
|
@@ -308,6 +234,7 @@ pnpm dev
|
|
|
308
234
|
|
|
309
235
|
MIT
|
|
310
236
|
|
|
237
|
+
|
|
311
238
|
## 致谢
|
|
312
239
|
|
|
313
240
|
本项目基于 [@webext-core/proxy-service](https://github.com/aklinker1/webext-core/tree/main/packages/proxy-service) 开发,感谢原作者的优秀工作。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@northsea4/proxy-service",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A type-safe wrapper around the web extension messaging APIs that lets you call a function from anywhere, but execute it in the background. Forked from @webext-core/proxy-service with sender parameter support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|