@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.
Files changed (3) hide show
  1. package/README.md +100 -185
  2. package/lib/index.d.ts +2 -4
  3. 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) 的二次开发版本,添加了 `sender` 参数支持。
3
+ 基于 [@webext-core/proxy-service](https://github.com/aklinker1/webext-core/tree/main/packages/proxy-service) 的二次开发版本,支持 ServiceCallContext 上下文对象,提供更丰富的调用上下文信息。
4
4
 
5
- 类型安全的浏览器扩展消息传递 API 封装,允许你在任何地方调用函数,但在后台脚本中执行。支持所有浏览器(Chrome、Firefox、Safari 等)。
5
+ 类型安全的浏览器扩展消息传递 API 封装,允许你在任何地方调用函数,但在后台脚本中执行。支持所有主流浏览器(Chrome、Firefox、Safari 等)。
6
6
 
7
- ## 主要修改
7
+ ## 主要特性
8
8
 
9
- 相比原版 `@webext-core/proxy-service`,本包的主要修改:
10
-
11
- **`registerService` 函数现在会将 `sender` 参数传递给服务方法:**
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 { Runtime } from 'webextension-polyfill';
25
+ import { defineProxyService, type ServiceCallContext } from '@northsea4/proxy-service'
38
26
 
39
- export class TabService {
40
- // sender 参数会自动注入为最后一个参数
41
- async getCurrentTabInfo(sender: Runtime.MessageSender) {
27
+ class TabService {
28
+ async getCurrentTabInfo(context?: ServiceCallContext) {
42
29
  return {
43
- tabId: sender.tab?.id,
44
- url: sender.tab?.url,
45
- title: sender.tab?.title,
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, sender: Runtime.MessageSender) {
50
- console.log('closeTab called from:', sender.tab?.url);
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 TAB_SERVICE_KEY = 'tab-service' as ProxyServiceKey<TabService>;
44
+ export const [registerTabService, getTabService] = defineProxyService(
45
+ 'tab-service',
46
+ () => new TabService()
47
+ )
64
48
  ```
65
49
 
66
- ### 3. 在后台脚本中注册服务
50
+ ### 2. 注册服务(Background)
67
51
 
68
52
  ```ts
69
- // background.ts
70
- import { registerService } from '@northsea4/proxy-service';
71
- import { TabService } from './services/tab-service';
72
- import { TAB_SERVICE_KEY } from './services/keys';
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
- ### 4. 在其他上下文中使用服务
59
+ ### 3. 使用服务(任何地方)
80
60
 
81
61
  ```ts
82
62
  // content-script.ts 或 popup.ts
83
- import { createProxyService } from '@northsea4/proxy-service';
84
- import { TAB_SERVICE_KEY } from './services/keys';
63
+ import { getTabService } from '@/services/tab-service'
85
64
 
86
- const tabService = createProxyService(TAB_SERVICE_KEY);
65
+ const tabService = getTabService()
87
66
 
88
- // 调用服务方法(不需要手动传递 sender,它会自动注入)
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
- export class MathService {
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
- ```ts
114
- export function createMathService() {
115
- return {
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
- ```ts
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(sender: Runtime.MessageSender) {
95
+ async getCallerInfo(context?: ServiceCallContext) {
181
96
  return {
182
- fromTab: sender.tab?.id,
183
- fromUrl: sender.tab?.url,
184
- fromFrame: sender.frameId,
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
- #### 2. 根据调用者执行不同逻辑
107
+ ### 2. 权限检查
191
108
 
192
109
  ```ts
193
110
  class PermissionService {
194
- async checkPermission(action: string, sender: Runtime.MessageSender) {
195
- const url = sender.tab?.url;
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
- #### 3. 操作调用者的标签页
121
+ ### 3. 日志追踪
205
122
 
206
123
  ```ts
207
- class TabControlService {
208
- async closeCallerTab(sender: Runtime.MessageSender) {
209
- if (sender.tab?.id) {
210
- await browser.tabs.remove(sender.tab.id);
211
- }
212
- }
213
-
214
- async reloadCallerTab(sender: Runtime.MessageSender) {
215
- if (sender.tab?.id) {
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
- 在定义服务类型时,记得在每个方法的最后添加 `sender` 参数:
139
+ 服务方法签名应为:
225
140
 
226
141
  ```ts
227
- import type { Runtime } from 'webextension-polyfill';
142
+ import type { ServiceCallContext } from '@northsea4/proxy-service'
228
143
 
229
- // 正确的类型定义
230
144
  interface MyService {
231
- method1(arg1: string, sender: Runtime.MessageSender): Promise<void>;
232
- method2(arg1: number, arg2: string, sender: Runtime.MessageSender): Promise<number>;
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(sender: Runtime.MessageSender) {
255
- // 不需要 await dbPromise,可以直接使用
256
- return await db.get('store', 'key');
257
- // 而不是: await (await dbPromise).get('store', 'key');
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
- export const [registerMathService, getMathService] = defineProxyService(
270
- 'math-service',
271
- () => new MathService(),
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
- registerMathService();
189
+ // background/index.ts
190
+ registerMyService()
276
191
 
277
192
  // content-script.ts
278
- const mathService = getMathService();
279
- await mathService.add(1, 2);
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
- interface ProxyServiceConstraint<_> {
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
- interface ProxyServiceConfig extends ExtensionMessagingConfig {
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.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",