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/README.md +417 -0
- package/README.zh-CN.md +533 -0
- package/dist/background.d.ts +21 -0
- package/dist/background.js +146 -0
- package/dist/client.d.ts +27 -0
- package/dist/client.js +88 -0
- package/dist/const.d.ts +5 -0
- package/dist/const.js +5 -0
- package/dist/content.d.ts +14 -0
- package/dist/content.js +55 -0
- package/dist/disposable.d.ts +7 -0
- package/dist/disposable.js +16 -0
- package/dist/id.d.ts +12 -0
- package/dist/id.js +8 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +1 -0
- package/dist/web.d.ts +10 -0
- package/dist/web.js +25 -0
- package/package.json +27 -0
- package/src/background.ts +162 -0
- package/src/client.ts +110 -0
- package/src/const.ts +6 -0
- package/src/content.ts +71 -0
- package/src/disposable.ts +18 -0
- package/src/id.ts +17 -0
- package/src/index.ts +6 -0
- package/src/types.ts +49 -0
- package/src/web.ts +34 -0
- package/tsconfig.json +16 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IMessageAdapter } from './types';
|
|
2
|
+
import type { Identifier } from './id';
|
|
3
|
+
import { Disposable } from './disposable';
|
|
4
|
+
type FunctionArgs<T> = T extends (...args: infer A) => any ? A : never;
|
|
5
|
+
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
6
|
+
type ServiceProxy<T> = {
|
|
7
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any ? (...args: FunctionArgs<T[K]>) => Promise<Awaited<FunctionReturnType<T[K]>>> : never;
|
|
8
|
+
};
|
|
9
|
+
export declare class RPCClient extends Disposable {
|
|
10
|
+
private messageAdapter;
|
|
11
|
+
private pending;
|
|
12
|
+
constructor(messageAdapter: IMessageAdapter);
|
|
13
|
+
call<T = any>(service: string, method: string, args: any[]): Promise<T>;
|
|
14
|
+
createWebRPCService<T>(serviceIdentifier: Identifier<T>): ServiceProxy<T>;
|
|
15
|
+
}
|
|
16
|
+
export declare class BaseObservable<T> extends Disposable {
|
|
17
|
+
private identifier;
|
|
18
|
+
private key;
|
|
19
|
+
private _callback;
|
|
20
|
+
private _adapter;
|
|
21
|
+
private listeners;
|
|
22
|
+
private completed;
|
|
23
|
+
private get _finalKey();
|
|
24
|
+
constructor(identifier: Identifier<T>, key: string, _callback: (value: T) => void, _adapter: IMessageAdapter);
|
|
25
|
+
unsubscribe(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { OBSERVABLE_EVENT, RPC_EVENT_NAME, RPC_RESPONSE_EVENT_NAME, UNSUBSCRIBE_OBSERVABLE } from './const';
|
|
2
|
+
import { Disposable } from './disposable';
|
|
3
|
+
export class RPCClient extends Disposable {
|
|
4
|
+
messageAdapter;
|
|
5
|
+
pending = new Map();
|
|
6
|
+
constructor(messageAdapter) {
|
|
7
|
+
super();
|
|
8
|
+
this.messageAdapter = messageAdapter;
|
|
9
|
+
this.disposeWithMe(messageAdapter.onMessage(RPC_RESPONSE_EVENT_NAME, (event) => {
|
|
10
|
+
const { id, result, error } = event;
|
|
11
|
+
const promise = this.pending.get(id);
|
|
12
|
+
if (!promise)
|
|
13
|
+
return;
|
|
14
|
+
this.pending.delete(id);
|
|
15
|
+
if (error) {
|
|
16
|
+
const err = new Error(error.message);
|
|
17
|
+
err.name = error.name || 'RPCError';
|
|
18
|
+
err.stack = error.stack;
|
|
19
|
+
promise.reject(err);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
promise.resolve(result);
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
call(service, method, args) {
|
|
27
|
+
const id = crypto.randomUUID();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
this.pending.set(id, { resolve, reject });
|
|
30
|
+
const requestParam = {
|
|
31
|
+
method,
|
|
32
|
+
args,
|
|
33
|
+
id,
|
|
34
|
+
service,
|
|
35
|
+
};
|
|
36
|
+
this.messageAdapter.sendMessage(RPC_EVENT_NAME, requestParam);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
createWebRPCService(serviceIdentifier) {
|
|
40
|
+
const serviceKey = serviceIdentifier.key;
|
|
41
|
+
// 创建代理对象,拦截方法调用
|
|
42
|
+
return new Proxy({}, {
|
|
43
|
+
get: (target, prop) => {
|
|
44
|
+
if (typeof prop === 'string') {
|
|
45
|
+
// 返回一个代理函数
|
|
46
|
+
return (...args) => {
|
|
47
|
+
return this.call(serviceKey, prop, args);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return target[prop];
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export class BaseObservable extends Disposable {
|
|
56
|
+
identifier;
|
|
57
|
+
key;
|
|
58
|
+
_callback;
|
|
59
|
+
_adapter;
|
|
60
|
+
listeners = new Set();
|
|
61
|
+
completed = false;
|
|
62
|
+
get _finalKey() {
|
|
63
|
+
return `${this.identifier.key}-${this.key}`;
|
|
64
|
+
}
|
|
65
|
+
constructor(identifier, key, _callback, _adapter) {
|
|
66
|
+
super();
|
|
67
|
+
this.identifier = identifier;
|
|
68
|
+
this.key = key;
|
|
69
|
+
this._callback = _callback;
|
|
70
|
+
this._adapter = _adapter;
|
|
71
|
+
this.disposeWithMe(this._adapter.onMessage(OBSERVABLE_EVENT, (event) => {
|
|
72
|
+
const msg = event.detail;
|
|
73
|
+
if (msg.key !== this._finalKey)
|
|
74
|
+
return;
|
|
75
|
+
if (msg.operation === 'next' && !this.completed && msg.value) {
|
|
76
|
+
this._callback(msg.value);
|
|
77
|
+
}
|
|
78
|
+
if (msg.operation === 'complete') {
|
|
79
|
+
this.completed = true;
|
|
80
|
+
this.listeners.clear();
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
this._adapter.sendMessage(OBSERVABLE_EVENT, { key: this._finalKey });
|
|
84
|
+
}
|
|
85
|
+
unsubscribe() {
|
|
86
|
+
this._adapter.sendMessage(UNSUBSCRIBE_OBSERVABLE, { key: this._finalKey });
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/const.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const RPC_EVENT_NAME = "__RPC_CALL_CLIPSHEET_AWESOME__";
|
|
2
|
+
export declare const RPC_RESPONSE_EVENT_NAME = "__RPC_RESPONSE_CLIPSHEET_AWESOME__";
|
|
3
|
+
export declare const SUBSCRIBABLE_OBSERVABLE = "__SUBSCRIBABLE_OBSERVABLE__";
|
|
4
|
+
export declare const UNSUBSCRIBE_OBSERVABLE = "__UNSUBSCRIBE_OBSERVABLE__";
|
|
5
|
+
export declare const OBSERVABLE_EVENT = "__OBSERVABLE_EVENT__";
|
package/dist/const.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const RPC_EVENT_NAME = '__RPC_CALL_CLIPSHEET_AWESOME__';
|
|
2
|
+
export const RPC_RESPONSE_EVENT_NAME = '__RPC_RESPONSE_CLIPSHEET_AWESOME__';
|
|
3
|
+
export const SUBSCRIBABLE_OBSERVABLE = '__SUBSCRIBABLE_OBSERVABLE__';
|
|
4
|
+
export const UNSUBSCRIBE_OBSERVABLE = '__UNSUBSCRIBE_OBSERVABLE__';
|
|
5
|
+
export const OBSERVABLE_EVENT = '__OBSERVABLE_EVENT__';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseObservable, RPCClient } from './client';
|
|
2
|
+
import { Disposable } from './disposable';
|
|
3
|
+
import { Identifier } from './id';
|
|
4
|
+
import type { IMessageAdapter } from './types';
|
|
5
|
+
export declare class ContentRPC extends Disposable {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
export declare const contentMessageAdapter: IMessageAdapter;
|
|
9
|
+
export declare class ContentRPCClient extends RPCClient {
|
|
10
|
+
constructor();
|
|
11
|
+
}
|
|
12
|
+
export declare class ContentObservable<T> extends BaseObservable<T> {
|
|
13
|
+
constructor(identifier: Identifier<T>, key: string, callback: (value: T) => void);
|
|
14
|
+
}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
const WEB_TO_BACKGROUND = [RPC_EVENT_NAME, SUBSCRIBABLE_OBSERVABLE, UNSUBSCRIBE_OBSERVABLE];
|
|
5
|
+
const BACKGROUND_TO_WEB = [RPC_RESPONSE_EVENT_NAME, OBSERVABLE_EVENT];
|
|
6
|
+
export class ContentRPC extends Disposable {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
WEB_TO_BACKGROUND.forEach(eventName => {
|
|
10
|
+
const handler = (event) => {
|
|
11
|
+
chrome.runtime.sendMessage({ ...event.detail, type: eventName });
|
|
12
|
+
};
|
|
13
|
+
window.addEventListener(eventName, handler);
|
|
14
|
+
this.disposeWithMe(() => {
|
|
15
|
+
window.removeEventListener(eventName, handler);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
const handler = (msg) => {
|
|
19
|
+
if (!BACKGROUND_TO_WEB.includes(msg.type))
|
|
20
|
+
return;
|
|
21
|
+
const { type, ...detail } = msg;
|
|
22
|
+
window.dispatchEvent(new CustomEvent(type, { detail }));
|
|
23
|
+
};
|
|
24
|
+
chrome.runtime.onMessage.addListener(handler);
|
|
25
|
+
this.disposeWithMe(() => {
|
|
26
|
+
chrome.runtime.onMessage.removeListener(handler);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const contentMessageAdapter = {
|
|
31
|
+
onMessage(type, callback) {
|
|
32
|
+
const handler = (msg) => {
|
|
33
|
+
if (msg.type === type) {
|
|
34
|
+
callback(msg);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
chrome.runtime.onMessage.addListener(handler);
|
|
38
|
+
return () => {
|
|
39
|
+
chrome.runtime.onMessage.removeListener(handler);
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
sendMessage(type, message) {
|
|
43
|
+
chrome.runtime.sendMessage({ ...message, type });
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
export class ContentRPCClient extends RPCClient {
|
|
47
|
+
constructor() {
|
|
48
|
+
super(contentMessageAdapter);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class ContentObservable extends BaseObservable {
|
|
52
|
+
constructor(identifier, key, callback) {
|
|
53
|
+
super(identifier, key, callback, contentMessageAdapter);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class Disposable {
|
|
2
|
+
_isDisposed = false;
|
|
3
|
+
_disposeCallbacks = new Set();
|
|
4
|
+
dispose() {
|
|
5
|
+
if (this._isDisposed)
|
|
6
|
+
return;
|
|
7
|
+
this._isDisposed = true;
|
|
8
|
+
this._disposeCallbacks.forEach(callback => callback());
|
|
9
|
+
}
|
|
10
|
+
isDisposed() {
|
|
11
|
+
return this._isDisposed;
|
|
12
|
+
}
|
|
13
|
+
disposeWithMe(disposeLike) {
|
|
14
|
+
this._disposeCallbacks.add(disposeLike);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/id.d.ts
ADDED
package/dist/id.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface RpcRequest {
|
|
2
|
+
id: string;
|
|
3
|
+
method: string;
|
|
4
|
+
service: string;
|
|
5
|
+
args: any[];
|
|
6
|
+
}
|
|
7
|
+
export interface RpcResponse {
|
|
8
|
+
id: string;
|
|
9
|
+
result?: any;
|
|
10
|
+
error?: {
|
|
11
|
+
message: string;
|
|
12
|
+
stack?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
};
|
|
15
|
+
service: string;
|
|
16
|
+
method: string;
|
|
17
|
+
}
|
|
18
|
+
export type RpcHandler = (...args: any[]) => Promise<any> | any;
|
|
19
|
+
export type RpcService = Record<string, RpcHandler>;
|
|
20
|
+
export interface ObservableLike<T> {
|
|
21
|
+
subscribe(next: (value: T) => void): () => void;
|
|
22
|
+
}
|
|
23
|
+
export interface SubjectLike<T> extends ObservableLike<T> {
|
|
24
|
+
next(value: T): void;
|
|
25
|
+
complete(): void;
|
|
26
|
+
}
|
|
27
|
+
export interface RpcObservableUpdateMessage<T> {
|
|
28
|
+
type: string;
|
|
29
|
+
operation: 'next' | 'complete';
|
|
30
|
+
key: string;
|
|
31
|
+
value?: T;
|
|
32
|
+
}
|
|
33
|
+
export interface RpcObservableSubscribeMessage {
|
|
34
|
+
type: string;
|
|
35
|
+
key: string;
|
|
36
|
+
}
|
|
37
|
+
export interface IMessageAdapter {
|
|
38
|
+
onMessage<T>(type: string, callback: (message: T) => void): () => void;
|
|
39
|
+
sendMessage<T>(type: string, message: T): void;
|
|
40
|
+
}
|
|
41
|
+
export interface IDisposable {
|
|
42
|
+
dispose(): void;
|
|
43
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseObservable, RPCClient } from "./client";
|
|
2
|
+
import { Identifier } from "./id";
|
|
3
|
+
import { IMessageAdapter } from "./types";
|
|
4
|
+
export declare const webMessageAdapter: IMessageAdapter;
|
|
5
|
+
export declare class WebRPCClient extends RPCClient {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
export declare class WebObservable<T> extends BaseObservable<T> {
|
|
9
|
+
constructor(identifier: Identifier<T>, key: string, callback: (value: T) => void);
|
|
10
|
+
}
|
package/dist/web.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseObservable, RPCClient } from "./client";
|
|
2
|
+
export const webMessageAdapter = {
|
|
3
|
+
onMessage(type, callback) {
|
|
4
|
+
const handler = (event) => {
|
|
5
|
+
callback(event.detail);
|
|
6
|
+
};
|
|
7
|
+
window.addEventListener(type, handler);
|
|
8
|
+
return () => {
|
|
9
|
+
window.removeEventListener(type, handler);
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
sendMessage(type, message) {
|
|
13
|
+
window.dispatchEvent(new CustomEvent(type, { detail: message }));
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export class WebRPCClient extends RPCClient {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(webMessageAdapter);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class WebObservable extends BaseObservable {
|
|
22
|
+
constructor(identifier, key, callback) {
|
|
23
|
+
super(identifier, key, callback, webMessageAdapter);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crx-rpc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight RPC framework for Chrome Extension (background <-> content <-> web)",
|
|
5
|
+
"author": "YourName",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"chrome-extension",
|
|
9
|
+
"rpc",
|
|
10
|
+
"content-script",
|
|
11
|
+
"background",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"watch": "tsc -w",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"postinstall": "tsc"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/chrome": "^0.0.270",
|
|
24
|
+
"@types/node": "^24.3.0",
|
|
25
|
+
"typescript": "^5.2.2"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Identifier } from './id';
|
|
2
|
+
import { OBSERVABLE_EVENT, RPC_EVENT_NAME, RPC_RESPONSE_EVENT_NAME, SUBSCRIBABLE_OBSERVABLE, UNSUBSCRIBE_OBSERVABLE } from './const';
|
|
3
|
+
import type { RpcRequest, RpcResponse, RpcService, SubjectLike, RpcObservableUpdateMessage, RpcObservableSubscribeMessage } from './types';
|
|
4
|
+
import { Disposable } from './disposable';
|
|
5
|
+
|
|
6
|
+
export class BackgroundRPC extends Disposable {
|
|
7
|
+
private services: Record<string, RpcService> = {};
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
const handler = ((msg: RpcRequest & { type?: string }, sender: chrome.runtime.MessageSender) => {
|
|
12
|
+
if (msg.type !== RPC_EVENT_NAME) return;
|
|
13
|
+
const senderId = sender.tab!.id!;
|
|
14
|
+
const sendResponse = (response: RpcResponse) => {
|
|
15
|
+
chrome.tabs.sendMessage(senderId, {
|
|
16
|
+
...response,
|
|
17
|
+
type: RPC_RESPONSE_EVENT_NAME
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const { id, method, args, service } = msg;
|
|
22
|
+
const serviceInstance = this.services[service];
|
|
23
|
+
|
|
24
|
+
if (!serviceInstance) {
|
|
25
|
+
const resp: RpcResponse = {
|
|
26
|
+
id,
|
|
27
|
+
error: { message: `Unknown service: ${service}` },
|
|
28
|
+
service,
|
|
29
|
+
method,
|
|
30
|
+
};
|
|
31
|
+
sendResponse(resp);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!(method in serviceInstance)) {
|
|
36
|
+
const resp: RpcResponse = {
|
|
37
|
+
id,
|
|
38
|
+
error: { message: `Unknown method: ${method}` },
|
|
39
|
+
service,
|
|
40
|
+
method,
|
|
41
|
+
};
|
|
42
|
+
sendResponse(resp);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Promise.resolve()
|
|
47
|
+
.then(() => serviceInstance[method](...args))
|
|
48
|
+
.then((result) => sendResponse({
|
|
49
|
+
id,
|
|
50
|
+
result,
|
|
51
|
+
service,
|
|
52
|
+
method
|
|
53
|
+
}))
|
|
54
|
+
.catch((err) => sendResponse({
|
|
55
|
+
id,
|
|
56
|
+
error: {
|
|
57
|
+
message: err.message,
|
|
58
|
+
stack: err.stack,
|
|
59
|
+
name: err.name
|
|
60
|
+
},
|
|
61
|
+
service,
|
|
62
|
+
method
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
return true; // 异步 sendResponse
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
chrome.runtime.onMessage.addListener(handler);
|
|
69
|
+
this.disposeWithMe(() => {
|
|
70
|
+
chrome.runtime.onMessage.removeListener(handler);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
register<T>(service: Identifier<T>, serviceInstance: T) {
|
|
75
|
+
this.services[service.key] = serviceInstance as unknown as RpcService;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class RemoteSubject<T> extends Disposable implements SubjectLike<T> {
|
|
80
|
+
private completed = false;
|
|
81
|
+
|
|
82
|
+
private get _finalKey() {
|
|
83
|
+
return `${this.identifier.key}-${this._key}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private senders = new Set<number>();
|
|
87
|
+
|
|
88
|
+
constructor(
|
|
89
|
+
private identifier: Identifier<T>,
|
|
90
|
+
private _key: string,
|
|
91
|
+
private initialValue: T,
|
|
92
|
+
) {
|
|
93
|
+
super();
|
|
94
|
+
// 初始化时立即广播一次
|
|
95
|
+
const handleMessage = (msg: RpcObservableSubscribeMessage, sender: chrome.runtime.MessageSender) => {
|
|
96
|
+
const senderId = sender.tab!.id!;
|
|
97
|
+
if (!senderId) return;
|
|
98
|
+
|
|
99
|
+
if (msg.type === SUBSCRIBABLE_OBSERVABLE) {
|
|
100
|
+
const { key } = msg;
|
|
101
|
+
if (key === this._finalKey) {
|
|
102
|
+
this.senders.add(senderId);
|
|
103
|
+
chrome.tabs.sendMessage(senderId, {
|
|
104
|
+
operation: 'next',
|
|
105
|
+
key: this._finalKey,
|
|
106
|
+
value: this.initialValue,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (msg.type === UNSUBSCRIBE_OBSERVABLE) {
|
|
112
|
+
const { key } = msg;
|
|
113
|
+
if (key === this._finalKey) {
|
|
114
|
+
this.senders.delete(senderId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
chrome.runtime.onMessage.addListener(handleMessage);
|
|
119
|
+
this.disposeWithMe(() => {
|
|
120
|
+
chrome.runtime.onMessage.removeListener(handleMessage);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const handleRemove = (tabId: number) => {
|
|
124
|
+
this.senders.delete(tabId);
|
|
125
|
+
};
|
|
126
|
+
chrome.tabs.onRemoved.addListener(handleRemove);
|
|
127
|
+
this.disposeWithMe(() => {
|
|
128
|
+
chrome.tabs.onRemoved.removeListener(handleRemove);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private _sendMessage(message: RpcObservableUpdateMessage<any>) {
|
|
133
|
+
chrome.runtime.sendMessage(message);
|
|
134
|
+
this.senders.forEach(senderId => {
|
|
135
|
+
chrome.tabs.sendMessage(senderId, message);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
next(value: T): void {
|
|
140
|
+
if (this.completed) return;
|
|
141
|
+
this._sendMessage({
|
|
142
|
+
operation: 'next',
|
|
143
|
+
key: this._finalKey,
|
|
144
|
+
value,
|
|
145
|
+
type: OBSERVABLE_EVENT
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
complete(): void {
|
|
150
|
+
if (this.completed) return;
|
|
151
|
+
this.completed = true;
|
|
152
|
+
this._sendMessage({
|
|
153
|
+
operation: 'complete',
|
|
154
|
+
key: this._finalKey,
|
|
155
|
+
type: OBSERVABLE_EVENT
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
subscribe(): () => void {
|
|
160
|
+
throw new Error('RemoteSubject should not be subscribed locally.');
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { OBSERVABLE_EVENT, RPC_EVENT_NAME, RPC_RESPONSE_EVENT_NAME, UNSUBSCRIBE_OBSERVABLE } from './const';
|
|
2
|
+
import type { RpcRequest, RpcResponse, RpcObservableUpdateMessage, IMessageAdapter } from './types';
|
|
3
|
+
import type { Identifier } from './id';
|
|
4
|
+
import { Disposable } from './disposable';
|
|
5
|
+
|
|
6
|
+
// 类型工具:提取函数类型的参数和返回值类型
|
|
7
|
+
type FunctionArgs<T> = T extends (...args: infer A) => any ? A : never;
|
|
8
|
+
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
9
|
+
|
|
10
|
+
// 类型工具:将服务接口转换为客户端代理类型
|
|
11
|
+
type ServiceProxy<T> = {
|
|
12
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any
|
|
13
|
+
? (...args: FunctionArgs<T[K]>) => Promise<Awaited<FunctionReturnType<T[K]>>>
|
|
14
|
+
: never;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class RPCClient extends Disposable {
|
|
18
|
+
private pending: Map<string, { resolve: Function; reject: Function }> = new Map();
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private messageAdapter: IMessageAdapter
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
this.disposeWithMe(messageAdapter.onMessage<RpcResponse>(RPC_RESPONSE_EVENT_NAME, (event: RpcResponse) => {
|
|
25
|
+
const { id, result, error } = event as RpcResponse;
|
|
26
|
+
const promise = this.pending.get(id);
|
|
27
|
+
if (!promise) return;
|
|
28
|
+
|
|
29
|
+
this.pending.delete(id);
|
|
30
|
+
|
|
31
|
+
if (error) {
|
|
32
|
+
const err = new Error(error.message);
|
|
33
|
+
err.name = error.name || 'RPCError';
|
|
34
|
+
err.stack = error.stack;
|
|
35
|
+
promise.reject(err);
|
|
36
|
+
} else {
|
|
37
|
+
promise.resolve(result);
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
call<T = any>(service: string, method: string, args: any[]): Promise<T> {
|
|
43
|
+
const id = crypto.randomUUID();
|
|
44
|
+
return new Promise<T>((resolve, reject) => {
|
|
45
|
+
this.pending.set(id, { resolve, reject });
|
|
46
|
+
const requestParam: RpcRequest = {
|
|
47
|
+
method,
|
|
48
|
+
args,
|
|
49
|
+
id,
|
|
50
|
+
service,
|
|
51
|
+
};
|
|
52
|
+
this.messageAdapter.sendMessage(RPC_EVENT_NAME, requestParam);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
createWebRPCService<T>(serviceIdentifier: Identifier<T>): ServiceProxy<T> {
|
|
57
|
+
const serviceKey = serviceIdentifier.key;
|
|
58
|
+
|
|
59
|
+
// 创建代理对象,拦截方法调用
|
|
60
|
+
return new Proxy({} as ServiceProxy<T>, {
|
|
61
|
+
get: (target, prop: string | symbol) => {
|
|
62
|
+
if (typeof prop === 'string') {
|
|
63
|
+
// 返回一个代理函数
|
|
64
|
+
return (...args: any[]) => {
|
|
65
|
+
return this.call(serviceKey, prop, args);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return (target as any)[prop];
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class BaseObservable<T> extends Disposable {
|
|
75
|
+
private listeners = new Set<(value: T) => void>();
|
|
76
|
+
private completed = false;
|
|
77
|
+
|
|
78
|
+
private get _finalKey() {
|
|
79
|
+
return `${this.identifier.key}-${this.key}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
constructor(
|
|
83
|
+
private identifier: Identifier<T>,
|
|
84
|
+
private key: string,
|
|
85
|
+
private _callback: (value: T) => void,
|
|
86
|
+
private _adapter: IMessageAdapter
|
|
87
|
+
) {
|
|
88
|
+
super();
|
|
89
|
+
this.disposeWithMe(this._adapter.onMessage(OBSERVABLE_EVENT, (event: any) => {
|
|
90
|
+
const msg = event.detail as RpcObservableUpdateMessage<T>;
|
|
91
|
+
if (msg.key !== this._finalKey) return;
|
|
92
|
+
|
|
93
|
+
if (msg.operation === 'next' && !this.completed && msg.value) {
|
|
94
|
+
this._callback(msg.value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (msg.operation === 'complete') {
|
|
98
|
+
this.completed = true;
|
|
99
|
+
this.listeners.clear();
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
this._adapter.sendMessage(OBSERVABLE_EVENT, { key: this._finalKey });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
unsubscribe(): void {
|
|
107
|
+
this._adapter.sendMessage(UNSUBSCRIBE_OBSERVABLE, { key: this._finalKey });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const RPC_EVENT_NAME = '__RPC_CALL_CLIPSHEET_AWESOME__';
|
|
2
|
+
export const RPC_RESPONSE_EVENT_NAME = '__RPC_RESPONSE_CLIPSHEET_AWESOME__';
|
|
3
|
+
|
|
4
|
+
export const SUBSCRIBABLE_OBSERVABLE = '__SUBSCRIBABLE_OBSERVABLE__';
|
|
5
|
+
export const UNSUBSCRIBE_OBSERVABLE = '__UNSUBSCRIBE_OBSERVABLE__';
|
|
6
|
+
export const OBSERVABLE_EVENT = '__OBSERVABLE_EVENT__';
|