crx-rpc 1.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/src/content.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { BaseObservable, RPCClient } from './client';
2
+ import { OBSERVABLE_EVENT, RPC_EVENT_NAME, RPC_RESPONSE_EVENT_NAME, SUBSCRIBABLE_OBSERVABLE, UNSUBSCRIBE_OBSERVABLE } from './const';
3
+ import { Disposable } from './disposable';
4
+ import { Identifier } from './id';
5
+ import type { IMessageAdapter, RpcRequest, RpcResponse, } from './types';
6
+
7
+ const WEB_TO_BACKGROUND = [RPC_EVENT_NAME, SUBSCRIBABLE_OBSERVABLE, UNSUBSCRIBE_OBSERVABLE]
8
+ const BACKGROUND_TO_WEB = [RPC_RESPONSE_EVENT_NAME, OBSERVABLE_EVENT]
9
+
10
+ export class ContentRPC extends Disposable {
11
+ constructor() {
12
+ super();
13
+ WEB_TO_BACKGROUND.forEach(eventName => {
14
+ const handler = (event: any) => {
15
+ chrome.runtime.sendMessage({ ...event.detail, type: eventName });
16
+ }
17
+ window.addEventListener(eventName, handler);
18
+
19
+ this.disposeWithMe(() => {
20
+ window.removeEventListener(eventName, handler);
21
+ });
22
+ });
23
+
24
+ const handler = (msg: { type: string } & (RpcRequest | RpcResponse)) => {
25
+ if (!BACKGROUND_TO_WEB.includes(msg.type)) return;
26
+ const { type, ...detail } = msg
27
+ window.dispatchEvent(new CustomEvent(type, { detail }));
28
+ }
29
+ chrome.runtime.onMessage.addListener(handler);
30
+
31
+ this.disposeWithMe(() => {
32
+ chrome.runtime.onMessage.removeListener(handler);
33
+ });
34
+ }
35
+ }
36
+
37
+
38
+ export const contentMessageAdapter: IMessageAdapter = {
39
+ onMessage<T>(type: string, callback: (message: T) => void) {
40
+ const handler = (msg: { type: string } & T) => {
41
+ if (msg.type === type) {
42
+ callback(msg);
43
+ }
44
+ }
45
+
46
+ chrome.runtime.onMessage.addListener(handler);
47
+ return () => {
48
+ chrome.runtime.onMessage.removeListener(handler);
49
+ }
50
+ },
51
+ sendMessage<T>(type: string, message: T): void {
52
+ chrome.runtime.sendMessage({ ...message, type });
53
+ },
54
+ };
55
+
56
+ export class ContentRPCClient extends RPCClient {
57
+ constructor() {
58
+ super(contentMessageAdapter);
59
+ }
60
+ }
61
+
62
+ export class ContentObservable<T> extends BaseObservable<T> {
63
+ constructor(
64
+ identifier: Identifier<T>,
65
+ key: string,
66
+ callback: (value: T) => void,
67
+ ) {
68
+ super(identifier, key, callback, contentMessageAdapter);
69
+ }
70
+ }
71
+
@@ -0,0 +1,18 @@
1
+ export class Disposable {
2
+ private _isDisposed = false;
3
+ private _disposeCallbacks: Set<() => void> = new Set();
4
+
5
+ dispose(): void {
6
+ if (this._isDisposed) return;
7
+ this._isDisposed = true;
8
+ this._disposeCallbacks.forEach(callback => callback());
9
+ }
10
+
11
+ isDisposed(): boolean {
12
+ return this._isDisposed;
13
+ }
14
+
15
+ protected disposeWithMe(disposeLike: () => void) {
16
+ this._disposeCallbacks.add(disposeLike);
17
+ }
18
+ }
package/src/id.ts ADDED
@@ -0,0 +1,17 @@
1
+ // shared/utils/identifier.ts
2
+
3
+ /**
4
+ * Identifier 类型,既携带类型信息,又在运行时能唯一标识。
5
+ */
6
+ export interface Identifier<T> {
7
+ key: string;
8
+ __type?: T; // 用于 TS 类型推导,不会出现在运行时
9
+ }
10
+
11
+ /**
12
+ * 创建一个 Identifier。
13
+ * @param key 唯一字符串标识
14
+ */
15
+ export function createIdentifier<T>(key: string): Identifier<T> {
16
+ return { key } as Identifier<T>;
17
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './background';
2
+ export * from './content';
3
+ export * from './types';
4
+ export * from './id';
5
+ export * from './web';
6
+ export * from './content';
package/src/types.ts ADDED
@@ -0,0 +1,49 @@
1
+ export interface RpcRequest {
2
+ id: string;
3
+ method: string;
4
+ service: string;
5
+ args: any[];
6
+ }
7
+
8
+ export interface RpcResponse {
9
+ id: string;
10
+ result?: any;
11
+ error?: { message: string; stack?: string; name?: string };
12
+ service: string;
13
+ method: string;
14
+ }
15
+
16
+ export type RpcHandler = (...args: any[]) => Promise<any> | any;
17
+
18
+ export type RpcService = Record<string, RpcHandler>;
19
+
20
+ export interface ObservableLike<T> {
21
+ subscribe(next: (value: T) => void): () => void;
22
+ }
23
+
24
+ export interface SubjectLike<T> extends ObservableLike<T> {
25
+ next(value: T): void;
26
+ complete(): void;
27
+ }
28
+
29
+ export interface RpcObservableUpdateMessage<T> {
30
+ type: string;
31
+ operation: 'next' | 'complete';
32
+ key: string;
33
+ value?: T;
34
+ }
35
+
36
+ export interface RpcObservableSubscribeMessage {
37
+ type: string;
38
+ key: string;
39
+ }
40
+
41
+ export interface IMessageAdapter {
42
+ onMessage<T>(type: string, callback: (message: T) => void): () => void;
43
+
44
+ sendMessage<T>(type: string, message: T): void;
45
+ }
46
+
47
+ export interface IDisposable {
48
+ dispose(): void;
49
+ }
package/src/web.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { BaseObservable, RPCClient } from "./client";
2
+ import { Identifier } from "./id";
3
+ import { IMessageAdapter } from "./types";
4
+
5
+ export const webMessageAdapter: IMessageAdapter = {
6
+ onMessage<T>(type: string, callback: (message: T) => void) {
7
+ const handler = (event: any) => {
8
+ callback(event.detail);
9
+ }
10
+ window.addEventListener(type, handler);
11
+ return () => {
12
+ window.removeEventListener(type, handler);
13
+ };
14
+ },
15
+ sendMessage<T>(type: string, message: T): void {
16
+ window.dispatchEvent(new CustomEvent(type, { detail: message }));
17
+ },
18
+ };
19
+
20
+ export class WebRPCClient extends RPCClient {
21
+ constructor() {
22
+ super(webMessageAdapter);
23
+ }
24
+ }
25
+
26
+ export class WebObservable<T> extends BaseObservable<T> {
27
+ constructor(
28
+ identifier: Identifier<T>,
29
+ key: string,
30
+ callback: (value: T) => void,
31
+ ) {
32
+ super(identifier, key, callback, webMessageAdapter);
33
+ }
34
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["DOM", "ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "node",
7
+ "types": ["node", "chrome"],
8
+ "strict": true,
9
+ "declaration": true,
10
+ "outDir": "dist",
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["src"]
16
+ }