electron-json-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/LICENSE +21 -0
- package/README.md +978 -0
- package/README.zh-CN.md +978 -0
- package/dist/debug.d.mts +92 -0
- package/dist/debug.d.mts.map +1 -0
- package/dist/debug.mjs +206 -0
- package/dist/debug.mjs.map +1 -0
- package/dist/error-xVRu7Lxq.mjs +131 -0
- package/dist/error-xVRu7Lxq.mjs.map +1 -0
- package/dist/event.d.mts +71 -0
- package/dist/event.d.mts.map +1 -0
- package/dist/event.mjs +60 -0
- package/dist/event.mjs.map +1 -0
- package/dist/index.d.mts +78 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/internal-BZK_0O3n.mjs +23 -0
- package/dist/internal-BZK_0O3n.mjs.map +1 -0
- package/dist/main.d.mts +151 -0
- package/dist/main.d.mts.map +1 -0
- package/dist/main.mjs +329 -0
- package/dist/main.mjs.map +1 -0
- package/dist/preload.d.mts +23 -0
- package/dist/preload.d.mts.map +1 -0
- package/dist/preload.mjs +417 -0
- package/dist/preload.mjs.map +1 -0
- package/dist/renderer/builder.d.mts +64 -0
- package/dist/renderer/builder.d.mts.map +1 -0
- package/dist/renderer/builder.mjs +101 -0
- package/dist/renderer/builder.mjs.map +1 -0
- package/dist/renderer/client.d.mts +42 -0
- package/dist/renderer/client.d.mts.map +1 -0
- package/dist/renderer/client.mjs +136 -0
- package/dist/renderer/client.mjs.map +1 -0
- package/dist/renderer/event.d.mts +17 -0
- package/dist/renderer/event.d.mts.map +1 -0
- package/dist/renderer/event.mjs +117 -0
- package/dist/renderer/event.mjs.map +1 -0
- package/dist/renderer/index.d.mts +6 -0
- package/dist/renderer/index.mjs +6 -0
- package/dist/stream.d.mts +38 -0
- package/dist/stream.d.mts.map +1 -0
- package/dist/stream.mjs +80 -0
- package/dist/stream.mjs.map +1 -0
- package/dist/types-BnGse9DF.d.mts +201 -0
- package/dist/types-BnGse9DF.d.mts.map +1 -0
- package/package.json +92 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { S as UnderlyingSource, _ as RpcValidator, a as ExposeRpcApiOptions, b as StreamMethodOptions, c as JsonRpcRequest, d as RpcClientOptions, f as RpcDebugOptions, g as RpcMethodOptions, h as RpcLogger, i as EventSubscriber, l as JsonRpcResponse, m as RpcLogEntry, n as EventBusEvents, o as JsonRpcError, p as RpcHandler, r as EventHandler, s as JsonRpcErrorCode, t as EventBus, u as RpcApi, v as StreamChunk, x as StreamRequest, y as StreamHandler } from "./types-BnGse9DF.mjs";
|
|
2
|
+
import { STREAM_CHANNEL, asyncGeneratorToStream, createStreamChunk, generateStreamId, isReadableStream, iterableToStream, readStream } from "./stream.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/error.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Create a JSON-RPC error object
|
|
7
|
+
*/
|
|
8
|
+
declare function createJsonRpcError(code: number, message: string, data?: unknown): JsonRpcError;
|
|
9
|
+
/**
|
|
10
|
+
* Predefined error creators
|
|
11
|
+
*/
|
|
12
|
+
declare const errors: {
|
|
13
|
+
parseError: (data?: unknown) => JsonRpcError;
|
|
14
|
+
invalidRequest: (data?: unknown) => JsonRpcError;
|
|
15
|
+
methodNotFound: (method?: string) => JsonRpcError;
|
|
16
|
+
invalidParams: (data?: unknown) => JsonRpcError;
|
|
17
|
+
internalError: (data?: unknown) => JsonRpcError;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Check if an error is a JSON-RPC error
|
|
21
|
+
*/
|
|
22
|
+
declare function isJsonRpcError(error: unknown): error is JsonRpcError;
|
|
23
|
+
/**
|
|
24
|
+
* Convert an Error object to JSON-RPC error
|
|
25
|
+
*/
|
|
26
|
+
declare function errorToJsonRpc(error: unknown): JsonRpcError;
|
|
27
|
+
/**
|
|
28
|
+
* Timeout error class
|
|
29
|
+
*/
|
|
30
|
+
declare class RpcTimeoutError extends Error {
|
|
31
|
+
readonly name: "RpcTimeoutError";
|
|
32
|
+
readonly timeout: number;
|
|
33
|
+
constructor(timeout: number);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if an error is a timeout error
|
|
37
|
+
*/
|
|
38
|
+
declare function isTimeoutError(error: unknown): error is RpcTimeoutError;
|
|
39
|
+
/**
|
|
40
|
+
* Error thrown when queue is full and fullBehavior is 'reject'
|
|
41
|
+
*/
|
|
42
|
+
declare class RpcQueueFullError extends Error {
|
|
43
|
+
readonly name: "RpcQueueFullError";
|
|
44
|
+
readonly currentSize: number;
|
|
45
|
+
readonly maxSize: number;
|
|
46
|
+
constructor(currentSize: number, maxSize: number);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Error thrown when connection to main process is lost
|
|
50
|
+
*/
|
|
51
|
+
declare class RpcConnectionError extends Error {
|
|
52
|
+
readonly name: "RpcConnectionError";
|
|
53
|
+
readonly code?: string;
|
|
54
|
+
constructor(message: string, code?: string);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Error thrown when request is evicted from queue
|
|
58
|
+
*/
|
|
59
|
+
declare class RpcQueueEvictedError extends Error {
|
|
60
|
+
readonly name: "RpcQueueEvictedError";
|
|
61
|
+
readonly reason: "full" | "timeout";
|
|
62
|
+
constructor(reason: "full" | "timeout");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if an error is a queue full error
|
|
66
|
+
*/
|
|
67
|
+
declare function isQueueFullError(error: unknown): error is RpcQueueFullError;
|
|
68
|
+
/**
|
|
69
|
+
* Check if an error is a connection error
|
|
70
|
+
*/
|
|
71
|
+
declare function isConnectionError(error: unknown): error is RpcConnectionError;
|
|
72
|
+
/**
|
|
73
|
+
* Check if an error is a queue evicted error
|
|
74
|
+
*/
|
|
75
|
+
declare function isQueueEvictedError(error: unknown): error is RpcQueueEvictedError;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { EventBus, EventBusEvents, EventHandler, EventSubscriber, ExposeRpcApiOptions, JsonRpcError, JsonRpcErrorCode, JsonRpcRequest, JsonRpcResponse, RpcApi, RpcClientOptions, RpcConnectionError, RpcDebugOptions, RpcHandler, RpcLogEntry, RpcLogger, RpcMethodOptions, RpcQueueEvictedError, RpcQueueFullError, RpcTimeoutError, RpcValidator, STREAM_CHANNEL, StreamChunk, StreamHandler, StreamMethodOptions, StreamRequest, UnderlyingSource, asyncGeneratorToStream, createJsonRpcError, createStreamChunk, errorToJsonRpc, errors, generateStreamId, isConnectionError, isJsonRpcError, isQueueEvictedError, isQueueFullError, isReadableStream, isTimeoutError, iterableToStream, readStream };
|
|
78
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/error.ts"],"mappings":";;;;;AAUA;AAOA;iBAPgB,kBAAA,CAAA,IAAA,UAAA,OAAA,UAAA,IAAA,aAAmE,YAAA;AAAA;AAOnF;;AAPmF,cAOtE,MAAA;EAAA,UAAA,GAAA,IAAA,eACgB,YAAA;EAAA,cAAA,GAAA,IAAA,eAEI,YAAA;EAAA,cAAA,GAAA,MAAA,cAEC,YAAA;EAAA,aAAA,GAAA,IAAA,eAKF,YAAA;EAAA,aAAA,GAAA,IAAA,eAEA,YAAA;AAAA;AAAA;;AAOhC;AAPgC,iBAOhB,cAAA,CAAA,KAAA,YAAA,KAAA,IAAyC,YAAA;AAAA;AAczD;AAuBA;AArCyD,iBAczC,cAAA,CAAA,KAAA,YAAgC,YAAA;AAAA;AAuBhD;AAcA;AArCgD,cAuBnC,eAAA,SAAwB,KAAA;EAAA,SAAA,IAAA;EAAA,SAAA,OAAA;EAAA,YAAA,OAAA;AAAA;AAAA;AAcrC;AAOA;AArBqC,iBAcrB,cAAA,CAAA,KAAA,YAAA,KAAA,IAAyC,eAAA;AAAA;AAOzD;AAgBA;AAvByD,cAO5C,iBAAA,SAA0B,KAAA;EAAA,SAAA,IAAA;EAAA,SAAA,WAAA;EAAA,SAAA,OAAA;EAAA,YAAA,WAAA,UAAA,OAAA;AAAA;AAAA;AAgBvC;AAcA;AA9BuC,cAgB1B,kBAAA,SAA2B,KAAA;EAAA,SAAA,IAAA;EAAA,SAAA,IAAA;EAAA,YAAA,OAAA,UAAA,IAAA;AAAA;AAAA;AAcxC;AAcA;AA5BwC,cAc3B,oBAAA,SAA6B,KAAA;EAAA,SAAA,IAAA;EAAA,SAAA,MAAA;EAAA,YAAA,MAAA;AAAA;AAAA;AAc1C;AAOA;AArB0C,iBAc1B,gBAAA,CAAA,KAAA,YAAA,KAAA,IAA2C,iBAAA;AAAA;AAO3D;AAOA;AAd2D,iBAO3C,iBAAA,CAAA,KAAA,YAAA,KAAA,IAA4C,kBAAA;AAAA;AAO5D;;AAP4D,iBAO5C,mBAAA,CAAA,KAAA,YAAA,KAAA,IAA8C,oBAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as createJsonRpcError, c as isConnectionError, d as isQueueFullError, f as isTimeoutError, i as RpcTimeoutError, l as isJsonRpcError, n as RpcQueueEvictedError, o as errorToJsonRpc, p as JsonRpcErrorCode, r as RpcQueueFullError, s as errors, t as RpcConnectionError, u as isQueueEvictedError } from "./error-xVRu7Lxq.mjs";
|
|
2
|
+
import { STREAM_CHANNEL, asyncGeneratorToStream, createStreamChunk, generateStreamId, isReadableStream, iterableToStream, readStream } from "./stream.mjs";
|
|
3
|
+
|
|
4
|
+
export { JsonRpcErrorCode, RpcConnectionError, RpcQueueEvictedError, RpcQueueFullError, RpcTimeoutError, STREAM_CHANNEL, asyncGeneratorToStream, createJsonRpcError, createStreamChunk, errorToJsonRpc, errors, generateStreamId, isConnectionError, isJsonRpcError, isQueueEvictedError, isQueueFullError, isReadableStream, isTimeoutError, iterableToStream, readStream };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createDebugTracker, isDebugEnabled } from "./debug.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/renderer/internal.ts
|
|
4
|
+
/**
|
|
5
|
+
* Default API name exposed by preload
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_API_NAME = "rpc";
|
|
8
|
+
/**
|
|
9
|
+
* Get the exposed API from window object
|
|
10
|
+
*/
|
|
11
|
+
function getExposedApi(apiName) {
|
|
12
|
+
return window[apiName] ?? null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create debug tracker with optional logger
|
|
16
|
+
*/
|
|
17
|
+
function createTracker(debug, logger) {
|
|
18
|
+
return createDebugTracker(isDebugEnabled(debug), logger ?? (() => {}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { createTracker as n, getExposedApi as r, DEFAULT_API_NAME as t };
|
|
23
|
+
//# sourceMappingURL=internal-BZK_0O3n.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-BZK_0O3n.mjs","names":[],"sources":["../src/renderer/internal.ts"],"sourcesContent":["/**\n * Internal utilities for renderer modules\n */\n\nimport type { RpcLogger } from \"../types.js\";\nimport { isDebugEnabled, createDebugTracker, type DebugTracker } from \"../debug.js\";\n\n/**\n * Default API name exposed by preload\n */\nexport const DEFAULT_API_NAME = \"rpc\";\n\n/**\n * Preload API type\n */\nexport type PreloadApi = {\n call: (method: string, ...params: unknown[]) => Promise<unknown>;\n notify: (method: string, ...params: unknown[]) => void;\n stream: (method: string, ...params: unknown[]) => ReadableStream;\n on?: (eventName: string, callback: (data?: unknown) => void) => () => void;\n off?: (eventName: string, callback?: (data?: unknown) => void) => void;\n once?: (eventName: string, callback: (data?: unknown) => void) => void;\n proxy?: <T>(methodNames?: (keyof T)[]) => T;\n};\n\n/**\n * Get the exposed API from window object\n */\nexport function getExposedApi(apiName: string): PreloadApi | null {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const win = window as any;\n return win[apiName] ?? null;\n}\n\n/**\n * Create debug tracker with optional logger\n */\nexport function createTracker(\n debug: boolean | undefined,\n logger: RpcLogger | undefined,\n): DebugTracker {\n const enabled = isDebugEnabled(debug);\n return createDebugTracker(enabled, logger ?? (() => {}));\n}\n"],"mappings":";;;;;;AAUA,MAAa,mBAAmB;;;;AAkBhC,SAAgB,cAAc,SAAoC;AAGhE,QADY,OACD,YAAY;;;;;AAMzB,SAAgB,cACd,OACA,QACc;AAEd,QAAO,mBADS,eAAe,MAAM,EACF,iBAAiB,IAAI"}
|
package/dist/main.d.mts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { g as RpcMethodOptions, p as RpcHandler, y as StreamHandler } from "./types-BnGse9DF.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/main.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* JSON-RPC Server for Electron Main Process
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { app, BrowserWindow } from 'electron';
|
|
10
|
+
* import { RpcServer } from 'electron-json-rpc/main';
|
|
11
|
+
*
|
|
12
|
+
* const rpc = new RpcServer();
|
|
13
|
+
*
|
|
14
|
+
* rpc.register('add', (a: number, b: number) => a + b);
|
|
15
|
+
* rpc.register('fetchData', async (url: string) => {
|
|
16
|
+
* const response = await fetch(url);
|
|
17
|
+
* return response.json();
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare class RpcServer {
|
|
22
|
+
private readonly methods;
|
|
23
|
+
private readonly streams;
|
|
24
|
+
private readonly eventSubscribers;
|
|
25
|
+
private readonly handleMessageBound;
|
|
26
|
+
private readonly handleStreamMessageBound;
|
|
27
|
+
private readonly handleEventBusMessageBound;
|
|
28
|
+
private ipcMain;
|
|
29
|
+
private webContents;
|
|
30
|
+
private isListening;
|
|
31
|
+
constructor(
|
|
32
|
+
/**
|
|
33
|
+
* Electron instance (for dependency injection in testing)
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
electron?: {
|
|
37
|
+
ipcMain: any;
|
|
38
|
+
webContents: any;
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Register a RPC method
|
|
42
|
+
* @param name - Method name
|
|
43
|
+
* @param handler - Handler function
|
|
44
|
+
* @param options - Optional validation and metadata
|
|
45
|
+
*/
|
|
46
|
+
register(name: string, handler: RpcHandler, options?: RpcMethodOptions): void;
|
|
47
|
+
/**
|
|
48
|
+
* Register a stream method that returns a ReadableStream
|
|
49
|
+
* @param name - Method name
|
|
50
|
+
* @param handler - Handler function that returns a ReadableStream
|
|
51
|
+
* @param options - Optional validation and metadata
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* rpc.registerStream('dataStream', (count: number) => {
|
|
56
|
+
* return new ReadableStream({
|
|
57
|
+
* async start(controller) {
|
|
58
|
+
* for (let i = 0; i < count; i++) {
|
|
59
|
+
* controller.enqueue({ index: i, data: `chunk ${i}` });
|
|
60
|
+
* await new Promise(r => setTimeout(r, 100));
|
|
61
|
+
* }
|
|
62
|
+
* controller.close();
|
|
63
|
+
* }
|
|
64
|
+
* });
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
registerStream(name: string, handler: StreamHandler, options?: RpcMethodOptions): void;
|
|
69
|
+
/**
|
|
70
|
+
* Unregister a RPC method
|
|
71
|
+
*/
|
|
72
|
+
unregister(name: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Check if a method is registered
|
|
75
|
+
*/
|
|
76
|
+
has(name: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Get all registered method names
|
|
79
|
+
*/
|
|
80
|
+
getMethodNames(): string[];
|
|
81
|
+
/**
|
|
82
|
+
* Start listening for IPC messages
|
|
83
|
+
*/
|
|
84
|
+
listen(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Stop listening for IPC messages
|
|
87
|
+
*/
|
|
88
|
+
dispose(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Handle incoming IPC message
|
|
91
|
+
*/
|
|
92
|
+
private handleMessage;
|
|
93
|
+
/**
|
|
94
|
+
* Handle stream-related messages from renderer
|
|
95
|
+
*/
|
|
96
|
+
private handleStreamMessage;
|
|
97
|
+
/**
|
|
98
|
+
* Process a single request and return response
|
|
99
|
+
*/
|
|
100
|
+
private processRequest;
|
|
101
|
+
/**
|
|
102
|
+
* Handle a stream method request
|
|
103
|
+
* Returns stream ID and starts streaming in background
|
|
104
|
+
*/
|
|
105
|
+
private handleStreamRequest;
|
|
106
|
+
/**
|
|
107
|
+
* Execute a stream handler and send chunks to renderer
|
|
108
|
+
*/
|
|
109
|
+
private executeStreamHandler;
|
|
110
|
+
/**
|
|
111
|
+
* Handle event bus messages (subscribe/unsubscribe)
|
|
112
|
+
*/
|
|
113
|
+
private handleEventBusMessage;
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe a window to an event
|
|
116
|
+
*/
|
|
117
|
+
private subscribeToEvent;
|
|
118
|
+
/**
|
|
119
|
+
* Unsubscribe a window from an event
|
|
120
|
+
*/
|
|
121
|
+
private unsubscribeFromEvent;
|
|
122
|
+
/**
|
|
123
|
+
* Publish an event to all subscribed windows
|
|
124
|
+
* @param eventName - The name of the event to publish
|
|
125
|
+
* @param data - Optional data to send with the event
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* rpc.publish('user-updated', { id: '123', name: 'John' });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
publish(eventName: string, data?: unknown): void;
|
|
133
|
+
/**
|
|
134
|
+
* Get current event subscribers
|
|
135
|
+
* @returns Record mapping event names to subscriber counts
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const subscribers = rpc.getEventSubscribers();
|
|
140
|
+
* console.log(subscribers); // { 'user-updated': 2, 'data-changed': 1 }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
getEventSubscribers(): Record<string, number>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Create a new RPC server instance
|
|
147
|
+
*/
|
|
148
|
+
declare function createRpcServer(): RpcServer;
|
|
149
|
+
//#endregion
|
|
150
|
+
export { RpcServer, createRpcServer };
|
|
151
|
+
//# sourceMappingURL=main.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.mts","names":[],"sources":["../src/main.ts"],"mappings":";;;;AA2DA;;;;;;;AAibA;;;;;;;;;cAjba,SAAA;EAAA,iBAAA,OAAA;EAAA,iBAAA,OAAA;EAAA,iBAAA,gBAAA;EAAA,iBAAA,kBAAA;EAAA,iBAAA,wBAAA;EAAA,iBAAA,0BAAA;EAAA,QAAA,OAAA;EAAA,QAAA,WAAA;EAAA,QAAA,WAAA;EAAA;EAAA;;;;EAAA,QAAA;IAAA,OAAA;IAAA,WAAA;EAAA;EAAA;;;;;;EAAA,SAAA,IAAA,UAAA,OAAA,EAmDqB,UAAA,EAAA,OAAA,GAAsB,gBAAA;EAAA;;;;;AA8XxD;;;;;;;;;;;;;;;;EA9XwD,eAAA,IAAA,UAAA,OAAA,EA6BhB,aAAA,EAAA,OAAA,GAAyB,gBAAA;EAAA;;;EAAA,WAAA,IAAA;EAAA;;;EAAA,IAAA,IAAA;EAAA;;;EAAA,eAAA;EAAA;;;EAAA,OAAA;EAAA;;;EAAA,QAAA;EAAA;;;EAAA,QAAA,aAAA;EAAA;;;EAAA,QAAA,mBAAA;EAAA;;;EAAA,QAAA,cAAA;EAAA;;;AAiWjE;EAjWiE,QAAA,mBAAA;EAAA;;;EAAA,QAAA,oBAAA;EAAA;;;EAAA,QAAA,qBAAA;EAAA;;;EAAA,QAAA,gBAAA;EAAA;;;EAAA,QAAA,oBAAA;EAAA;;;AAiWjE;;;;;;;EAjWiE,QAAA,SAAA,UAAA,IAAA;EAAA;;;AAiWjE;;;;;;;EAjWiE,oBAAA,GAqVxC,MAAA;AAAA;AAAA;;AAYzB;AAZyB,iBAYT,eAAA,CAAA,GAAmB,SAAA"}
|
package/dist/main.mjs
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { o as errorToJsonRpc, s as errors } from "./error-xVRu7Lxq.mjs";
|
|
2
|
+
import { STREAM_CHANNEL, createStreamChunk, generateStreamId } from "./stream.mjs";
|
|
3
|
+
import { EVENT_CHANNEL } from "./event.mjs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
|
|
6
|
+
//#region rolldown:runtime
|
|
7
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/main.ts
|
|
11
|
+
/**
|
|
12
|
+
* IPC channel name for JSON-RPC communication
|
|
13
|
+
*/
|
|
14
|
+
const RPC_CHANNEL = "json-rpc";
|
|
15
|
+
const HEALTH_CHECK_METHOD = "__rpc_health__";
|
|
16
|
+
/**
|
|
17
|
+
* JSON-RPC Server for Electron Main Process
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { app, BrowserWindow } from 'electron';
|
|
22
|
+
* import { RpcServer } from 'electron-json-rpc/main';
|
|
23
|
+
*
|
|
24
|
+
* const rpc = new RpcServer();
|
|
25
|
+
*
|
|
26
|
+
* rpc.register('add', (a: number, b: number) => a + b);
|
|
27
|
+
* rpc.register('fetchData', async (url: string) => {
|
|
28
|
+
* const response = await fetch(url);
|
|
29
|
+
* return response.json();
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
var RpcServer = class {
|
|
34
|
+
methods = /* @__PURE__ */ new Map();
|
|
35
|
+
streams = /* @__PURE__ */ new Map();
|
|
36
|
+
eventSubscribers = /* @__PURE__ */ new Map();
|
|
37
|
+
handleMessageBound;
|
|
38
|
+
handleStreamMessageBound;
|
|
39
|
+
handleEventBusMessageBound;
|
|
40
|
+
ipcMain;
|
|
41
|
+
webContents;
|
|
42
|
+
isListening = false;
|
|
43
|
+
constructor(electron) {
|
|
44
|
+
try {
|
|
45
|
+
const e = electron || __require("electron");
|
|
46
|
+
this.ipcMain = e.ipcMain;
|
|
47
|
+
this.webContents = e.webContents;
|
|
48
|
+
} catch {
|
|
49
|
+
throw new Error("Electron not found. Please install electron as a peer dependency.");
|
|
50
|
+
}
|
|
51
|
+
this.handleMessageBound = this.handleMessage.bind(this);
|
|
52
|
+
this.handleStreamMessageBound = this.handleStreamMessage.bind(this);
|
|
53
|
+
this.handleEventBusMessageBound = this.handleEventBusMessage.bind(this);
|
|
54
|
+
if (!this.methods.has(HEALTH_CHECK_METHOD)) this.register(HEALTH_CHECK_METHOD, () => true);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Register a RPC method
|
|
58
|
+
* @param name - Method name
|
|
59
|
+
* @param handler - Handler function
|
|
60
|
+
* @param options - Optional validation and metadata
|
|
61
|
+
*/
|
|
62
|
+
register(name, handler, options) {
|
|
63
|
+
this.methods.set(name, {
|
|
64
|
+
handler,
|
|
65
|
+
validate: options?.validate,
|
|
66
|
+
description: options?.description
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Register a stream method that returns a ReadableStream
|
|
71
|
+
* @param name - Method name
|
|
72
|
+
* @param handler - Handler function that returns a ReadableStream
|
|
73
|
+
* @param options - Optional validation and metadata
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* rpc.registerStream('dataStream', (count: number) => {
|
|
78
|
+
* return new ReadableStream({
|
|
79
|
+
* async start(controller) {
|
|
80
|
+
* for (let i = 0; i < count; i++) {
|
|
81
|
+
* controller.enqueue({ index: i, data: `chunk ${i}` });
|
|
82
|
+
* await new Promise(r => setTimeout(r, 100));
|
|
83
|
+
* }
|
|
84
|
+
* controller.close();
|
|
85
|
+
* }
|
|
86
|
+
* });
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
registerStream(name, handler, options) {
|
|
91
|
+
this.methods.set(name, {
|
|
92
|
+
handler,
|
|
93
|
+
validate: options?.validate,
|
|
94
|
+
description: options?.description,
|
|
95
|
+
isStream: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Unregister a RPC method
|
|
100
|
+
*/
|
|
101
|
+
unregister(name) {
|
|
102
|
+
this.methods.delete(name);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if a method is registered
|
|
106
|
+
*/
|
|
107
|
+
has(name) {
|
|
108
|
+
return this.methods.has(name);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get all registered method names
|
|
112
|
+
*/
|
|
113
|
+
getMethodNames() {
|
|
114
|
+
return Array.from(this.methods.keys());
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Start listening for IPC messages
|
|
118
|
+
*/
|
|
119
|
+
listen() {
|
|
120
|
+
if (this.isListening) return;
|
|
121
|
+
this.ipcMain.on(RPC_CHANNEL, this.handleMessageBound);
|
|
122
|
+
this.ipcMain.on(STREAM_CHANNEL, this.handleStreamMessageBound);
|
|
123
|
+
this.ipcMain.on(EVENT_CHANNEL, this.handleEventBusMessageBound);
|
|
124
|
+
this.isListening = true;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Stop listening for IPC messages
|
|
128
|
+
*/
|
|
129
|
+
dispose() {
|
|
130
|
+
if (!this.isListening) return;
|
|
131
|
+
this.ipcMain.removeListener(RPC_CHANNEL, this.handleMessageBound);
|
|
132
|
+
this.ipcMain.removeListener(STREAM_CHANNEL, this.handleStreamMessageBound);
|
|
133
|
+
this.ipcMain.removeListener(EVENT_CHANNEL, this.handleEventBusMessageBound);
|
|
134
|
+
this.isListening = false;
|
|
135
|
+
this.methods.clear();
|
|
136
|
+
this.streams.clear();
|
|
137
|
+
this.eventSubscribers.clear();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Handle incoming IPC message
|
|
141
|
+
*/
|
|
142
|
+
async handleMessage(_event, request) {
|
|
143
|
+
const sender = _event.sender;
|
|
144
|
+
const response = await this.processRequest(request, sender);
|
|
145
|
+
if (request.id !== void 0 && request.id !== null) _event.reply?.(RPC_CHANNEL, response);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Handle stream-related messages from renderer
|
|
149
|
+
*/
|
|
150
|
+
handleStreamMessage(_event, chunk) {
|
|
151
|
+
const { streamId, type } = chunk;
|
|
152
|
+
if (type === "end" || type === "error") this.streams.delete(String(streamId));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Process a single request and return response
|
|
156
|
+
*/
|
|
157
|
+
async processRequest(request, sender) {
|
|
158
|
+
const { id, method, params } = request;
|
|
159
|
+
if (!method || typeof method !== "string") return {
|
|
160
|
+
jsonrpc: "2.0",
|
|
161
|
+
id: id ?? null,
|
|
162
|
+
error: errors.invalidRequest()
|
|
163
|
+
};
|
|
164
|
+
const storedMethod = this.methods.get(method);
|
|
165
|
+
if (!storedMethod) return {
|
|
166
|
+
jsonrpc: "2.0",
|
|
167
|
+
id: id ?? null,
|
|
168
|
+
error: errors.methodNotFound(method)
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
let args;
|
|
172
|
+
if (params === void 0) args = [];
|
|
173
|
+
else if (Array.isArray(params)) args = params;
|
|
174
|
+
else args = [params];
|
|
175
|
+
if (storedMethod.validate) try {
|
|
176
|
+
await storedMethod.validate(args);
|
|
177
|
+
} catch (validationError) {
|
|
178
|
+
return {
|
|
179
|
+
jsonrpc: "2.0",
|
|
180
|
+
id: id ?? null,
|
|
181
|
+
error: errors.invalidParams(validationError instanceof Error ? validationError.message : void 0)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (storedMethod.isStream) return this.handleStreamRequest(id, method, args, storedMethod, sender);
|
|
185
|
+
const result = await storedMethod.handler(...args);
|
|
186
|
+
return {
|
|
187
|
+
jsonrpc: "2.0",
|
|
188
|
+
id: id ?? null,
|
|
189
|
+
result
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
jsonrpc: "2.0",
|
|
194
|
+
id: id ?? null,
|
|
195
|
+
error: errorToJsonRpc(error)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Handle a stream method request
|
|
201
|
+
* Returns stream ID and starts streaming in background
|
|
202
|
+
*/
|
|
203
|
+
async handleStreamRequest(id, _method, args, storedMethod, sender) {
|
|
204
|
+
const streamId = generateStreamId();
|
|
205
|
+
const sendChunk = sender ? (channel, data) => sender.send(channel, data) : (channel, data) => {
|
|
206
|
+
const allWindows = this.webContents.getAllWebContents();
|
|
207
|
+
for (const wc of allWindows) wc.send(channel, data);
|
|
208
|
+
};
|
|
209
|
+
this.streams.set(streamId, {
|
|
210
|
+
streamId,
|
|
211
|
+
sender: sendChunk
|
|
212
|
+
});
|
|
213
|
+
this.executeStreamHandler(streamId, storedMethod.handler, args, sendChunk).catch((error) => {
|
|
214
|
+
sendChunk(STREAM_CHANNEL, createStreamChunk(streamId, "error", errorToJsonRpc(error)));
|
|
215
|
+
this.streams.delete(streamId);
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
jsonrpc: "2.0",
|
|
219
|
+
id: id ?? null,
|
|
220
|
+
result: { streamId }
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Execute a stream handler and send chunks to renderer
|
|
225
|
+
*/
|
|
226
|
+
async executeStreamHandler(streamId, handler, args, sendChunk) {
|
|
227
|
+
const result = await handler(...args);
|
|
228
|
+
if (result instanceof ReadableStream) {
|
|
229
|
+
const reader = result.getReader();
|
|
230
|
+
try {
|
|
231
|
+
while (true) {
|
|
232
|
+
const { done, value } = await reader.read();
|
|
233
|
+
if (done) break;
|
|
234
|
+
sendChunk(STREAM_CHANNEL, createStreamChunk(streamId, "chunk", value));
|
|
235
|
+
}
|
|
236
|
+
sendChunk(STREAM_CHANNEL, createStreamChunk(streamId, "end"));
|
|
237
|
+
} catch (error) {
|
|
238
|
+
sendChunk(STREAM_CHANNEL, createStreamChunk(streamId, "error", errorToJsonRpc(error)));
|
|
239
|
+
} finally {
|
|
240
|
+
reader.releaseLock();
|
|
241
|
+
this.streams.delete(streamId);
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
sendChunk(STREAM_CHANNEL, createStreamChunk(streamId, "chunk", result));
|
|
245
|
+
sendChunk(STREAM_CHANNEL, createStreamChunk(streamId, "end"));
|
|
246
|
+
this.streams.delete(streamId);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Handle event bus messages (subscribe/unsubscribe)
|
|
251
|
+
*/
|
|
252
|
+
handleEventBusMessage(_event, message) {
|
|
253
|
+
const { type, eventName } = message;
|
|
254
|
+
const sender = _event.sender;
|
|
255
|
+
if (!sender) return;
|
|
256
|
+
const windowId = sender.id;
|
|
257
|
+
switch (type) {
|
|
258
|
+
case "subscribe":
|
|
259
|
+
this.subscribeToEvent(eventName, windowId);
|
|
260
|
+
break;
|
|
261
|
+
case "unsubscribe":
|
|
262
|
+
this.unsubscribeFromEvent(eventName, windowId);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Subscribe a window to an event
|
|
268
|
+
*/
|
|
269
|
+
subscribeToEvent(eventName, windowId) {
|
|
270
|
+
if (!this.eventSubscribers.has(eventName)) this.eventSubscribers.set(eventName, /* @__PURE__ */ new Set());
|
|
271
|
+
this.eventSubscribers.get(eventName).add(windowId);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Unsubscribe a window from an event
|
|
275
|
+
*/
|
|
276
|
+
unsubscribeFromEvent(eventName, windowId) {
|
|
277
|
+
const subscribers = this.eventSubscribers.get(eventName);
|
|
278
|
+
if (subscribers) {
|
|
279
|
+
subscribers.delete(windowId);
|
|
280
|
+
if (subscribers.size === 0) this.eventSubscribers.delete(eventName);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Publish an event to all subscribed windows
|
|
285
|
+
* @param eventName - The name of the event to publish
|
|
286
|
+
* @param data - Optional data to send with the event
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* rpc.publish('user-updated', { id: '123', name: 'John' });
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
publish(eventName, data) {
|
|
294
|
+
const subscribers = this.eventSubscribers.get(eventName);
|
|
295
|
+
if (!subscribers || subscribers.size === 0) return;
|
|
296
|
+
const message = {
|
|
297
|
+
type: "event",
|
|
298
|
+
eventName,
|
|
299
|
+
data
|
|
300
|
+
};
|
|
301
|
+
const allWindows = this.webContents.getAllWebContents();
|
|
302
|
+
for (const wc of allWindows) if (subscribers.has(wc.id)) wc.send(EVENT_CHANNEL, message);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get current event subscribers
|
|
306
|
+
* @returns Record mapping event names to subscriber counts
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* const subscribers = rpc.getEventSubscribers();
|
|
311
|
+
* console.log(subscribers); // { 'user-updated': 2, 'data-changed': 1 }
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
getEventSubscribers() {
|
|
315
|
+
const result = {};
|
|
316
|
+
for (const [eventName, subscribers] of this.eventSubscribers.entries()) result[eventName] = subscribers.size;
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Create a new RPC server instance
|
|
322
|
+
*/
|
|
323
|
+
function createRpcServer() {
|
|
324
|
+
return new RpcServer();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
export { RpcServer, createRpcServer };
|
|
329
|
+
//# sourceMappingURL=main.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.mjs","names":[],"sources":["../src/main.ts"],"sourcesContent":["/**\n * JSON-RPC Server for Electron Main Process\n */\n\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n RpcHandler,\n RpcMethodOptions,\n RpcValidator,\n StreamHandler,\n StreamChunk,\n} from \"./types.js\";\nimport { errors, errorToJsonRpc } from \"./error.js\";\nimport { generateStreamId, createStreamChunk, STREAM_CHANNEL } from \"./stream.js\";\nimport { EVENT_CHANNEL, type EventMessage } from \"./event.js\";\n\n/**\n * Stored method with metadata\n */\ninterface StoredMethod {\n handler: RpcHandler;\n validate?: RpcValidator;\n description?: string;\n isStream?: boolean;\n}\n\n/**\n * Active stream state\n */\ninterface ActiveStream {\n streamId: string;\n sender: (channel: string, data: unknown) => void;\n}\n\n/**\n * IPC channel name for JSON-RPC communication\n */\nconst RPC_CHANNEL = \"json-rpc\";\n\nconst HEALTH_CHECK_METHOD = \"__rpc_health__\";\n\n/**\n * JSON-RPC Server for Electron Main Process\n *\n * @example\n * ```typescript\n * import { app, BrowserWindow } from 'electron';\n * import { RpcServer } from 'electron-json-rpc/main';\n *\n * const rpc = new RpcServer();\n *\n * rpc.register('add', (a: number, b: number) => a + b);\n * rpc.register('fetchData', async (url: string) => {\n * const response = await fetch(url);\n * return response.json();\n * });\n * ```\n */\nexport class RpcServer {\n private readonly methods = new Map<string, StoredMethod>();\n private readonly streams = new Map<string, ActiveStream>();\n private readonly eventSubscribers = new Map<string, Set<number>>(); // eventName -> Set of window IDs\n private readonly handleMessageBound: (event: unknown, request: JsonRpcRequest) => void;\n private readonly handleStreamMessageBound: (event: unknown, chunk: StreamChunk) => void;\n private readonly handleEventBusMessageBound: (event: unknown, message: EventMessage) => void;\n private ipcMain: any;\n private webContents: any;\n private isListening = false;\n\n constructor(\n /**\n * Electron instance (for dependency injection in testing)\n * @internal\n */\n electron?: {\n ipcMain: any;\n webContents: any;\n },\n ) {\n // Electron is a peer dependency, we'll get it at runtime\n // Or use provided instance (for testing)\n try {\n const e =\n electron ||\n (() => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n return require(\"electron\");\n })();\n this.ipcMain = e.ipcMain;\n this.webContents = e.webContents;\n } catch {\n throw new Error(\"Electron not found. Please install electron as a peer dependency.\");\n }\n\n this.handleMessageBound = this.handleMessage.bind(this);\n this.handleStreamMessageBound = this.handleStreamMessage.bind(this);\n this.handleEventBusMessageBound = this.handleEventBusMessage.bind(this);\n\n if (!this.methods.has(HEALTH_CHECK_METHOD)) {\n this.register(HEALTH_CHECK_METHOD, () => true);\n }\n }\n\n /**\n * Register a RPC method\n * @param name - Method name\n * @param handler - Handler function\n * @param options - Optional validation and metadata\n */\n register(name: string, handler: RpcHandler, options?: RpcMethodOptions): void {\n this.methods.set(name, {\n handler,\n validate: options?.validate,\n description: options?.description,\n });\n }\n\n /**\n * Register a stream method that returns a ReadableStream\n * @param name - Method name\n * @param handler - Handler function that returns a ReadableStream\n * @param options - Optional validation and metadata\n *\n * @example\n * ```typescript\n * rpc.registerStream('dataStream', (count: number) => {\n * return new ReadableStream({\n * async start(controller) {\n * for (let i = 0; i < count; i++) {\n * controller.enqueue({ index: i, data: `chunk ${i}` });\n * await new Promise(r => setTimeout(r, 100));\n * }\n * controller.close();\n * }\n * });\n * });\n * ```\n */\n registerStream(name: string, handler: StreamHandler, options?: RpcMethodOptions): void {\n this.methods.set(name, {\n handler: handler as RpcHandler,\n validate: options?.validate,\n description: options?.description,\n isStream: true,\n });\n }\n\n /**\n * Unregister a RPC method\n */\n unregister(name: string): void {\n this.methods.delete(name);\n }\n\n /**\n * Check if a method is registered\n */\n has(name: string): boolean {\n return this.methods.has(name);\n }\n\n /**\n * Get all registered method names\n */\n getMethodNames(): string[] {\n return Array.from(this.methods.keys());\n }\n\n /**\n * Start listening for IPC messages\n */\n listen(): void {\n if (this.isListening) {\n return;\n }\n\n this.ipcMain.on(RPC_CHANNEL, this.handleMessageBound);\n this.ipcMain.on(STREAM_CHANNEL, this.handleStreamMessageBound);\n this.ipcMain.on(EVENT_CHANNEL, this.handleEventBusMessageBound);\n this.isListening = true;\n }\n\n /**\n * Stop listening for IPC messages\n */\n dispose(): void {\n if (!this.isListening) {\n return;\n }\n\n this.ipcMain.removeListener(RPC_CHANNEL, this.handleMessageBound);\n this.ipcMain.removeListener(STREAM_CHANNEL, this.handleStreamMessageBound);\n this.ipcMain.removeListener(EVENT_CHANNEL, this.handleEventBusMessageBound);\n this.isListening = false;\n this.methods.clear();\n this.streams.clear();\n this.eventSubscribers.clear();\n }\n\n /**\n * Handle incoming IPC message\n */\n private async handleMessage(_event: unknown, request: JsonRpcRequest): Promise<void> {\n const sender = (_event as { sender?: { send: (channel: string, data: unknown) => void } })\n .sender;\n\n const response = await this.processRequest(request, sender);\n\n // Only send response for non-notifications (requests with id)\n if (request.id !== undefined && request.id !== null) {\n // Send response via the same event's reply method\n const event = _event as {\n reply?: (channel: string, data: unknown) => void;\n };\n event.reply?.(RPC_CHANNEL, response);\n }\n }\n\n /**\n * Handle stream-related messages from renderer\n */\n private handleStreamMessage(_event: unknown, chunk: StreamChunk): void {\n const { streamId, type } = chunk;\n\n if (type === \"end\" || type === \"error\") {\n // Clean up stream state\n this.streams.delete(String(streamId));\n }\n }\n\n /**\n * Process a single request and return response\n */\n private async processRequest(\n request: JsonRpcRequest,\n sender?: { send: (channel: string, data: unknown) => void },\n ): Promise<JsonRpcResponse> {\n const { id, method, params } = request;\n\n // Validate request\n if (!method || typeof method !== \"string\") {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: errors.invalidRequest(),\n };\n }\n\n // Check if method exists\n const storedMethod = this.methods.get(method);\n if (!storedMethod) {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: errors.methodNotFound(method),\n };\n }\n\n try {\n // Normalize params to array\n let args: unknown[];\n if (params === undefined) {\n args = [];\n } else if (Array.isArray(params)) {\n args = params;\n } else {\n // Named params - pass as single object\n args = [params];\n }\n\n // Run validator if provided\n if (storedMethod.validate) {\n try {\n await storedMethod.validate(args);\n } catch (validationError) {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: errors.invalidParams(\n validationError instanceof Error ? validationError.message : undefined,\n ),\n };\n }\n }\n\n // Handle stream methods\n if (storedMethod.isStream) {\n return this.handleStreamRequest(id, method, args, storedMethod, sender);\n }\n\n // Execute regular handler\n const result = await storedMethod.handler(...args);\n\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n result,\n };\n } catch (error) {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: errorToJsonRpc(error),\n };\n }\n }\n\n /**\n * Handle a stream method request\n * Returns stream ID and starts streaming in background\n */\n private async handleStreamRequest(\n id: string | number | null | undefined,\n _method: string,\n args: unknown[],\n storedMethod: StoredMethod,\n sender?: { send: (channel: string, data: unknown) => void },\n ): Promise<JsonRpcResponse> {\n const streamId = generateStreamId();\n\n // Prefer responding to the requesting sender; fallback to all windows\n const sendChunk = sender\n ? (channel: string, data: unknown) => sender.send(channel, data)\n : (channel: string, data: unknown) => {\n const allWindows = this.webContents.getAllWebContents();\n for (const wc of allWindows) {\n wc.send(channel, data);\n }\n };\n\n this.streams.set(streamId, { streamId, sender: sendChunk });\n\n // Start streaming in background\n this.executeStreamHandler(streamId, storedMethod.handler, args, sendChunk).catch((error) => {\n const errorChunk = createStreamChunk(streamId, \"error\", errorToJsonRpc(error));\n sendChunk(STREAM_CHANNEL, errorChunk);\n this.streams.delete(streamId);\n });\n\n // Return stream ID immediately\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n result: { streamId },\n };\n }\n\n /**\n * Execute a stream handler and send chunks to renderer\n */\n private async executeStreamHandler(\n streamId: string,\n handler: RpcHandler,\n args: unknown[],\n sendChunk: (channel: string, data: unknown) => void,\n ): Promise<void> {\n const result = await handler(...args);\n\n if (result instanceof ReadableStream) {\n const reader = result.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = createStreamChunk(streamId, \"chunk\", value);\n sendChunk(STREAM_CHANNEL, chunk);\n }\n\n // Send end chunk\n const endChunk = createStreamChunk(streamId, \"end\");\n sendChunk(STREAM_CHANNEL, endChunk);\n } catch (error) {\n const errorChunk = createStreamChunk(streamId, \"error\", errorToJsonRpc(error));\n sendChunk(STREAM_CHANNEL, errorChunk);\n } finally {\n reader.releaseLock();\n this.streams.delete(streamId);\n }\n } else {\n // Non-stream result, send as single chunk and end\n const chunk = createStreamChunk(streamId, \"chunk\", result);\n sendChunk(STREAM_CHANNEL, chunk);\n\n const endChunk = createStreamChunk(streamId, \"end\");\n sendChunk(STREAM_CHANNEL, endChunk);\n\n this.streams.delete(streamId);\n }\n }\n\n /**\n * Handle event bus messages (subscribe/unsubscribe)\n */\n private handleEventBusMessage(_event: unknown, message: EventMessage): void {\n const { type, eventName } = message;\n const sender = (_event as { sender?: { id: number } }).sender;\n\n if (!sender) {\n return;\n }\n\n const windowId = sender.id;\n\n switch (type) {\n case \"subscribe\":\n this.subscribeToEvent(eventName, windowId);\n break;\n case \"unsubscribe\":\n this.unsubscribeFromEvent(eventName, windowId);\n break;\n }\n }\n\n /**\n * Subscribe a window to an event\n */\n private subscribeToEvent(eventName: string, windowId: number): void {\n if (!this.eventSubscribers.has(eventName)) {\n this.eventSubscribers.set(eventName, new Set());\n }\n this.eventSubscribers.get(eventName)!.add(windowId);\n }\n\n /**\n * Unsubscribe a window from an event\n */\n private unsubscribeFromEvent(eventName: string, windowId: number): void {\n const subscribers = this.eventSubscribers.get(eventName);\n if (subscribers) {\n subscribers.delete(windowId);\n if (subscribers.size === 0) {\n this.eventSubscribers.delete(eventName);\n }\n }\n }\n\n /**\n * Publish an event to all subscribed windows\n * @param eventName - The name of the event to publish\n * @param data - Optional data to send with the event\n *\n * @example\n * ```typescript\n * rpc.publish('user-updated', { id: '123', name: 'John' });\n * ```\n */\n publish(eventName: string, data?: unknown): void {\n const subscribers = this.eventSubscribers.get(eventName);\n if (!subscribers || subscribers.size === 0) {\n return; // No subscribers, nothing to do\n }\n\n const message: EventMessage = {\n type: \"event\",\n eventName,\n data,\n };\n\n // Send to all subscribed windows\n const allWindows = this.webContents.getAllWebContents();\n for (const wc of allWindows) {\n if (subscribers.has(wc.id)) {\n wc.send(EVENT_CHANNEL, message);\n }\n }\n }\n\n /**\n * Get current event subscribers\n * @returns Record mapping event names to subscriber counts\n *\n * @example\n * ```typescript\n * const subscribers = rpc.getEventSubscribers();\n * console.log(subscribers); // { 'user-updated': 2, 'data-changed': 1 }\n * ```\n */\n getEventSubscribers(): Record<string, number> {\n const result: Record<string, number> = {};\n for (const [eventName, subscribers] of this.eventSubscribers.entries()) {\n result[eventName] = subscribers.size;\n }\n return result;\n }\n}\n\n/**\n * Create a new RPC server instance\n */\nexport function createRpcServer(): RpcServer {\n return new RpcServer();\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { a as ExposeRpcApiOptions } from "./types-BnGse9DF.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/preload.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Expose an RPC API to the renderer process via contextBridge
|
|
6
|
+
*/
|
|
7
|
+
declare function exposeRpcApi(options: ExposeRpcApiOptions): void;
|
|
8
|
+
/**
|
|
9
|
+
* Create an RPC client for use in preload scripts
|
|
10
|
+
* (without exposing to renderer)
|
|
11
|
+
*/
|
|
12
|
+
declare function createPreloadClient(ipcRenderer: ExposeRpcApiOptions["ipcRenderer"], timeout?: number): {
|
|
13
|
+
call: (method: string, ...params: unknown[]) => Promise<unknown>;
|
|
14
|
+
notify: (method: string, ...params: unknown[]) => void;
|
|
15
|
+
stream: (method: string, ...params: unknown[]) => ReadableStream;
|
|
16
|
+
on: (eventName: string, callback: (data?: unknown) => void) => () => void;
|
|
17
|
+
off: (eventName: string, callback?: (data?: unknown) => void) => void;
|
|
18
|
+
once: (eventName: string, callback: (data?: unknown) => void) => void;
|
|
19
|
+
dispose: () => void;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { createPreloadClient, exposeRpcApi };
|
|
23
|
+
//# sourceMappingURL=preload.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preload.d.mts","names":[],"sources":["../src/preload.ts"],"mappings":";;;;AAkXA;AAoBA;iBApBgB,YAAA,CAAA,OAAA,EAAsB,mBAAA;AAAA;AAoBtC;;;AApBsC,iBAoBtB,mBAAA,CAAA,WAAA,EACD,mBAAA,iBAAA,OAAA;EAAA,IAAA,GAAA,MAAA,aAAA,MAAA,gBAGmC,OAAA;EAAA,MAAA,GAAA,MAAA,aAAA,MAAA;EAAA,MAAA,GAAA,MAAA,aAAA,MAAA,gBAEE,cAAA;EAAA,EAAA,GAAA,SAAA,UAAA,QAAA,GAAA,IAAA;EAAA,GAAA,GAAA,SAAA,UAAA,QAAA,IAAA,IAAA;EAAA,IAAA,GAAA,SAAA,UAAA,QAAA,GAAA,IAAA;EAAA,OAAA;AAAA"}
|