crx-rpc 1.0.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -157,21 +157,86 @@ async function calculate() {
157
157
 
158
158
  ## Architecture
159
159
 
160
- ### Hybrid Mode (Both Bridge + Direct)
161
- ```
162
- ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
163
- │ Web Page │ │ Content Script │ │ Background │
164
- │ │ (Bridge+Client) │ │ Script │
165
- ├─────────────────┤ ├─────────────────┤ ├─────────────────┤
166
- │ WebRPCClient │ │ ContentRPC │ │ BackgroundRPC │
167
- │ │ │ + │ │ │
168
- │ mathService ────┼───▶│ContentRPCClient │◄──▶│ MathService │
169
- │ .add(1,2) │◄───│ │ UserService │
170
- │ │ │ userService │ │ │
171
- │ │ │ .getUser() ─────┼───▶│ │
172
- └─────────────────┘ └─────────────────┘◄───└─────────────────┘
160
+ ### Complete Communication Topology
161
+
162
+ ```mermaid
163
+ graph TB
164
+ subgraph WebPage["Web Page Context"]
165
+ WC[WebRPCClient]
166
+ WO[WebObservable]
167
+ end
168
+
169
+ subgraph ContentScript["Content Script Context"]
170
+ CR[ContentRPC<br/>Bridge Mode]
171
+ CC[ContentRPCClient<br/>Direct Mode]
172
+ CO[ContentObservable]
173
+ end
174
+
175
+ subgraph Background["Background Script Context"]
176
+ BR[BackgroundRPC]
177
+ MS[MathService]
178
+ US[UserService]
179
+ RS[RemoteSubject]
180
+ RSM[RemoteSubjectManager]
181
+ end
182
+
183
+ subgraph ExtPage["Extension Page Context<br/>(Popup/Options/Sidepanel)"]
184
+ EC[ExtPageRPCClient]
185
+ EO[ExtPageObservable]
186
+ end
187
+
188
+ subgraph TabContext["Tab-specific Access"]
189
+ TC[TabRPCClient]
190
+ end
191
+
192
+ %% RPC Calls
193
+ WC -->|"CustomEvent<br/>.add(1,2)"| CR
194
+ CR -->|"chrome.runtime<br/>Forward"| BR
195
+ CC -->|"chrome.runtime<br/>.multiply(2,3)"| BR
196
+ EC -->|"chrome.runtime<br/>.divide(10,2)"| BR
197
+ TC -->|"chrome.tabs<br/>Access Content Service"| CC
198
+
199
+ BR -->|Response| CR
200
+ CR -->|CustomEvent| WC
201
+ BR -->|Response| CC
202
+ BR -->|Response| EC
203
+
204
+ BR -.->|Manages| MS
205
+ BR -.->|Manages| US
206
+
207
+ %% Observable Streams
208
+ WO -.->|Subscribe| CR
209
+ CR -.->|Forward| RSM
210
+ CO -.->|Subscribe| RSM
211
+ EO -.->|Subscribe| RSM
212
+ RSM -.->|Broadcast| RS
213
+ RS -.->|Updates| CR
214
+ CR -.->|Updates| WO
215
+ RS -.->|Updates| CO
216
+ RS -.->|Updates| EO
217
+
218
+ style WC fill:#e1f5ff
219
+ style CC fill:#e1f5ff
220
+ style EC fill:#e1f5ff
221
+ style TC fill:#e1f5ff
222
+ style BR fill:#fff4e6
223
+ style MS fill:#f0f0f0
224
+ style US fill:#f0f0f0
225
+ style RS fill:#ffe6f0
226
+ style RSM fill:#ffe6f0
227
+ style CR fill:#e8f5e9
173
228
  ```
174
229
 
230
+ ### Communication Paths
231
+
232
+ | Path | Method | Description |
233
+ |------|--------|-------------|
234
+ | **Web Page → Background** | CustomEvent + chrome.runtime | Through ContentRPC bridge |
235
+ | **Content Script → Background** | chrome.runtime | Direct communication |
236
+ | **Extension Page → Background** | chrome.runtime | Direct communication |
237
+ | **Extension Page → Content Script** | chrome.tabs + TabRPCClient | Tab-specific access |
238
+ | **Background → All Contexts** | RemoteSubject broadcast | Real-time data streaming |
239
+
175
240
  ### Key Components
176
241
 
177
242
  - **WebRPCClient**: Client for web pages using window events
@@ -201,31 +266,12 @@ const rpc = new BackgroundRPC(true); // Enable logging
201
266
 
202
267
  ### Log Output
203
268
 
204
- When logging is enabled, the following information is logged with **color-coded formatting**:
205
-
206
- - **Function Calls**: `[RPC] Call: Service.Method [ID]` with colored components
207
- - **Success Responses**: `[RPC] Success: Service.Method [ID]` with green success indicator
208
- - **Error Responses**: `[RPC] Error: Service.Method [ID]` with red error indicator
209
- - **Unknown Services/Methods**: `[RPC] Unknown service/method: Service.Method [ID]` with orange warnings
210
-
211
- ### Color Scheme
212
-
213
- - 🟣 **Purple**: `[RPC]` prefix and request IDs
214
- - 🟢 **Green**: Service names and success indicators
215
- - 🔴 **Red**: Method names and error indicators
216
- - 🟠 **Orange**: Warning messages
217
- - ⚫ **Gray**: Separators and structural elements
269
+ When logging is enabled, the following information is logged:
218
270
 
219
- ### Example Output
220
-
221
- ```
222
- [RPC] Call: MathService.add [rpc-123]
223
- [RPC] Success: MathService.add [rpc-123]
224
- [RPC] Error: MathService.divide [rpc-124]
225
- [RPC] Unknown service: InvalidService [rpc-125]
226
- ```
227
-
228
- *Note: Colors are visible in browser developer console, not in plain text.*
271
+ - **Function Calls**: Service name, method name, arguments, sender ID, and timestamp
272
+ - **Success Responses**: Service name, method name, result, and timestamp
273
+ - **Error Responses**: Service name, method name, error message, and timestamp
274
+ - **Unknown Services/Methods**: Warnings for invalid service or method calls
229
275
 
230
276
  ### Use Cases
231
277
 
@@ -361,6 +407,34 @@ const observable = new ContentObservable(
361
407
  // observable.dispose();
362
408
  ```
363
409
 
410
+ ### Subscribing from Extension Page
411
+
412
+ ```typescript
413
+ // popup.ts / options.ts
414
+ import { ExtPageObservable, createIdentifier } from 'crx-rpc';
415
+
416
+ interface ICounterObservable {
417
+ value: number;
418
+ }
419
+
420
+ const ICounterObservable = createIdentifier<ICounterObservable>('Counter');
421
+
422
+ // Extension page can subscribe to background observables
423
+ const observable = new ExtPageObservable(
424
+ ICounterObservable,
425
+ 'main',
426
+ (value) => {
427
+ console.log('Counter from extension page:', value.value);
428
+ document.getElementById('counter').textContent = value.value.toString();
429
+ }
430
+ );
431
+
432
+ // Cleanup when done
433
+ window.addEventListener('unload', () => {
434
+ observable.dispose();
435
+ });
436
+ ```
437
+
364
438
  ### Observable Communication Patterns
365
439
 
366
440
  The Observable system supports multiple communication patterns with centralized management:
@@ -413,6 +487,94 @@ if (!client.isDisposed()) {
413
487
  }
414
488
  ```
415
489
 
490
+ ### Extension Page Accessing Content Script Services
491
+
492
+ Extension pages can access content script services using `TabRPCClient` by specifying the target tab ID:
493
+
494
+ ```typescript
495
+ // popup.ts
496
+ import { TabRPCClient } from 'crx-rpc';
497
+ import { IContentService } from './services';
498
+
499
+ // Get current active tab
500
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
501
+
502
+ if (tab.id) {
503
+ // Create RPC client for specific tab
504
+ const tabClient = new TabRPCClient(tab.id);
505
+
506
+ // Access content script services in that tab
507
+ const contentService = tabClient.createWebRPCService(IContentService);
508
+
509
+ // Call content script methods
510
+ const result = await contentService.getDOMInfo();
511
+ console.log('DOM info from content script:', result);
512
+
513
+ // Cleanup when done
514
+ window.addEventListener('unload', () => {
515
+ tabClient.dispose();
516
+ });
517
+ }
518
+ ```
519
+
520
+ #### Use Cases for Extension Page → Content Script Communication:
521
+
522
+ 1. **DOM Inspection**: Popup queries content script for page information
523
+ 2. **User Actions**: Options page triggers content script actions on specific tabs
524
+ 3. **Multi-tab Management**: Sidepanel coordinates actions across multiple tabs
525
+ 4. **Live Preview**: Extension page gets real-time updates from content script
526
+
527
+ #### Complete Example: Popup with Tab-specific Services
528
+
529
+ ```typescript
530
+ // content.ts - Register services in content script
531
+ import { ContentRPCHost } from 'crx-rpc';
532
+ import { IPageService } from './services';
533
+
534
+ class PageService implements IPageService {
535
+ async getTitle(): Promise<string> {
536
+ return document.title;
537
+ }
538
+
539
+ async getSelection(): Promise<string> {
540
+ return window.getSelection()?.toString() || '';
541
+ }
542
+
543
+ async highlightText(text: string): Promise<void> {
544
+ // Highlight logic...
545
+ }
546
+ }
547
+
548
+ const contentHost = new ContentRPCHost();
549
+ contentHost.register(IPageService, new PageService());
550
+
551
+ // popup.ts - Access content script from popup
552
+ import { TabRPCClient, ExtPageRPCClient } from 'crx-rpc';
553
+ import { IPageService, IMathService } from './services';
554
+
555
+ // Access background services
556
+ const bgClient = new ExtPageRPCClient();
557
+ const mathService = bgClient.createWebRPCService(IMathService);
558
+
559
+ // Access content script services in active tab
560
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
561
+ if (tab.id) {
562
+ const tabClient = new TabRPCClient(tab.id);
563
+ const pageService = tabClient.createWebRPCService(IPageService);
564
+
565
+ // Get page info from content script
566
+ const title = await pageService.getTitle();
567
+ const selection = await pageService.getSelection();
568
+
569
+ // Process with background service
570
+ const result = await mathService.calculate(selection.length);
571
+
572
+ // Update popup UI
573
+ document.getElementById('title').textContent = title;
574
+ document.getElementById('result').textContent = result.toString();
575
+ }
576
+ ```
577
+
416
578
  ## Usage Scenarios
417
579
 
418
580
  ### Scenario 1: Web Page Only
package/README.zh-CN.md CHANGED
@@ -155,25 +155,118 @@ async function calculate() {
155
155
  }
156
156
  ```
157
157
 
158
+ ### 5. 使用客户端(扩展页面)
159
+
160
+ ```typescript
161
+ // popup.ts / options.ts / sidepanel.ts
162
+ import { ExtPageRPCClient } from 'crx-rpc';
163
+ import { IMathService } from './services/math';
164
+
165
+ async function calculate() {
166
+ // 创建扩展页面RPC客户端
167
+ const client = new ExtPageRPCClient();
168
+
169
+ // 创建类型安全的服务代理
170
+ const mathService = client.createWebRPCService(IMathService);
171
+
172
+ // 直接调用background服务
173
+ const sum = await mathService.add(1, 2);
174
+ const difference = await mathService.subtract(10, 5);
175
+ const product = await mathService.multiply(3, 4);
176
+ const quotient = await mathService.divide(15, 3);
177
+
178
+ console.log('结果:', { sum, difference, product, quotient });
179
+
180
+ // 页面关闭时自动清理
181
+ window.addEventListener('unload', () => {
182
+ client.dispose();
183
+ });
184
+ }
185
+ ```
186
+
158
187
  ## 架构
159
188
 
189
+ ### 完整通信拓扑图
190
+
191
+ ```mermaid
192
+ graph TB
193
+ subgraph WebPage["网页上下文"]
194
+ WC[WebRPCClient]
195
+ WO[WebObservable]
196
+ end
197
+
198
+ subgraph ContentScript["内容脚本上下文"]
199
+ CR[ContentRPC<br/>桥接模式]
200
+ CC[ContentRPCClient<br/>直接模式]
201
+ CO[ContentObservable]
202
+ end
203
+
204
+ subgraph Background["背景脚本上下文"]
205
+ BR[BackgroundRPC]
206
+ MS[MathService]
207
+ US[UserService]
208
+ RS[RemoteSubject]
209
+ RSM[RemoteSubjectManager]
210
+ end
211
+
212
+ subgraph ExtPage["扩展页面上下文<br/>(Popup/Options/Sidepanel)"]
213
+ EC[ExtPageRPCClient]
214
+ EO[ExtPageObservable]
215
+ end
216
+
217
+ subgraph TabContext["标签页特定访问"]
218
+ TC[TabRPCClient]
219
+ end
220
+
221
+ %% RPC 调用
222
+ WC -->|"CustomEvent<br/>.add(1,2)"| CR
223
+ CR -->|"chrome.runtime<br/>转发"| BR
224
+ CC -->|"chrome.runtime<br/>.multiply(2,3)"| BR
225
+ EC -->|"chrome.runtime<br/>.divide(10,2)"| BR
226
+ TC -->|"chrome.tabs<br/>访问内容服务"| CC
227
+
228
+ BR -->|响应| CR
229
+ CR -->|CustomEvent| WC
230
+ BR -->|响应| CC
231
+ BR -->|响应| EC
232
+
233
+ BR -.->|管理| MS
234
+ BR -.->|管理| US
235
+
236
+ %% Observable 数据流
237
+ WO -.->|订阅| CR
238
+ CR -.->|转发| RSM
239
+ CO -.->|订阅| RSM
240
+ EO -.->|订阅| RSM
241
+ RSM -.->|广播| RS
242
+ RS -.->|更新| CR
243
+ CR -.->|更新| WO
244
+ RS -.->|更新| CO
245
+ RS -.->|更新| EO
246
+
247
+ style WC fill:#e1f5ff
248
+ style CC fill:#e1f5ff
249
+ style EC fill:#e1f5ff
250
+ style TC fill:#e1f5ff
251
+ style BR fill:#fff4e6
252
+ style MS fill:#f0f0f0
253
+ style US fill:#f0f0f0
254
+ style RS fill:#ffe6f0
255
+ style RSM fill:#ffe6f0
256
+ style CR fill:#e8f5e9
160
257
  ```
161
- 网页 内容脚本 背景脚本
162
- ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
163
- │ WebRPCClient│──▶│ ContentRPC │──▶│ BackgroundRPC │
164
- │ │ │ (桥接器) │ │ │
165
- │ 代理 │ │ │ │ 服务 │
166
- 服务 │ │ MessageAdapter │ │ 注册表 │
167
- .add(1, 2) │ │ │ │ │
168
- └─────────────┘ └─────────────────┘ └─────────────────┘
169
- │ │ ▲
170
- │ CustomEvent │ chrome.runtime │
171
- │ │ Messages │
172
- └──────────────────┴──────────────────────┘
173
-
174
- ┌─────────────────┐
175
- │ContentRPCClient │
176
- │ (直接) │
258
+
259
+ ### 通信路径
260
+
261
+ | 路径 | 方式 | 描述 |
262
+ |------|------|------|
263
+ | **网页 背景脚本** | CustomEvent + chrome.runtime | 通过 ContentRPC 桥接 |
264
+ | **内容脚本 → 背景脚本** | chrome.runtime | 直接通信 |
265
+ | **扩展页面 → 背景脚本** | chrome.runtime | 直接通信 |
266
+ | **扩展页面 → 内容脚本** | chrome.tabs + TabRPCClient | 标签页特定访问 |
267
+ | **背景脚本 → 所有上下文** | RemoteSubject 广播 | 实时数据流 |
268
+
269
+ ### 核心组件
177
270
  │ │
178
271
  │ 代理服务 │
179
272
  │ .subtract(5,2) │
@@ -187,12 +280,14 @@ async function calculate() {
187
280
  3. **背景脚本 → 内容脚本**: 使用 `chrome.tabs.sendMessage`
188
281
  4. **内容脚本 → 网页**: 使用 `window.dispatchEvent` 和 `CustomEvent`
189
282
  5. **内容脚本直接**: 直接使用 `chrome.runtime.sendMessage` (ContentRPCClient)
283
+ 6. **扩展页面 ↔ 背景脚本**: 直接使用 `chrome.runtime.sendMessage/onMessage` (ExtPageRPCClient)
190
284
 
191
285
  ### 核心组件
192
286
 
193
- - **WebRPCClient**: 用于网页的客户端,使用window事件
287
+ - **WebRPCClient**: 用于网页的客户端,使用window事件
194
288
  - **ContentRPC**: 在网页和背景脚本间转发消息的桥接器
195
- - **ContentRPCClient**: 内容脚本的直接RPC客户端(绕过桥接器)
289
+ - **ContentRPCClient**: 内容脚本的直接RPC客户端(绕过桥接器)
290
+ - **ExtPageRPCClient**: 用于扩展页面(popup/options/sidepanel)的直接RPC客户端
196
291
  - **BackgroundRPC**: 背景脚本中的服务注册表和处理器
197
292
  - **RPCClient**: 具有服务代理生成功能的基础客户端
198
293
 
@@ -217,31 +312,12 @@ const rpc = new BackgroundRPC(true); // 启用日志
217
312
 
218
313
  ### 日志输出
219
314
 
220
- 启用日志时,会记录以下信息并使用**彩色格式**:
221
-
222
- - **函数调用**: `[RPC] Call: Service.Method [ID]` 带有彩色组件
223
- - **成功响应**: `[RPC] Success: Service.Method [ID]` 带有绿色成功指示器
224
- - **错误响应**: `[RPC] Error: Service.Method [ID]` 带有红色错误指示器
225
- - **未知服务/方法**: `[RPC] Unknown service/method: Service.Method [ID]` 带有橙色警告
226
-
227
- ### 颜色方案
228
-
229
- - 🟣 **紫色**: `[RPC]` 前缀和请求ID
230
- - 🟢 **绿色**: 服务名和成功指示器
231
- - 🔴 **红色**: 方法名和错误指示器
232
- - 🟠 **橙色**: 警告消息
233
- - ⚫ **灰色**: 分隔符和结构元素
315
+ 启用日志时,会记录以下信息:
234
316
 
235
- ### 输出示例
236
-
237
- ```
238
- [RPC] Call: MathService.add [rpc-123]
239
- [RPC] Success: MathService.add [rpc-123]
240
- [RPC] Error: MathService.divide [rpc-124]
241
- [RPC] Unknown service: InvalidService [rpc-125]
242
- ```
243
-
244
- *注意:颜色在浏览器开发者控制台中可见,不在纯文本中显示。*
317
+ - **函数调用**: 服务名、方法名、参数、发送者ID和时间戳
318
+ - **成功响应**: 服务名、方法名、结果和时间戳
319
+ - **错误响应**: 服务名、方法名、错误消息和时间戳
320
+ - **未知服务/方法**: 无效服务或方法调用的警告
245
321
 
246
322
  ### 使用场景
247
323
 
@@ -377,6 +453,35 @@ const observable = new ContentObservable(
377
453
  // observable.dispose();
378
454
  ```
379
455
 
456
+ ### 从扩展页面订阅
457
+
458
+ ```typescript
459
+ // popup.ts / options.ts / sidepanel.ts
460
+ import { ExtPageObservable, createIdentifier } from 'crx-rpc';
461
+
462
+ interface ICounterObservable {
463
+ value: number;
464
+ }
465
+
466
+ const ICounterObservable = createIdentifier<ICounterObservable>('Counter');
467
+
468
+ // 扩展页面可以订阅background的observables
469
+ const observable = new ExtPageObservable(
470
+ ICounterObservable,
471
+ 'main',
472
+ (value) => {
473
+ console.log('Popup计数器更新:', value.value);
474
+ // 更新popup UI
475
+ document.getElementById('counter').textContent = value.value.toString();
476
+ }
477
+ );
478
+
479
+ // 页面关闭时自动清理
480
+ window.addEventListener('unload', () => {
481
+ observable.dispose();
482
+ });
483
+ ```
484
+
380
485
  ### Observable通信模式
381
486
 
382
487
  Observable系统支持多种具有集中式管理的通信模式:
@@ -477,6 +582,150 @@ document.addEventListener('DOMContentLoaded', async () => {
477
582
  4. **桥接+客户端**: 既作为网页的桥接器又作为直接客户端
478
583
  5. **DOM操作**: 使用RPC数据修改页面内容
479
584
 
585
+ ### 扩展页面作为直接客户端
586
+
587
+ 扩展页面(popup、options、sidepanel等)可以直接与background通信,无需content script:
588
+
589
+ ```typescript
590
+ // popup.ts
591
+ import { ExtPageRPCClient, ExtPageObservable } from 'crx-rpc';
592
+ import { IMathService, IUserService, ICounterObservable } from './services';
593
+
594
+ const client = new ExtPageRPCClient();
595
+
596
+ // 创建服务代理
597
+ const mathService = client.createWebRPCService(IMathService);
598
+ const userService = client.createWebRPCService(IUserService);
599
+
600
+ // 直接调用背景服务
601
+ const result = await mathService.add(5, 3);
602
+ const user = await userService.getUser('123');
603
+
604
+ // 扩展页面也可以订阅observables
605
+ const counterObservable = new ExtPageObservable(
606
+ ICounterObservable,
607
+ 'main',
608
+ (value) => {
609
+ // 实时更新popup UI
610
+ document.getElementById('counter').textContent = value.toString();
611
+ }
612
+ );
613
+
614
+ // 在popup中使用
615
+ document.addEventListener('DOMContentLoaded', async () => {
616
+ const calculation = await mathService.multiply(2, 3);
617
+ document.getElementById('result').textContent = `结果: ${calculation}`;
618
+
619
+ // 更新用户信息
620
+ const currentUser = await userService.getUser('me');
621
+ document.getElementById('username').textContent = currentUser.name;
622
+ });
623
+
624
+ // 页面关闭时清理
625
+ window.addEventListener('unload', () => {
626
+ client.dispose();
627
+ counterObservable.dispose();
628
+ });
629
+ ```
630
+
631
+ ### 扩展页面使用场景
632
+
633
+ 扩展页面在各种场景中使用RPC:
634
+
635
+ 1. **直接通信**: 与background service直接通信,无需content script
636
+ 2. **UI交互**: 根据background数据更新popup/options界面
637
+ 3. **实时更新**: 订阅observables获取实时数据推送
638
+ 4. **用户设置**: 在options页面中读取/保存配置
639
+ 5. **状态同步**: 与background保持状态同步
640
+
641
+ ### 扩展页面访问内容脚本服务
642
+
643
+ 扩展页面可以使用 `TabRPCClient` 通过指定 tab ID 来访问内容脚本的服务:
644
+
645
+ ```typescript
646
+ // popup.ts
647
+ import { TabRPCClient } from 'crx-rpc';
648
+ import { IContentService } from './services';
649
+
650
+ // 获取当前活动标签页
651
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
652
+
653
+ if (tab.id) {
654
+ // 为特定 tab 创建 RPC 客户端
655
+ const tabClient = new TabRPCClient(tab.id);
656
+
657
+ // 访问该 tab 中的内容脚本服务
658
+ const contentService = tabClient.createWebRPCService(IContentService);
659
+
660
+ // 调用内容脚本方法
661
+ const result = await contentService.getDOMInfo();
662
+ console.log('来自内容脚本的 DOM 信息:', result);
663
+
664
+ // 完成时清理
665
+ window.addEventListener('unload', () => {
666
+ tabClient.dispose();
667
+ });
668
+ }
669
+ ```
670
+
671
+ #### 扩展页面 → 内容脚本通信的使用场景:
672
+
673
+ 1. **DOM 检查**: Popup 查询内容脚本获取页面信息
674
+ 2. **用户操作**: Options 页面触发特定标签页的内容脚本操作
675
+ 3. **多标签管理**: Sidepanel 协调多个标签页的操作
676
+ 4. **实时预览**: 扩展页面从内容脚本获取实时更新
677
+
678
+ #### 完整示例: Popup 与标签页特定服务交互
679
+
680
+ ```typescript
681
+ // content.ts - 在内容脚本中注册服务
682
+ import { ContentRPCHost } from 'crx-rpc';
683
+ import { IPageService } from './services';
684
+
685
+ class PageService implements IPageService {
686
+ async getTitle(): Promise<string> {
687
+ return document.title;
688
+ }
689
+
690
+ async getSelection(): Promise<string> {
691
+ return window.getSelection()?.toString() || '';
692
+ }
693
+
694
+ async highlightText(text: string): Promise<void> {
695
+ // 高亮逻辑...
696
+ }
697
+ }
698
+
699
+ const contentHost = new ContentRPCHost();
700
+ contentHost.register(IPageService, new PageService());
701
+
702
+ // popup.ts - 从 popup 访问内容脚本
703
+ import { TabRPCClient, ExtPageRPCClient } from 'crx-rpc';
704
+ import { IPageService, IMathService } from './services';
705
+
706
+ // 访问背景服务
707
+ const bgClient = new ExtPageRPCClient();
708
+ const mathService = bgClient.createWebRPCService(IMathService);
709
+
710
+ // 访问活动标签页中的内容脚本服务
711
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
712
+ if (tab.id) {
713
+ const tabClient = new TabRPCClient(tab.id);
714
+ const pageService = tabClient.createWebRPCService(IPageService);
715
+
716
+ // 从内容脚本获取页面信息
717
+ const title = await pageService.getTitle();
718
+ const selection = await pageService.getSelection();
719
+
720
+ // 使用背景服务处理
721
+ const result = await mathService.calculate(selection.length);
722
+
723
+ // 更新 popup UI
724
+ document.getElementById('title').textContent = title;
725
+ document.getElementById('result').textContent = result.toString();
726
+ }
727
+ ```
728
+
480
729
  ### 复杂数据类型
481
730
 
482
731
  ```typescript
@@ -537,15 +786,20 @@ const [sum, user, file] = await Promise.all([
537
786
 
538
787
  ### 场景2: 仅内容脚本
539
788
  - 内容脚本需要直接访问背景服务
540
- - 使用: 直接使用 `ContentRPCClient`(无需桥接器)
789
+ - 使用: 直接使用 `ContentRPCClient`(无需桥接器)
541
790
 
542
791
  ### 场景3: 网页和内容脚本同时
543
792
  - 两个上下文都需要RPC访问
544
793
  - 使用: `ContentRPC` 桥接器 + `ContentRPCClient` 进行直接访问
545
794
 
546
- ### 场景4: 实时数据流
795
+ ### 场景4: 扩展页面(Popup/Options/Sidepanel)
796
+ - 扩展内置页面需要访问背景服务
797
+ - 使用: 直接使用 `ExtPageRPCClient`
798
+ - 特点: 不需要content script,直接与background通信
799
+
800
+ ### 场景5: 实时数据流
547
801
  - 背景脚本需要向多个上下文推送更新
548
- - 使用: `RemoteSubject` + `WebObservable`/`ContentObservable`
802
+ - 使用: `RemoteSubject` + `WebObservable`/`ContentObservable`/`ExtPageObservable`
549
803
 
550
804
  ## API参考
551
805
 
@@ -555,6 +809,7 @@ const [sum, user, file] = await Promise.all([
555
809
  - **`ContentRPC`**: 网页和背景脚本间的消息桥接器
556
810
  - **`WebRPCClient`**: 网页的RPC客户端
557
811
  - **`ContentRPCClient`**: 内容脚本的直接RPC客户端
812
+ - **`ExtPageRPCClient`**: 扩展页面(popup/options/sidepanel)的直接RPC客户端
558
813
  - **`RemoteSubjectManager`**: 集中式observable消息管理系统
559
814
 
560
815
  ### Observable类
@@ -563,6 +818,7 @@ const [sum, user, file] = await Promise.all([
563
818
  - **`RemoteSubject<T>`**: 与管理器配合进行纯状态管理的Observable subject
564
819
  - **`WebObservable<T>`**: 网页的Observable订阅者
565
820
  - **`ContentObservable<T>`**: 内容脚本的Observable订阅者
821
+ - **`ExtPageObservable<T>`**: 扩展页面的Observable订阅者
566
822
 
567
823
  ### 工具函数
568
824