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