electron-buff 1.0.4 → 1.0.6
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 +1 -7
- package/dist/xpc/main/index.d.mts +50 -1
- package/dist/xpc/main/index.d.ts +50 -1
- package/dist/xpc/main/index.js +49 -4
- package/dist/xpc/main/index.js.map +1 -1
- package/dist/xpc/main/index.mjs +48 -5
- package/dist/xpc/main/index.mjs.map +1 -1
- package/dist/xpc/preload/index.d.mts +50 -1
- package/dist/xpc/preload/index.d.ts +50 -1
- package/dist/xpc/preload/index.js +46 -1
- package/dist/xpc/preload/index.js.map +1 -1
- package/dist/xpc/preload/index.mjs +45 -2
- package/dist/xpc/preload/index.mjs.map +1 -1
- package/dist/xpc/renderer/index.d.mts +50 -1
- package/dist/xpc/renderer/index.d.ts +50 -1
- package/dist/xpc/renderer/index.js +45 -0
- package/dist/xpc/renderer/index.js.map +1 -1
- package/dist/xpc/renderer/index.mjs +44 -1
- package/dist/xpc/renderer/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,18 +14,12 @@ Electron enhancement utilities for electron-vite projects.
|
|
|
14
14
|
yarn add electron-buff
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## TODO
|
|
18
|
-
|
|
19
|
-
1. 通过 Proxy 实现 XPC 调用:发送通过调用 class 的 function 实现,监听通过 class 的函数名在实例化过程中自动注册,无需手动调用 `handle`/`send` 并传入字符串句柄,可实现代码自动补全。
|
|
20
|
-
2. 重复监听处理优化:对同一 handleName 的重复注册进行检测与策略处理(覆盖、警告或抛错)。
|
|
21
|
-
3. 多进程广播支持:支持向多个已注册的进程同时广播消息。
|
|
22
|
-
|
|
23
17
|
|
|
24
18
|
## Module Overview
|
|
25
19
|
|
|
26
20
|
### XPC — Cross-Process Communication
|
|
27
21
|
|
|
28
|
-
XPC is a bidirectional async/await RPC module for **Electron** applications. Unlike Electron's built-in `ipcRenderer.invoke` / `ipcMain.handle`, which only supports renderer-to-main request–response, XPC enables **any process** (renderer or main) to call handlers registered in **any other process** with full `async/await` semantics — including renderer-to-renderer, main-to-renderer, and main-to-main invocations.
|
|
22
|
+
XPC is a bidirectional **async/await** RPC module for **Electron** applications. Unlike Electron's built-in `ipcRenderer.invoke` / `ipcMain.handle`, which only supports renderer-to-main request–response, XPC enables **any process** (renderer or main) to call handlers registered in **any other process** with full `async/await` semantics — including renderer-to-renderer, main-to-renderer, and main-to-main invocations.
|
|
29
23
|
|
|
30
24
|
**Advantages:**
|
|
31
25
|
|
|
@@ -77,4 +77,53 @@ declare class XpcTask implements XpcPayload {
|
|
|
77
77
|
toPayload(): XpcPayload;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Base class for main-process xpc handlers.
|
|
82
|
+
* Subclass this and define async methods — they will be auto-registered
|
|
83
|
+
* as xpc handlers with channel `xpc:ClassName/methodName`.
|
|
84
|
+
*
|
|
85
|
+
* Example:
|
|
86
|
+
* ```ts
|
|
87
|
+
* class UserTable extends XpcMainHandler {
|
|
88
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
89
|
+
* }
|
|
90
|
+
* const userTable = new UserTable();
|
|
91
|
+
* // auto-registers handler for 'xpc:UserTable/getUserList'
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare class XpcMainHandler {
|
|
95
|
+
constructor();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Helper: checks if a function type has at most 1 parameter.
|
|
100
|
+
* Returns the function type itself if valid, `never` otherwise.
|
|
101
|
+
*/
|
|
102
|
+
type AssertSingleParam<F> = F extends () => any ? F : F extends (p: any) => any ? F extends (p: any, q: any, ...rest: any[]) => any ? never : F : never;
|
|
103
|
+
/**
|
|
104
|
+
* Utility type: extracts the method signatures from a handler class,
|
|
105
|
+
* turning each method into an emitter-compatible signature.
|
|
106
|
+
* Methods with 2+ parameters are mapped to `never`, causing a compile error on use.
|
|
107
|
+
*/
|
|
108
|
+
type XpcEmitterOf<T> = {
|
|
109
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: AssertSingleParam<T[K]> extends never ? never : T[K] extends (params: infer P) => any ? (params: P) => Promise<any> : () => Promise<any>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a type-safe emitter proxy for a main-process xpc handler.
|
|
114
|
+
* The emitter mirrors the handler's method signatures, but each call
|
|
115
|
+
* sends a message via xpcMain.send() to `xpc:ClassName/methodName`.
|
|
116
|
+
*
|
|
117
|
+
* Example:
|
|
118
|
+
* ```ts
|
|
119
|
+
* class UserTable extends XpcMainHandler {
|
|
120
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
121
|
+
* }
|
|
122
|
+
* const userTableEmitter = createXpcMainEmitter<UserTable>('UserTable');
|
|
123
|
+
* const list = await userTableEmitter.getUserList({ page: 1 });
|
|
124
|
+
* // sends to 'xpc:UserTable/getUserList'
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
declare const createXpcMainEmitter: <T>(className: string) => XpcEmitterOf<T>;
|
|
128
|
+
|
|
129
|
+
export { type XpcEmitterOf, XpcMainHandler, type XpcPayload, XpcTask, createXpcMainEmitter, xpcCenter, xpcMain };
|
package/dist/xpc/main/index.d.ts
CHANGED
|
@@ -77,4 +77,53 @@ declare class XpcTask implements XpcPayload {
|
|
|
77
77
|
toPayload(): XpcPayload;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Base class for main-process xpc handlers.
|
|
82
|
+
* Subclass this and define async methods — they will be auto-registered
|
|
83
|
+
* as xpc handlers with channel `xpc:ClassName/methodName`.
|
|
84
|
+
*
|
|
85
|
+
* Example:
|
|
86
|
+
* ```ts
|
|
87
|
+
* class UserTable extends XpcMainHandler {
|
|
88
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
89
|
+
* }
|
|
90
|
+
* const userTable = new UserTable();
|
|
91
|
+
* // auto-registers handler for 'xpc:UserTable/getUserList'
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare class XpcMainHandler {
|
|
95
|
+
constructor();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Helper: checks if a function type has at most 1 parameter.
|
|
100
|
+
* Returns the function type itself if valid, `never` otherwise.
|
|
101
|
+
*/
|
|
102
|
+
type AssertSingleParam<F> = F extends () => any ? F : F extends (p: any) => any ? F extends (p: any, q: any, ...rest: any[]) => any ? never : F : never;
|
|
103
|
+
/**
|
|
104
|
+
* Utility type: extracts the method signatures from a handler class,
|
|
105
|
+
* turning each method into an emitter-compatible signature.
|
|
106
|
+
* Methods with 2+ parameters are mapped to `never`, causing a compile error on use.
|
|
107
|
+
*/
|
|
108
|
+
type XpcEmitterOf<T> = {
|
|
109
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: AssertSingleParam<T[K]> extends never ? never : T[K] extends (params: infer P) => any ? (params: P) => Promise<any> : () => Promise<any>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a type-safe emitter proxy for a main-process xpc handler.
|
|
114
|
+
* The emitter mirrors the handler's method signatures, but each call
|
|
115
|
+
* sends a message via xpcMain.send() to `xpc:ClassName/methodName`.
|
|
116
|
+
*
|
|
117
|
+
* Example:
|
|
118
|
+
* ```ts
|
|
119
|
+
* class UserTable extends XpcMainHandler {
|
|
120
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
121
|
+
* }
|
|
122
|
+
* const userTableEmitter = createXpcMainEmitter<UserTable>('UserTable');
|
|
123
|
+
* const list = await userTableEmitter.getUserList({ page: 1 });
|
|
124
|
+
* // sends to 'xpc:UserTable/getUserList'
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
declare const createXpcMainEmitter: <T>(className: string) => XpcEmitterOf<T>;
|
|
128
|
+
|
|
129
|
+
export { type XpcEmitterOf, XpcMainHandler, type XpcPayload, XpcTask, createXpcMainEmitter, xpcCenter, xpcMain };
|
package/dist/xpc/main/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var electron = require('electron');
|
|
4
4
|
var rigFoundation = require('rig-foundation');
|
|
5
5
|
|
|
6
|
-
// src/xpc/main/
|
|
6
|
+
// src/xpc/main/xpcCenter.helper.ts
|
|
7
7
|
var XpcTask = class {
|
|
8
8
|
constructor(payload) {
|
|
9
9
|
this.id = payload.id;
|
|
@@ -33,7 +33,7 @@ var XpcTask = class {
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
// src/xpc/main/
|
|
36
|
+
// src/xpc/main/xpcId.helper.ts
|
|
37
37
|
var prefix = Math.random().toString(36).slice(2, 8);
|
|
38
38
|
var counter = 0;
|
|
39
39
|
var generateXpcId = () => {
|
|
@@ -72,7 +72,7 @@ var PathMainHelper = class {
|
|
|
72
72
|
};
|
|
73
73
|
var pathMainHelper = new PathMainHelper();
|
|
74
74
|
|
|
75
|
-
// src/xpc/main/
|
|
75
|
+
// src/xpc/main/xpcMain.helper.ts
|
|
76
76
|
var XpcMain = class {
|
|
77
77
|
constructor() {
|
|
78
78
|
this.handlers = /* @__PURE__ */ new Map();
|
|
@@ -102,7 +102,7 @@ var XpcMain = class {
|
|
|
102
102
|
};
|
|
103
103
|
var xpcMain = new XpcMain();
|
|
104
104
|
|
|
105
|
-
// src/xpc/main/
|
|
105
|
+
// src/xpc/main/xpcCenter.helper.ts
|
|
106
106
|
var XPC_REGISTER = "__xpc_register__";
|
|
107
107
|
var XPC_EXEC = "__xpc_exec__";
|
|
108
108
|
var XPC_FINISH = "__xpc_finish__";
|
|
@@ -178,7 +178,52 @@ var XpcCenter = class {
|
|
|
178
178
|
};
|
|
179
179
|
var xpcCenter = new XpcCenter();
|
|
180
180
|
|
|
181
|
+
// src/xpc/shared/xpcHandler.type.ts
|
|
182
|
+
var XPC_HANDLER_PREFIX = "xpc:";
|
|
183
|
+
var buildXpcChannel = (className, methodName) => {
|
|
184
|
+
return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;
|
|
185
|
+
};
|
|
186
|
+
var getHandlerMethodNames = (prototype) => {
|
|
187
|
+
const names = [];
|
|
188
|
+
const keys = Object.getOwnPropertyNames(prototype);
|
|
189
|
+
for (const key of keys) {
|
|
190
|
+
if (key === "constructor") continue;
|
|
191
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
192
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
193
|
+
names.push(key);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return names;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/xpc/main/xpcMain.handler.ts
|
|
200
|
+
var XpcMainHandler = class {
|
|
201
|
+
constructor() {
|
|
202
|
+
const className = this.constructor.name;
|
|
203
|
+
const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));
|
|
204
|
+
for (const methodName of methodNames) {
|
|
205
|
+
const channel = buildXpcChannel(className, methodName);
|
|
206
|
+
const method = this[methodName].bind(this);
|
|
207
|
+
xpcMain.handle(channel, async (payload) => {
|
|
208
|
+
return await method(payload.params);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/xpc/main/xpcMain.emitter.ts
|
|
215
|
+
var createXpcMainEmitter = (className) => {
|
|
216
|
+
return new Proxy({}, {
|
|
217
|
+
get(_target, prop) {
|
|
218
|
+
const channel = buildXpcChannel(className, prop);
|
|
219
|
+
return (params) => xpcMain.send(channel, params);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
exports.XpcMainHandler = XpcMainHandler;
|
|
181
225
|
exports.XpcTask = XpcTask;
|
|
226
|
+
exports.createXpcMainEmitter = createXpcMainEmitter;
|
|
182
227
|
exports.xpcCenter = xpcCenter;
|
|
183
228
|
exports.xpcMain = xpcMain;
|
|
184
229
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/xpc/main/xpc-task.helper.ts","../../../src/xpc/main/xpc-id.helper.ts","../../../src/pathHelper/main/pathMain.helper.ts","../../../src/xpc/main/xpc-main.helper.ts","../../../src/xpc/main/xpc-center.helper.ts"],"names":["Semaphore","ipcMain","app","webContents"],"mappings":"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","file":"index.js","sourcesContent":["import { Semaphore } from 'rig-foundation';\nimport { XpcPayload } from '../shared/xpc.type';\n\nexport class XpcTask implements XpcPayload {\n id: string;\n handleName: string;\n params?: any;\n ret?: any;\n\n private semaphore: Semaphore;\n\n constructor(payload: XpcPayload) {\n this.id = payload.id;\n this.handleName = payload.handleName;\n this.params = payload.params;\n this.ret = payload.ret ?? null;\n this.semaphore = new Semaphore(1);\n this.semaphore.take(() => {});\n }\n\n /** Block until unblock() is called */\n block(): Promise<void> {\n return this.semaphore.takeAsync();\n }\n\n /** Release the semaphore, unblocking the waiting block() call */\n unblock(): void {\n this.semaphore.leave();\n }\n\n /** Convert to a plain XpcPayload (serializable for IPC) */\n toPayload(): XpcPayload {\n return {\n id: this.id,\n handleName: this.handleName,\n params: this.params,\n ret: this.ret,\n };\n }\n}\n","/**\n * High-performance process-unique ID generator.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `${prefix}-${(++counter).toString(36)}`;\n};\n","import { app, ipcMain } from 'electron';\nimport type { PathName } from '../shared/pathHelper.type';\n\nconst IPC_GET_APP_PATH = '__buff_path_getAppPath__';\nconst IPC_GET_PATH = '__buff_path_getPath__';\nconst IPC_GET_USER_DATA_PATH = '__buff_path_getUserDataPath__';\n\nclass PathMainHelper {\n init(): void {\n this.setupListeners();\n }\n\n private setupListeners(): void {\n ipcMain.handle(IPC_GET_APP_PATH, () => {\n return app.getAppPath();\n });\n\n ipcMain.handle(IPC_GET_PATH, (_event, name: PathName) => {\n return app.getPath(name);\n });\n\n ipcMain.handle(IPC_GET_USER_DATA_PATH, () => {\n return this.getUserDataPath();\n });\n }\n\n /** Get the app installation path */\n getAppPath(): string {\n return app.getAppPath();\n }\n\n /** Get a special directory or file path by name */\n getPath(name: PathName): string {\n return app.getPath(name);\n }\n\n /** Get the user data path (e.g. Application Support on macOS, Roaming on Windows) */\n getUserDataPath(): string {\n return app.getPath('userData');\n }\n\n}\n\nexport const pathMainHelper = new PathMainHelper();\n","import { XpcPayload } from '../shared/xpc.type';\nimport { xpcCenter } from './xpc-center.helper';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/**\n * XpcMain: runs in the main process.\n * - handle(): register a handler callable by renderers or other main-process code.\n * - send(): invoke a registered handleName (main-process or renderer), delegating to xpcCenter.\n */\nclass XpcMain {\n private handlers = new Map<string, XpcHandler>();\n\n /**\n * Register a handler in the main process.\n * When another renderer calls send() with this handleName, xpcCenter will\n * invoke this handler directly (webContentsId = 0) without forwarding to a renderer.\n */\n handle(handleName: string, handler: XpcHandler): void {\n this.handlers.set(handleName, handler);\n xpcCenter.registerMainHandler(handleName);\n }\n\n /**\n * Get the registered handler for a given handleName.\n */\n getHandler(handleName: string): XpcHandler | undefined {\n return this.handlers.get(handleName);\n }\n\n /**\n * Send a message to a registered handler by handleName.\n * Delegates to xpcCenter.exec() which handles both main-process and renderer targets.\n */\n async send(handleName: string, params?: any): Promise<any> {\n return xpcCenter.exec(handleName, params);\n }\n}\n\nexport const xpcMain = new XpcMain();\n","import { ipcMain, webContents } from 'electron';\nimport { XpcPayload } from '../shared/xpc.type';\nimport { XpcTask } from './xpc-task.helper';\nimport { generateXpcId } from './xpc-id.helper';\nimport { pathMainHelper } from '../../pathHelper/main/pathMain.helper';\nimport { xpcMain } from './xpc-main.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\n/**\n * XpcCenter: runs in the main process.\n * - Listens for __xpc_register__: renderer registers a handleName, center stores {handleName → webContentsId}\n * - Listens for __xpc_exec__ (ipcMain.handle): renderer invokes exec, center forwards to target renderer,\n * blocks via semaphore until __xpc_finish__ is received, then returns result.\n * - Listens for __xpc_finish__: target renderer finished execution, unblocks the pending task.\n */\nclass XpcCenter {\n /** handleName → webContentsId */\n private registry = new Map<string, number>();\n /** task.id → XpcTask (with semaphore block/unblock) */\n private pendingTasks = new Map<string, XpcTask>();\n\n init(): void {\n pathMainHelper.init();\n this.setupListeners();\n }\n\n /**\n * Register a main-process handleName in the registry with webContentsId = 0.\n */\n registerMainHandler(handleName: string): void {\n this.registry.set(handleName, 0);\n }\n\n /**\n * Execute a handleName: if main-process handler, call directly;\n * otherwise forward to target renderer, block until __xpc_finish__.\n * Used by both ipcMain.handle(XPC_EXEC) and xpcMain.send().\n */\n async exec(handleName: string, params?: any): Promise<any> {\n const targetId = this.registry.get(handleName);\n if (targetId == null) {\n return null;\n }\n\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n };\n\n // targetId === 0 means the handler is registered in the main process\n if (targetId === 0) {\n const handler = xpcMain.getHandler(handleName);\n if (!handler) {\n return null;\n }\n try {\n return await handler(payload);\n } catch (_e) {\n return null;\n }\n }\n\n const target = webContents.fromId(targetId);\n if (!target || target.isDestroyed() || target.isCrashed()) {\n return null;\n }\n\n // Create semaphore-blocked task\n const task = new XpcTask(payload);\n\n this.pendingTasks.set(task.id, task);\n\n // Forward handleName event + payload to target renderer\n target.send(handleName, payload);\n\n // Block until __xpc_finish__ unblocks\n await task.block();\n this.pendingTasks.delete(task.id);\n\n return task.toPayload().ret ?? null;\n }\n\n private setupListeners(): void {\n // Renderer registers a handleName\n ipcMain.on(XPC_REGISTER, (event, payload: { handleName: string }) => {\n this.registry.set(payload.handleName, event.sender.id);\n });\n\n // Renderer invokes exec via IPC\n ipcMain.handle(XPC_EXEC, async (_event, payload: XpcPayload): Promise<any> => {\n return this.exec(payload.handleName, payload.params);\n });\n\n // Target renderer finished execution, unblock pending task\n ipcMain.on(XPC_FINISH, (_event, payload: XpcPayload) => {\n const task = this.pendingTasks.get(payload.id);\n if (task) {\n task.ret = payload.ret ?? null;\n task.unblock();\n }\n });\n }\n}\n\nexport const xpcCenter = new XpcCenter();\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/xpc/main/xpcTask.helper.ts","../../../src/xpc/main/xpcId.helper.ts","../../../src/pathHelper/main/pathMain.helper.ts","../../../src/xpc/main/xpcMain.helper.ts","../../../src/xpc/main/xpcCenter.helper.ts","../../../src/xpc/shared/xpcHandler.type.ts","../../../src/xpc/main/xpcMain.handler.ts","../../../src/xpc/main/xpcMain.emitter.ts"],"names":["Semaphore","ipcMain","app","webContents"],"mappings":"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","file":"index.js","sourcesContent":["import { Semaphore } from 'rig-foundation';\nimport { XpcPayload } from '../shared/xpc.type';\n\nexport class XpcTask implements XpcPayload {\n id: string;\n handleName: string;\n params?: any;\n ret?: any;\n\n private semaphore: Semaphore;\n\n constructor(payload: XpcPayload) {\n this.id = payload.id;\n this.handleName = payload.handleName;\n this.params = payload.params;\n this.ret = payload.ret ?? null;\n this.semaphore = new Semaphore(1);\n this.semaphore.take(() => {});\n }\n\n /** Block until unblock() is called */\n block(): Promise<void> {\n return this.semaphore.takeAsync();\n }\n\n /** Release the semaphore, unblocking the waiting block() call */\n unblock(): void {\n this.semaphore.leave();\n }\n\n /** Convert to a plain XpcPayload (serializable for IPC) */\n toPayload(): XpcPayload {\n return {\n id: this.id,\n handleName: this.handleName,\n params: this.params,\n ret: this.ret,\n };\n }\n}\n","/**\n * High-performance process-unique ID generator.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `${prefix}-${(++counter).toString(36)}`;\n};\n","import { app, ipcMain } from 'electron';\nimport type { PathName } from '../shared/pathHelper.type';\n\nconst IPC_GET_APP_PATH = '__buff_path_getAppPath__';\nconst IPC_GET_PATH = '__buff_path_getPath__';\nconst IPC_GET_USER_DATA_PATH = '__buff_path_getUserDataPath__';\n\nclass PathMainHelper {\n init(): void {\n this.setupListeners();\n }\n\n private setupListeners(): void {\n ipcMain.handle(IPC_GET_APP_PATH, () => {\n return app.getAppPath();\n });\n\n ipcMain.handle(IPC_GET_PATH, (_event, name: PathName) => {\n return app.getPath(name);\n });\n\n ipcMain.handle(IPC_GET_USER_DATA_PATH, () => {\n return this.getUserDataPath();\n });\n }\n\n /** Get the app installation path */\n getAppPath(): string {\n return app.getAppPath();\n }\n\n /** Get a special directory or file path by name */\n getPath(name: PathName): string {\n return app.getPath(name);\n }\n\n /** Get the user data path (e.g. Application Support on macOS, Roaming on Windows) */\n getUserDataPath(): string {\n return app.getPath('userData');\n }\n\n}\n\nexport const pathMainHelper = new PathMainHelper();\n","import { XpcPayload } from '../shared/xpc.type';\nimport { xpcCenter } from './xpcCenter.helper';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/**\n * XpcMain: runs in the main process.\n * - handle(): register a handler callable by renderers or other main-process code.\n * - send(): invoke a registered handleName (main-process or renderer), delegating to xpcCenter.\n */\nclass XpcMain {\n private handlers = new Map<string, XpcHandler>();\n\n /**\n * Register a handler in the main process.\n * When another renderer calls send() with this handleName, xpcCenter will\n * invoke this handler directly (webContentsId = 0) without forwarding to a renderer.\n */\n handle(handleName: string, handler: XpcHandler): void {\n this.handlers.set(handleName, handler);\n xpcCenter.registerMainHandler(handleName);\n }\n\n /**\n * Get the registered handler for a given handleName.\n */\n getHandler(handleName: string): XpcHandler | undefined {\n return this.handlers.get(handleName);\n }\n\n /**\n * Send a message to a registered handler by handleName.\n * Delegates to xpcCenter.exec() which handles both main-process and renderer targets.\n */\n async send(handleName: string, params?: any): Promise<any> {\n return xpcCenter.exec(handleName, params);\n }\n}\n\nexport const xpcMain = new XpcMain();\n","import { ipcMain, webContents } from 'electron';\nimport { XpcPayload } from '../shared/xpc.type';\nimport { XpcTask } from './xpcTask.helper';\nimport { generateXpcId } from './xpcId.helper';\nimport { pathMainHelper } from '../../pathHelper/main/pathMain.helper';\nimport { xpcMain } from './xpcMain.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\n/**\n * XpcCenter: runs in the main process.\n * - Listens for __xpc_register__: renderer registers a handleName, center stores {handleName → webContentsId}\n * - Listens for __xpc_exec__ (ipcMain.handle): renderer invokes exec, center forwards to target renderer,\n * blocks via semaphore until __xpc_finish__ is received, then returns result.\n * - Listens for __xpc_finish__: target renderer finished execution, unblocks the pending task.\n */\nclass XpcCenter {\n /** handleName → webContentsId */\n private registry = new Map<string, number>();\n /** task.id → XpcTask (with semaphore block/unblock) */\n private pendingTasks = new Map<string, XpcTask>();\n\n init(): void {\n pathMainHelper.init();\n this.setupListeners();\n }\n\n /**\n * Register a main-process handleName in the registry with webContentsId = 0.\n */\n registerMainHandler(handleName: string): void {\n this.registry.set(handleName, 0);\n }\n\n /**\n * Execute a handleName: if main-process handler, call directly;\n * otherwise forward to target renderer, block until __xpc_finish__.\n * Used by both ipcMain.handle(XPC_EXEC) and xpcMain.send().\n */\n async exec(handleName: string, params?: any): Promise<any> {\n const targetId = this.registry.get(handleName);\n if (targetId == null) {\n return null;\n }\n\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n };\n\n // targetId === 0 means the handler is registered in the main process\n if (targetId === 0) {\n const handler = xpcMain.getHandler(handleName);\n if (!handler) {\n return null;\n }\n try {\n return await handler(payload);\n } catch (_e) {\n return null;\n }\n }\n\n const target = webContents.fromId(targetId);\n if (!target || target.isDestroyed() || target.isCrashed()) {\n return null;\n }\n\n // Create semaphore-blocked task\n const task = new XpcTask(payload);\n\n this.pendingTasks.set(task.id, task);\n\n // Forward handleName event + payload to target renderer\n target.send(handleName, payload);\n\n // Block until __xpc_finish__ unblocks\n await task.block();\n this.pendingTasks.delete(task.id);\n\n return task.toPayload().ret ?? null;\n }\n\n private setupListeners(): void {\n // Renderer registers a handleName\n ipcMain.on(XPC_REGISTER, (event, payload: { handleName: string }) => {\n this.registry.set(payload.handleName, event.sender.id);\n });\n\n // Renderer invokes exec via IPC\n ipcMain.handle(XPC_EXEC, async (_event, payload: XpcPayload): Promise<any> => {\n return this.exec(payload.handleName, payload.params);\n });\n\n // Target renderer finished execution, unblock pending task\n ipcMain.on(XPC_FINISH, (_event, payload: XpcPayload) => {\n const task = this.pendingTasks.get(payload.id);\n if (task) {\n task.ret = payload.ret ?? null;\n task.unblock();\n }\n });\n }\n}\n\nexport const xpcCenter = new XpcCenter();\n","/**\n * Prefix for all auto-registered xpc handler channels.\n * Channel format: `xpc:ClassName/methodName`\n */\nexport const XPC_HANDLER_PREFIX = 'xpc:';\n\n/**\n * Build the xpc channel name from class name and method name.\n * e.g. buildChannel('UserTable', 'getUserList') => 'xpc:UserTable/getUserList'\n */\nexport const buildXpcChannel = (className: string, methodName: string): string => {\n return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;\n};\n\n/**\n * Extract own method names from a class prototype, excluding constructor.\n */\nexport const getHandlerMethodNames = (prototype: object): string[] => {\n const names: string[] = [];\n const keys = Object.getOwnPropertyNames(prototype);\n for (const key of keys) {\n if (key === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n if (descriptor && typeof descriptor.value === 'function') {\n names.push(key);\n }\n }\n return names;\n};\n\n/**\n * Constraint: handler methods must accept 0 or 1 parameter.\n * Methods with 2+ parameters will fail type checking.\n */\nexport type XpcHandlerMethod = (() => Promise<any>) | ((params: any) => Promise<any>);\n\n/**\n * Helper: checks if a function type has at most 1 parameter.\n * Returns the function type itself if valid, `never` otherwise.\n */\ntype AssertSingleParam<F> =\n F extends () => any ? F :\n F extends (p: any) => any ?\n F extends (p: any, q: any, ...rest: any[]) => any ? never : F\n : never;\n\n/**\n * Utility type: extracts the method signatures from a handler class,\n * turning each method into an emitter-compatible signature.\n * Methods with 2+ parameters are mapped to `never`, causing a compile error on use.\n */\nexport type XpcEmitterOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:\n AssertSingleParam<T[K]> extends never\n ? never\n : T[K] extends (params: infer P) => any\n ? (params: P) => Promise<any>\n : () => Promise<any>;\n};\n","import { XpcPayload } from '../shared/xpc.type';\nimport { buildXpcChannel, getHandlerMethodNames } from '../shared/xpcHandler.type';\nimport { xpcMain } from './xpcMain.helper';\n\n/**\n * Base class for main-process xpc handlers.\n * Subclass this and define async methods — they will be auto-registered\n * as xpc handlers with channel `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcMainHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTable = new UserTable();\n * // auto-registers handler for 'xpc:UserTable/getUserList'\n * ```\n */\nexport class XpcMainHandler {\n constructor() {\n const className = this.constructor.name;\n const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));\n for (const methodName of methodNames) {\n const channel = buildXpcChannel(className, methodName);\n const method = (this as any)[methodName].bind(this);\n xpcMain.handle(channel, async (payload: XpcPayload) => {\n return await method(payload.params);\n });\n }\n }\n}\n","import { buildXpcChannel, XpcEmitterOf } from '../shared/xpcHandler.type';\nimport { xpcMain } from './xpcMain.helper';\n\n/**\n * Create a type-safe emitter proxy for a main-process xpc handler.\n * The emitter mirrors the handler's method signatures, but each call\n * sends a message via xpcMain.send() to `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcMainHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTableEmitter = createXpcMainEmitter<UserTable>('UserTable');\n * const list = await userTableEmitter.getUserList({ page: 1 });\n * // sends to 'xpc:UserTable/getUserList'\n * ```\n */\nexport const createXpcMainEmitter = <T>(className: string): XpcEmitterOf<T> => {\n return new Proxy({} as XpcEmitterOf<T>, {\n get(_target, prop: string) {\n const channel = buildXpcChannel(className, prop);\n return (params?: any) => xpcMain.send(channel, params);\n },\n });\n};\n"]}
|
package/dist/xpc/main/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { webContents, ipcMain, app } from 'electron';
|
|
2
2
|
import { Semaphore } from 'rig-foundation';
|
|
3
3
|
|
|
4
|
-
// src/xpc/main/
|
|
4
|
+
// src/xpc/main/xpcCenter.helper.ts
|
|
5
5
|
var XpcTask = class {
|
|
6
6
|
constructor(payload) {
|
|
7
7
|
this.id = payload.id;
|
|
@@ -31,7 +31,7 @@ var XpcTask = class {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
// src/xpc/main/
|
|
34
|
+
// src/xpc/main/xpcId.helper.ts
|
|
35
35
|
var prefix = Math.random().toString(36).slice(2, 8);
|
|
36
36
|
var counter = 0;
|
|
37
37
|
var generateXpcId = () => {
|
|
@@ -70,7 +70,7 @@ var PathMainHelper = class {
|
|
|
70
70
|
};
|
|
71
71
|
var pathMainHelper = new PathMainHelper();
|
|
72
72
|
|
|
73
|
-
// src/xpc/main/
|
|
73
|
+
// src/xpc/main/xpcMain.helper.ts
|
|
74
74
|
var XpcMain = class {
|
|
75
75
|
constructor() {
|
|
76
76
|
this.handlers = /* @__PURE__ */ new Map();
|
|
@@ -100,7 +100,7 @@ var XpcMain = class {
|
|
|
100
100
|
};
|
|
101
101
|
var xpcMain = new XpcMain();
|
|
102
102
|
|
|
103
|
-
// src/xpc/main/
|
|
103
|
+
// src/xpc/main/xpcCenter.helper.ts
|
|
104
104
|
var XPC_REGISTER = "__xpc_register__";
|
|
105
105
|
var XPC_EXEC = "__xpc_exec__";
|
|
106
106
|
var XPC_FINISH = "__xpc_finish__";
|
|
@@ -176,6 +176,49 @@ var XpcCenter = class {
|
|
|
176
176
|
};
|
|
177
177
|
var xpcCenter = new XpcCenter();
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
// src/xpc/shared/xpcHandler.type.ts
|
|
180
|
+
var XPC_HANDLER_PREFIX = "xpc:";
|
|
181
|
+
var buildXpcChannel = (className, methodName) => {
|
|
182
|
+
return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;
|
|
183
|
+
};
|
|
184
|
+
var getHandlerMethodNames = (prototype) => {
|
|
185
|
+
const names = [];
|
|
186
|
+
const keys = Object.getOwnPropertyNames(prototype);
|
|
187
|
+
for (const key of keys) {
|
|
188
|
+
if (key === "constructor") continue;
|
|
189
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
190
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
191
|
+
names.push(key);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return names;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// src/xpc/main/xpcMain.handler.ts
|
|
198
|
+
var XpcMainHandler = class {
|
|
199
|
+
constructor() {
|
|
200
|
+
const className = this.constructor.name;
|
|
201
|
+
const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));
|
|
202
|
+
for (const methodName of methodNames) {
|
|
203
|
+
const channel = buildXpcChannel(className, methodName);
|
|
204
|
+
const method = this[methodName].bind(this);
|
|
205
|
+
xpcMain.handle(channel, async (payload) => {
|
|
206
|
+
return await method(payload.params);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/xpc/main/xpcMain.emitter.ts
|
|
213
|
+
var createXpcMainEmitter = (className) => {
|
|
214
|
+
return new Proxy({}, {
|
|
215
|
+
get(_target, prop) {
|
|
216
|
+
const channel = buildXpcChannel(className, prop);
|
|
217
|
+
return (params) => xpcMain.send(channel, params);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export { XpcMainHandler, XpcTask, createXpcMainEmitter, xpcCenter, xpcMain };
|
|
180
223
|
//# sourceMappingURL=index.mjs.map
|
|
181
224
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/xpc/main/xpc-task.helper.ts","../../../src/xpc/main/xpc-id.helper.ts","../../../src/pathHelper/main/pathMain.helper.ts","../../../src/xpc/main/xpc-main.helper.ts","../../../src/xpc/main/xpc-center.helper.ts"],"names":["ipcMain"],"mappings":"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","file":"index.mjs","sourcesContent":["import { Semaphore } from 'rig-foundation';\nimport { XpcPayload } from '../shared/xpc.type';\n\nexport class XpcTask implements XpcPayload {\n id: string;\n handleName: string;\n params?: any;\n ret?: any;\n\n private semaphore: Semaphore;\n\n constructor(payload: XpcPayload) {\n this.id = payload.id;\n this.handleName = payload.handleName;\n this.params = payload.params;\n this.ret = payload.ret ?? null;\n this.semaphore = new Semaphore(1);\n this.semaphore.take(() => {});\n }\n\n /** Block until unblock() is called */\n block(): Promise<void> {\n return this.semaphore.takeAsync();\n }\n\n /** Release the semaphore, unblocking the waiting block() call */\n unblock(): void {\n this.semaphore.leave();\n }\n\n /** Convert to a plain XpcPayload (serializable for IPC) */\n toPayload(): XpcPayload {\n return {\n id: this.id,\n handleName: this.handleName,\n params: this.params,\n ret: this.ret,\n };\n }\n}\n","/**\n * High-performance process-unique ID generator.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `${prefix}-${(++counter).toString(36)}`;\n};\n","import { app, ipcMain } from 'electron';\nimport type { PathName } from '../shared/pathHelper.type';\n\nconst IPC_GET_APP_PATH = '__buff_path_getAppPath__';\nconst IPC_GET_PATH = '__buff_path_getPath__';\nconst IPC_GET_USER_DATA_PATH = '__buff_path_getUserDataPath__';\n\nclass PathMainHelper {\n init(): void {\n this.setupListeners();\n }\n\n private setupListeners(): void {\n ipcMain.handle(IPC_GET_APP_PATH, () => {\n return app.getAppPath();\n });\n\n ipcMain.handle(IPC_GET_PATH, (_event, name: PathName) => {\n return app.getPath(name);\n });\n\n ipcMain.handle(IPC_GET_USER_DATA_PATH, () => {\n return this.getUserDataPath();\n });\n }\n\n /** Get the app installation path */\n getAppPath(): string {\n return app.getAppPath();\n }\n\n /** Get a special directory or file path by name */\n getPath(name: PathName): string {\n return app.getPath(name);\n }\n\n /** Get the user data path (e.g. Application Support on macOS, Roaming on Windows) */\n getUserDataPath(): string {\n return app.getPath('userData');\n }\n\n}\n\nexport const pathMainHelper = new PathMainHelper();\n","import { XpcPayload } from '../shared/xpc.type';\nimport { xpcCenter } from './xpc-center.helper';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/**\n * XpcMain: runs in the main process.\n * - handle(): register a handler callable by renderers or other main-process code.\n * - send(): invoke a registered handleName (main-process or renderer), delegating to xpcCenter.\n */\nclass XpcMain {\n private handlers = new Map<string, XpcHandler>();\n\n /**\n * Register a handler in the main process.\n * When another renderer calls send() with this handleName, xpcCenter will\n * invoke this handler directly (webContentsId = 0) without forwarding to a renderer.\n */\n handle(handleName: string, handler: XpcHandler): void {\n this.handlers.set(handleName, handler);\n xpcCenter.registerMainHandler(handleName);\n }\n\n /**\n * Get the registered handler for a given handleName.\n */\n getHandler(handleName: string): XpcHandler | undefined {\n return this.handlers.get(handleName);\n }\n\n /**\n * Send a message to a registered handler by handleName.\n * Delegates to xpcCenter.exec() which handles both main-process and renderer targets.\n */\n async send(handleName: string, params?: any): Promise<any> {\n return xpcCenter.exec(handleName, params);\n }\n}\n\nexport const xpcMain = new XpcMain();\n","import { ipcMain, webContents } from 'electron';\nimport { XpcPayload } from '../shared/xpc.type';\nimport { XpcTask } from './xpc-task.helper';\nimport { generateXpcId } from './xpc-id.helper';\nimport { pathMainHelper } from '../../pathHelper/main/pathMain.helper';\nimport { xpcMain } from './xpc-main.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\n/**\n * XpcCenter: runs in the main process.\n * - Listens for __xpc_register__: renderer registers a handleName, center stores {handleName → webContentsId}\n * - Listens for __xpc_exec__ (ipcMain.handle): renderer invokes exec, center forwards to target renderer,\n * blocks via semaphore until __xpc_finish__ is received, then returns result.\n * - Listens for __xpc_finish__: target renderer finished execution, unblocks the pending task.\n */\nclass XpcCenter {\n /** handleName → webContentsId */\n private registry = new Map<string, number>();\n /** task.id → XpcTask (with semaphore block/unblock) */\n private pendingTasks = new Map<string, XpcTask>();\n\n init(): void {\n pathMainHelper.init();\n this.setupListeners();\n }\n\n /**\n * Register a main-process handleName in the registry with webContentsId = 0.\n */\n registerMainHandler(handleName: string): void {\n this.registry.set(handleName, 0);\n }\n\n /**\n * Execute a handleName: if main-process handler, call directly;\n * otherwise forward to target renderer, block until __xpc_finish__.\n * Used by both ipcMain.handle(XPC_EXEC) and xpcMain.send().\n */\n async exec(handleName: string, params?: any): Promise<any> {\n const targetId = this.registry.get(handleName);\n if (targetId == null) {\n return null;\n }\n\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n };\n\n // targetId === 0 means the handler is registered in the main process\n if (targetId === 0) {\n const handler = xpcMain.getHandler(handleName);\n if (!handler) {\n return null;\n }\n try {\n return await handler(payload);\n } catch (_e) {\n return null;\n }\n }\n\n const target = webContents.fromId(targetId);\n if (!target || target.isDestroyed() || target.isCrashed()) {\n return null;\n }\n\n // Create semaphore-blocked task\n const task = new XpcTask(payload);\n\n this.pendingTasks.set(task.id, task);\n\n // Forward handleName event + payload to target renderer\n target.send(handleName, payload);\n\n // Block until __xpc_finish__ unblocks\n await task.block();\n this.pendingTasks.delete(task.id);\n\n return task.toPayload().ret ?? null;\n }\n\n private setupListeners(): void {\n // Renderer registers a handleName\n ipcMain.on(XPC_REGISTER, (event, payload: { handleName: string }) => {\n this.registry.set(payload.handleName, event.sender.id);\n });\n\n // Renderer invokes exec via IPC\n ipcMain.handle(XPC_EXEC, async (_event, payload: XpcPayload): Promise<any> => {\n return this.exec(payload.handleName, payload.params);\n });\n\n // Target renderer finished execution, unblock pending task\n ipcMain.on(XPC_FINISH, (_event, payload: XpcPayload) => {\n const task = this.pendingTasks.get(payload.id);\n if (task) {\n task.ret = payload.ret ?? null;\n task.unblock();\n }\n });\n }\n}\n\nexport const xpcCenter = new XpcCenter();\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/xpc/main/xpcTask.helper.ts","../../../src/xpc/main/xpcId.helper.ts","../../../src/pathHelper/main/pathMain.helper.ts","../../../src/xpc/main/xpcMain.helper.ts","../../../src/xpc/main/xpcCenter.helper.ts","../../../src/xpc/shared/xpcHandler.type.ts","../../../src/xpc/main/xpcMain.handler.ts","../../../src/xpc/main/xpcMain.emitter.ts"],"names":["ipcMain"],"mappings":";;;;AAGO,IAAM,UAAN,MAAoC;AAAA,EAQzC,YAAY,OAAA,EAAqB;AAC/B,IAAA,IAAA,CAAK,KAAK,OAAA,CAAQ,EAAA;AAClB,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,UAAA;AAC1B,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,GAAA,GAAM,QAAQ,GAAA,IAAO,IAAA;AAC1B,IAAA,IAAA,CAAK,SAAA,GAAY,IAAI,SAAA,CAAU,CAAC,CAAA;AAChC,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EAC9B;AAAA;AAAA,EAGA,KAAA,GAAuB;AACrB,IAAA,OAAO,IAAA,CAAK,UAAU,SAAA,EAAU;AAAA,EAClC;AAAA;AAAA,EAGA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACvB;AAAA;AAAA,EAGA,SAAA,GAAwB;AACtB,IAAA,OAAO;AAAA,MACL,IAAI,IAAA,CAAK,EAAA;AAAA,MACT,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,KAAK,IAAA,CAAK;AAAA,KACZ;AAAA,EACF;AACF;;;AClCA,IAAM,MAAA,GAAS,KAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACpD,IAAI,OAAA,GAAU,CAAA;AAEP,IAAM,gBAAgB,MAAc;AACzC,EAAA,OAAO,GAAG,MAAM,CAAA,CAAA,EAAA,CAAK,EAAE,OAAA,EAAS,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA;AAC9C,CAAA;ACPA,IAAM,gBAAA,GAAmB,0BAAA;AACzB,IAAM,YAAA,GAAe,uBAAA;AACrB,IAAM,sBAAA,GAAyB,+BAAA;AAE/B,IAAM,iBAAN,MAAqB;AAAA,EACnB,IAAA,GAAa;AACX,IAAA,IAAA,CAAK,cAAA,EAAe;AAAA,EACtB;AAAA,EAEQ,cAAA,GAAuB;AAC7B,IAAA,OAAA,CAAQ,MAAA,CAAO,kBAAkB,MAAM;AACrC,MAAA,OAAO,IAAI,UAAA,EAAW;AAAA,IACxB,CAAC,CAAA;AAED,IAAA,OAAA,CAAQ,MAAA,CAAO,YAAA,EAAc,CAAC,MAAA,EAAQ,IAAA,KAAmB;AACvD,MAAA,OAAO,GAAA,CAAI,QAAQ,IAAI,CAAA;AAAA,IACzB,CAAC,CAAA;AAED,IAAA,OAAA,CAAQ,MAAA,CAAO,wBAAwB,MAAM;AAC3C,MAAA,OAAO,KAAK,eAAA,EAAgB;AAAA,IAC9B,CAAC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,UAAA,GAAqB;AACnB,IAAA,OAAO,IAAI,UAAA,EAAW;AAAA,EACxB;AAAA;AAAA,EAGA,QAAQ,IAAA,EAAwB;AAC9B,IAAA,OAAO,GAAA,CAAI,QAAQ,IAAI,CAAA;AAAA,EACzB;AAAA;AAAA,EAGA,eAAA,GAA0B;AACxB,IAAA,OAAO,GAAA,CAAI,QAAQ,UAAU,CAAA;AAAA,EAC/B;AAEF,CAAA;AAEO,IAAM,cAAA,GAAiB,IAAI,cAAA,EAAe;;;ACjCjD,IAAM,UAAN,MAAc;AAAA,EAAd,WAAA,GAAA;AACE,IAAA,IAAA,CAAQ,QAAA,uBAAe,GAAA,EAAwB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/C,MAAA,CAAO,YAAoB,OAAA,EAA2B;AACpD,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAA,EAAY,OAAO,CAAA;AACrC,IAAA,SAAA,CAAU,oBAAoB,UAAU,CAAA;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,UAAA,EAA4C;AACrD,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAA,CAAK,UAAA,EAAoB,MAAA,EAA4B;AACzD,IAAA,OAAO,SAAA,CAAU,IAAA,CAAK,UAAA,EAAY,MAAM,CAAA;AAAA,EAC1C;AACF,CAAA;AAEO,IAAM,OAAA,GAAU,IAAI,OAAA;;;AChC3B,IAAM,YAAA,GAAe,kBAAA;AACrB,IAAM,QAAA,GAAW,cAAA;AACjB,IAAM,UAAA,GAAa,gBAAA;AASnB,IAAM,YAAN,MAAgB;AAAA,EAAhB,WAAA,GAAA;AAEE;AAAA,IAAA,IAAA,CAAQ,QAAA,uBAAe,GAAA,EAAoB;AAE3C;AAAA,IAAA,IAAA,CAAQ,YAAA,uBAAmB,GAAA,EAAqB;AAAA,EAAA;AAAA,EAEhD,IAAA,GAAa;AACX,IAAA,cAAA,CAAe,IAAA,EAAK;AACpB,IAAA,IAAA,CAAK,cAAA,EAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAA,EAA0B;AAC5C,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAA,EAAY,CAAC,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAA,CAAK,UAAA,EAAoB,MAAA,EAA4B;AACzD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AAC7C,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,OAAA,GAAsB;AAAA,MAC1B,IAAI,aAAA,EAAc;AAAA,MAClB,UAAA;AAAA,MACA;AAAA,KACF;AAGA,IAAA,IAAI,aAAa,CAAA,EAAG;AAClB,MAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,UAAA,CAAW,UAAU,CAAA;AAC7C,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,QAAQ,OAAO,CAAA;AAAA,MAC9B,SAAS,EAAA,EAAI;AACX,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,WAAA,CAAY,MAAA,CAAO,QAAQ,CAAA;AAC1C,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,aAAY,IAAK,MAAA,CAAO,WAAU,EAAG;AACzD,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,MAAM,IAAA,GAAO,IAAI,OAAA,CAAQ,OAAO,CAAA;AAEhC,IAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI,IAAI,CAAA;AAGnC,IAAA,MAAA,CAAO,IAAA,CAAK,YAAY,OAAO,CAAA;AAG/B,IAAA,MAAM,KAAK,KAAA,EAAM;AACjB,IAAA,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,IAAA,CAAK,EAAE,CAAA;AAEhC,IAAA,OAAO,IAAA,CAAK,SAAA,EAAU,CAAE,GAAA,IAAO,IAAA;AAAA,EACjC;AAAA,EAEQ,cAAA,GAAuB;AAE7B,IAAAA,OAAAA,CAAQ,EAAA,CAAG,YAAA,EAAc,CAAC,OAAO,OAAA,KAAoC;AACnE,MAAA,IAAA,CAAK,SAAS,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,KAAA,CAAM,OAAO,EAAE,CAAA;AAAA,IACvD,CAAC,CAAA;AAGD,IAAAA,OAAAA,CAAQ,MAAA,CAAO,QAAA,EAAU,OAAO,QAAQ,OAAA,KAAsC;AAC5E,MAAA,OAAO,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,UAAA,EAAY,QAAQ,MAAM,CAAA;AAAA,IACrD,CAAC,CAAA;AAGD,IAAAA,OAAAA,CAAQ,EAAA,CAAG,UAAA,EAAY,CAAC,QAAQ,OAAA,KAAwB;AACtD,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,QAAQ,EAAE,CAAA;AAC7C,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,IAAA,CAAK,GAAA,GAAM,QAAQ,GAAA,IAAO,IAAA;AAC1B,QAAA,IAAA,CAAK,OAAA,EAAQ;AAAA,MACf;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AACF,CAAA;AAEO,IAAM,SAAA,GAAY,IAAI,SAAA;;;ACxGtB,IAAM,kBAAA,GAAqB,MAAA;AAM3B,IAAM,eAAA,GAAkB,CAAC,SAAA,EAAmB,UAAA,KAA+B;AAChF,EAAA,OAAO,CAAA,EAAG,kBAAkB,CAAA,EAAG,SAAS,IAAI,UAAU,CAAA,CAAA;AACxD,CAAA;AAKO,IAAM,qBAAA,GAAwB,CAAC,SAAA,KAAgC;AACpE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,mBAAA,CAAoB,SAAS,CAAA;AACjD,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,QAAQ,aAAA,EAAe;AAC3B,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,wBAAA,CAAyB,SAAA,EAAW,GAAG,CAAA;AACjE,IAAA,IAAI,UAAA,IAAc,OAAO,UAAA,CAAW,KAAA,KAAU,UAAA,EAAY;AACxD,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT,CAAA;;;ACVO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,WAAA,GAAc;AACZ,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,CAAY,IAAA;AACnC,IAAA,MAAM,WAAA,GAAc,qBAAA,CAAsB,MAAA,CAAO,cAAA,CAAe,IAAI,CAAC,CAAA;AACrE,IAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,SAAA,EAAW,UAAU,CAAA;AACrD,MAAA,MAAM,MAAA,GAAU,IAAA,CAAa,UAAU,CAAA,CAAE,KAAK,IAAI,CAAA;AAClD,MAAA,OAAA,CAAQ,MAAA,CAAO,OAAA,EAAS,OAAO,OAAA,KAAwB;AACrD,QAAA,OAAO,MAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA;AAAA,MACpC,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AACF;;;ACZO,IAAM,oBAAA,GAAuB,CAAI,SAAA,KAAuC;AAC7E,EAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAsB;AAAA,IACtC,GAAA,CAAI,SAAS,IAAA,EAAc;AACzB,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,SAAA,EAAW,IAAI,CAAA;AAC/C,MAAA,OAAO,CAAC,MAAA,KAAiB,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,IACvD;AAAA,GACD,CAAA;AACH","file":"index.mjs","sourcesContent":["import { Semaphore } from 'rig-foundation';\nimport { XpcPayload } from '../shared/xpc.type';\n\nexport class XpcTask implements XpcPayload {\n id: string;\n handleName: string;\n params?: any;\n ret?: any;\n\n private semaphore: Semaphore;\n\n constructor(payload: XpcPayload) {\n this.id = payload.id;\n this.handleName = payload.handleName;\n this.params = payload.params;\n this.ret = payload.ret ?? null;\n this.semaphore = new Semaphore(1);\n this.semaphore.take(() => {});\n }\n\n /** Block until unblock() is called */\n block(): Promise<void> {\n return this.semaphore.takeAsync();\n }\n\n /** Release the semaphore, unblocking the waiting block() call */\n unblock(): void {\n this.semaphore.leave();\n }\n\n /** Convert to a plain XpcPayload (serializable for IPC) */\n toPayload(): XpcPayload {\n return {\n id: this.id,\n handleName: this.handleName,\n params: this.params,\n ret: this.ret,\n };\n }\n}\n","/**\n * High-performance process-unique ID generator.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `${prefix}-${(++counter).toString(36)}`;\n};\n","import { app, ipcMain } from 'electron';\nimport type { PathName } from '../shared/pathHelper.type';\n\nconst IPC_GET_APP_PATH = '__buff_path_getAppPath__';\nconst IPC_GET_PATH = '__buff_path_getPath__';\nconst IPC_GET_USER_DATA_PATH = '__buff_path_getUserDataPath__';\n\nclass PathMainHelper {\n init(): void {\n this.setupListeners();\n }\n\n private setupListeners(): void {\n ipcMain.handle(IPC_GET_APP_PATH, () => {\n return app.getAppPath();\n });\n\n ipcMain.handle(IPC_GET_PATH, (_event, name: PathName) => {\n return app.getPath(name);\n });\n\n ipcMain.handle(IPC_GET_USER_DATA_PATH, () => {\n return this.getUserDataPath();\n });\n }\n\n /** Get the app installation path */\n getAppPath(): string {\n return app.getAppPath();\n }\n\n /** Get a special directory or file path by name */\n getPath(name: PathName): string {\n return app.getPath(name);\n }\n\n /** Get the user data path (e.g. Application Support on macOS, Roaming on Windows) */\n getUserDataPath(): string {\n return app.getPath('userData');\n }\n\n}\n\nexport const pathMainHelper = new PathMainHelper();\n","import { XpcPayload } from '../shared/xpc.type';\nimport { xpcCenter } from './xpcCenter.helper';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/**\n * XpcMain: runs in the main process.\n * - handle(): register a handler callable by renderers or other main-process code.\n * - send(): invoke a registered handleName (main-process or renderer), delegating to xpcCenter.\n */\nclass XpcMain {\n private handlers = new Map<string, XpcHandler>();\n\n /**\n * Register a handler in the main process.\n * When another renderer calls send() with this handleName, xpcCenter will\n * invoke this handler directly (webContentsId = 0) without forwarding to a renderer.\n */\n handle(handleName: string, handler: XpcHandler): void {\n this.handlers.set(handleName, handler);\n xpcCenter.registerMainHandler(handleName);\n }\n\n /**\n * Get the registered handler for a given handleName.\n */\n getHandler(handleName: string): XpcHandler | undefined {\n return this.handlers.get(handleName);\n }\n\n /**\n * Send a message to a registered handler by handleName.\n * Delegates to xpcCenter.exec() which handles both main-process and renderer targets.\n */\n async send(handleName: string, params?: any): Promise<any> {\n return xpcCenter.exec(handleName, params);\n }\n}\n\nexport const xpcMain = new XpcMain();\n","import { ipcMain, webContents } from 'electron';\nimport { XpcPayload } from '../shared/xpc.type';\nimport { XpcTask } from './xpcTask.helper';\nimport { generateXpcId } from './xpcId.helper';\nimport { pathMainHelper } from '../../pathHelper/main/pathMain.helper';\nimport { xpcMain } from './xpcMain.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\n/**\n * XpcCenter: runs in the main process.\n * - Listens for __xpc_register__: renderer registers a handleName, center stores {handleName → webContentsId}\n * - Listens for __xpc_exec__ (ipcMain.handle): renderer invokes exec, center forwards to target renderer,\n * blocks via semaphore until __xpc_finish__ is received, then returns result.\n * - Listens for __xpc_finish__: target renderer finished execution, unblocks the pending task.\n */\nclass XpcCenter {\n /** handleName → webContentsId */\n private registry = new Map<string, number>();\n /** task.id → XpcTask (with semaphore block/unblock) */\n private pendingTasks = new Map<string, XpcTask>();\n\n init(): void {\n pathMainHelper.init();\n this.setupListeners();\n }\n\n /**\n * Register a main-process handleName in the registry with webContentsId = 0.\n */\n registerMainHandler(handleName: string): void {\n this.registry.set(handleName, 0);\n }\n\n /**\n * Execute a handleName: if main-process handler, call directly;\n * otherwise forward to target renderer, block until __xpc_finish__.\n * Used by both ipcMain.handle(XPC_EXEC) and xpcMain.send().\n */\n async exec(handleName: string, params?: any): Promise<any> {\n const targetId = this.registry.get(handleName);\n if (targetId == null) {\n return null;\n }\n\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n };\n\n // targetId === 0 means the handler is registered in the main process\n if (targetId === 0) {\n const handler = xpcMain.getHandler(handleName);\n if (!handler) {\n return null;\n }\n try {\n return await handler(payload);\n } catch (_e) {\n return null;\n }\n }\n\n const target = webContents.fromId(targetId);\n if (!target || target.isDestroyed() || target.isCrashed()) {\n return null;\n }\n\n // Create semaphore-blocked task\n const task = new XpcTask(payload);\n\n this.pendingTasks.set(task.id, task);\n\n // Forward handleName event + payload to target renderer\n target.send(handleName, payload);\n\n // Block until __xpc_finish__ unblocks\n await task.block();\n this.pendingTasks.delete(task.id);\n\n return task.toPayload().ret ?? null;\n }\n\n private setupListeners(): void {\n // Renderer registers a handleName\n ipcMain.on(XPC_REGISTER, (event, payload: { handleName: string }) => {\n this.registry.set(payload.handleName, event.sender.id);\n });\n\n // Renderer invokes exec via IPC\n ipcMain.handle(XPC_EXEC, async (_event, payload: XpcPayload): Promise<any> => {\n return this.exec(payload.handleName, payload.params);\n });\n\n // Target renderer finished execution, unblock pending task\n ipcMain.on(XPC_FINISH, (_event, payload: XpcPayload) => {\n const task = this.pendingTasks.get(payload.id);\n if (task) {\n task.ret = payload.ret ?? null;\n task.unblock();\n }\n });\n }\n}\n\nexport const xpcCenter = new XpcCenter();\n","/**\n * Prefix for all auto-registered xpc handler channels.\n * Channel format: `xpc:ClassName/methodName`\n */\nexport const XPC_HANDLER_PREFIX = 'xpc:';\n\n/**\n * Build the xpc channel name from class name and method name.\n * e.g. buildChannel('UserTable', 'getUserList') => 'xpc:UserTable/getUserList'\n */\nexport const buildXpcChannel = (className: string, methodName: string): string => {\n return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;\n};\n\n/**\n * Extract own method names from a class prototype, excluding constructor.\n */\nexport const getHandlerMethodNames = (prototype: object): string[] => {\n const names: string[] = [];\n const keys = Object.getOwnPropertyNames(prototype);\n for (const key of keys) {\n if (key === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n if (descriptor && typeof descriptor.value === 'function') {\n names.push(key);\n }\n }\n return names;\n};\n\n/**\n * Constraint: handler methods must accept 0 or 1 parameter.\n * Methods with 2+ parameters will fail type checking.\n */\nexport type XpcHandlerMethod = (() => Promise<any>) | ((params: any) => Promise<any>);\n\n/**\n * Helper: checks if a function type has at most 1 parameter.\n * Returns the function type itself if valid, `never` otherwise.\n */\ntype AssertSingleParam<F> =\n F extends () => any ? F :\n F extends (p: any) => any ?\n F extends (p: any, q: any, ...rest: any[]) => any ? never : F\n : never;\n\n/**\n * Utility type: extracts the method signatures from a handler class,\n * turning each method into an emitter-compatible signature.\n * Methods with 2+ parameters are mapped to `never`, causing a compile error on use.\n */\nexport type XpcEmitterOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:\n AssertSingleParam<T[K]> extends never\n ? never\n : T[K] extends (params: infer P) => any\n ? (params: P) => Promise<any>\n : () => Promise<any>;\n};\n","import { XpcPayload } from '../shared/xpc.type';\nimport { buildXpcChannel, getHandlerMethodNames } from '../shared/xpcHandler.type';\nimport { xpcMain } from './xpcMain.helper';\n\n/**\n * Base class for main-process xpc handlers.\n * Subclass this and define async methods — they will be auto-registered\n * as xpc handlers with channel `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcMainHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTable = new UserTable();\n * // auto-registers handler for 'xpc:UserTable/getUserList'\n * ```\n */\nexport class XpcMainHandler {\n constructor() {\n const className = this.constructor.name;\n const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));\n for (const methodName of methodNames) {\n const channel = buildXpcChannel(className, methodName);\n const method = (this as any)[methodName].bind(this);\n xpcMain.handle(channel, async (payload: XpcPayload) => {\n return await method(payload.params);\n });\n }\n }\n}\n","import { buildXpcChannel, XpcEmitterOf } from '../shared/xpcHandler.type';\nimport { xpcMain } from './xpcMain.helper';\n\n/**\n * Create a type-safe emitter proxy for a main-process xpc handler.\n * The emitter mirrors the handler's method signatures, but each call\n * sends a message via xpcMain.send() to `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcMainHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTableEmitter = createXpcMainEmitter<UserTable>('UserTable');\n * const list = await userTableEmitter.getUserList({ page: 1 });\n * // sends to 'xpc:UserTable/getUserList'\n * ```\n */\nexport const createXpcMainEmitter = <T>(className: string): XpcEmitterOf<T> => {\n return new Proxy({} as XpcEmitterOf<T>, {\n get(_target, prop: string) {\n const channel = buildXpcChannel(className, prop);\n return (params?: any) => xpcMain.send(channel, params);\n },\n });\n};\n"]}
|
|
@@ -21,4 +21,53 @@ declare const xpcHandlers: Map<string, XpcHandler>;
|
|
|
21
21
|
/** The xpcRenderer API instance */
|
|
22
22
|
declare const xpcRenderer: XpcRendererApi;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Base class for preload-process xpc handlers.
|
|
26
|
+
* Subclass this and define async methods — they will be auto-registered
|
|
27
|
+
* as xpc handlers with channel `xpc:ClassName/methodName`.
|
|
28
|
+
*
|
|
29
|
+
* Example:
|
|
30
|
+
* ```ts
|
|
31
|
+
* class UserTable extends XpcPreloadHandler {
|
|
32
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
33
|
+
* }
|
|
34
|
+
* const userTable = new UserTable();
|
|
35
|
+
* // auto-registers handler for 'xpc:UserTable/getUserList'
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare class XpcPreloadHandler {
|
|
39
|
+
constructor();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Helper: checks if a function type has at most 1 parameter.
|
|
44
|
+
* Returns the function type itself if valid, `never` otherwise.
|
|
45
|
+
*/
|
|
46
|
+
type AssertSingleParam<F> = F extends () => any ? F : F extends (p: any) => any ? F extends (p: any, q: any, ...rest: any[]) => any ? never : F : never;
|
|
47
|
+
/**
|
|
48
|
+
* Utility type: extracts the method signatures from a handler class,
|
|
49
|
+
* turning each method into an emitter-compatible signature.
|
|
50
|
+
* Methods with 2+ parameters are mapped to `never`, causing a compile error on use.
|
|
51
|
+
*/
|
|
52
|
+
type XpcEmitterOf<T> = {
|
|
53
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: AssertSingleParam<T[K]> extends never ? never : T[K] extends (params: infer P) => any ? (params: P) => Promise<any> : () => Promise<any>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a type-safe emitter proxy for a preload-process xpc handler.
|
|
58
|
+
* The emitter mirrors the handler's method signatures, but each call
|
|
59
|
+
* sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.
|
|
60
|
+
*
|
|
61
|
+
* Example:
|
|
62
|
+
* ```ts
|
|
63
|
+
* class UserTable extends XpcPreloadHandler {
|
|
64
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
65
|
+
* }
|
|
66
|
+
* const userTableEmitter = createXpcPreloadEmitter<UserTable>('UserTable');
|
|
67
|
+
* const list = await userTableEmitter.getUserList({ page: 1 });
|
|
68
|
+
* // sends to 'xpc:UserTable/getUserList'
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare const createXpcPreloadEmitter: <T>(className: string) => XpcEmitterOf<T>;
|
|
72
|
+
|
|
73
|
+
export { type XpcEmitterOf, type XpcPayload, XpcPreloadHandler, type XpcRendererApi, createXpcPreloadEmitter, xpcHandlers, xpcRenderer };
|
|
@@ -21,4 +21,53 @@ declare const xpcHandlers: Map<string, XpcHandler>;
|
|
|
21
21
|
/** The xpcRenderer API instance */
|
|
22
22
|
declare const xpcRenderer: XpcRendererApi;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Base class for preload-process xpc handlers.
|
|
26
|
+
* Subclass this and define async methods — they will be auto-registered
|
|
27
|
+
* as xpc handlers with channel `xpc:ClassName/methodName`.
|
|
28
|
+
*
|
|
29
|
+
* Example:
|
|
30
|
+
* ```ts
|
|
31
|
+
* class UserTable extends XpcPreloadHandler {
|
|
32
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
33
|
+
* }
|
|
34
|
+
* const userTable = new UserTable();
|
|
35
|
+
* // auto-registers handler for 'xpc:UserTable/getUserList'
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare class XpcPreloadHandler {
|
|
39
|
+
constructor();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Helper: checks if a function type has at most 1 parameter.
|
|
44
|
+
* Returns the function type itself if valid, `never` otherwise.
|
|
45
|
+
*/
|
|
46
|
+
type AssertSingleParam<F> = F extends () => any ? F : F extends (p: any) => any ? F extends (p: any, q: any, ...rest: any[]) => any ? never : F : never;
|
|
47
|
+
/**
|
|
48
|
+
* Utility type: extracts the method signatures from a handler class,
|
|
49
|
+
* turning each method into an emitter-compatible signature.
|
|
50
|
+
* Methods with 2+ parameters are mapped to `never`, causing a compile error on use.
|
|
51
|
+
*/
|
|
52
|
+
type XpcEmitterOf<T> = {
|
|
53
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: AssertSingleParam<T[K]> extends never ? never : T[K] extends (params: infer P) => any ? (params: P) => Promise<any> : () => Promise<any>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a type-safe emitter proxy for a preload-process xpc handler.
|
|
58
|
+
* The emitter mirrors the handler's method signatures, but each call
|
|
59
|
+
* sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.
|
|
60
|
+
*
|
|
61
|
+
* Example:
|
|
62
|
+
* ```ts
|
|
63
|
+
* class UserTable extends XpcPreloadHandler {
|
|
64
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
65
|
+
* }
|
|
66
|
+
* const userTableEmitter = createXpcPreloadEmitter<UserTable>('UserTable');
|
|
67
|
+
* const list = await userTableEmitter.getUserList({ page: 1 });
|
|
68
|
+
* // sends to 'xpc:UserTable/getUserList'
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare const createXpcPreloadEmitter: <T>(className: string) => XpcEmitterOf<T>;
|
|
72
|
+
|
|
73
|
+
export { type XpcEmitterOf, type XpcPayload, XpcPreloadHandler, type XpcRendererApi, createXpcPreloadEmitter, xpcHandlers, xpcRenderer };
|
|
@@ -4,7 +4,7 @@ var electron = require('electron');
|
|
|
4
4
|
|
|
5
5
|
// src/xpc/preload/xpcPreload.helper.ts
|
|
6
6
|
|
|
7
|
-
// src/xpc/preload/
|
|
7
|
+
// src/xpc/preload/xpcId.helper.ts
|
|
8
8
|
var prefix = Math.random().toString(36).slice(2, 8);
|
|
9
9
|
var counter = 0;
|
|
10
10
|
var generateXpcId = () => {
|
|
@@ -74,6 +74,51 @@ if (process.contextIsolated) {
|
|
|
74
74
|
globalThis.xpcRenderer = xpcRenderer;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// src/xpc/shared/xpcHandler.type.ts
|
|
78
|
+
var XPC_HANDLER_PREFIX = "xpc:";
|
|
79
|
+
var buildXpcChannel = (className, methodName) => {
|
|
80
|
+
return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;
|
|
81
|
+
};
|
|
82
|
+
var getHandlerMethodNames = (prototype) => {
|
|
83
|
+
const names = [];
|
|
84
|
+
const keys = Object.getOwnPropertyNames(prototype);
|
|
85
|
+
for (const key of keys) {
|
|
86
|
+
if (key === "constructor") continue;
|
|
87
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
88
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
89
|
+
names.push(key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return names;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/xpc/preload/xpcPreload.handler.ts
|
|
96
|
+
var XpcPreloadHandler = class {
|
|
97
|
+
constructor() {
|
|
98
|
+
const className = this.constructor.name;
|
|
99
|
+
const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));
|
|
100
|
+
for (const methodName of methodNames) {
|
|
101
|
+
const channel = buildXpcChannel(className, methodName);
|
|
102
|
+
const method = this[methodName].bind(this);
|
|
103
|
+
xpcRenderer.handle(channel, async (payload) => {
|
|
104
|
+
return await method(payload.params);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/xpc/preload/xpcPreload.emitter.ts
|
|
111
|
+
var createXpcPreloadEmitter = (className) => {
|
|
112
|
+
return new Proxy({}, {
|
|
113
|
+
get(_target, prop) {
|
|
114
|
+
const channel = buildXpcChannel(className, prop);
|
|
115
|
+
return (params) => xpcRenderer.send(channel, params);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
exports.XpcPreloadHandler = XpcPreloadHandler;
|
|
121
|
+
exports.createXpcPreloadEmitter = createXpcPreloadEmitter;
|
|
77
122
|
exports.xpcHandlers = xpcHandlers;
|
|
78
123
|
exports.xpcRenderer = xpcRenderer;
|
|
79
124
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/xpc/preload/xpc-id.helper.ts","../../../src/xpc/preload/xpcPreload.helper.ts"],"names":["ipcRenderer","contextBridge"],"mappings":";;;;;;;AAKA,IAAM,MAAA,GAAS,KAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACpD,IAAI,OAAA,GAAU,CAAA;AAEP,IAAM,gBAAgB,MAAc;AACzC,EAAA,OAAO,KAAK,MAAM,CAAA,CAAA,EAAA,CAAK,EAAE,OAAA,EAAS,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA;AAChD,CAAA;;;ACNA,IAAM,YAAA,GAAe,kBAAA;AACrB,IAAM,QAAA,GAAW,cAAA;AACjB,IAAM,UAAA,GAAa,gBAAA;AAKZ,IAAM,WAAA,uBAAkB,GAAA;AAS/B,IAAM,MAAA,GAAS,CAAC,UAAA,EAAoB,OAAA,KAA8B;AAChE,EAAA,WAAA,CAAY,GAAA,CAAI,YAAY,OAAO,CAAA;AAGnC,EAAAA,oBAAA,CAAY,IAAA,CAAK,YAAA,EAAc,EAAE,UAAA,EAAY,CAAA;AAG7C,EAAAA,oBAAA,CAAY,EAAA,CAAG,UAAA,EAAY,OAAO,MAAA,EAAQ,OAAA,KAAwB;AAChE,IAAA,IAAI,GAAA,GAAW,IAAA;AACf,IAAA,MAAM,YAAA,GAAe,WAAA,CAAY,GAAA,CAAI,UAAU,CAAA;AAC/C,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAI;AACF,QAAA,GAAA,GAAM,MAAM,aAAa,OAAO,CAAA;AAAA,MAClC,SAAS,EAAA,EAAI;AACX,QAAA,GAAA,GAAM,IAAA;AAAA,MACR;AAAA,IACF;AAEA,IAAAA,oBAAA,CAAY,KAAK,UAAA,EAAY;AAAA,MAC3B,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,YAAY,OAAA,CAAQ,UAAA;AAAA,MACpB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB;AAAA,KACa,CAAA;AAAA,EACjB,CAAC,CAAA;AACH,CAAA;AAKA,IAAM,YAAA,GAAe,CAAC,UAAA,KAA6B;AACjD,EAAA,WAAA,CAAY,OAAO,UAAU,CAAA;AAC7B,EAAAA,oBAAA,CAAY,mBAAmB,UAAU,CAAA;AAC3C,CAAA;AAOA,IAAM,IAAA,GAAO,OAAO,UAAA,EAAoB,MAAA,KAA+B;AACrE,EAAA,MAAM,OAAA,GAAsB;AAAA,IAC1B,IAAI,aAAA,EAAc;AAAA,IAClB,UAAA;AAAA,IACA,MAAA;AAAA,IACA,GAAA,EAAK;AAAA,GACP;AAEA,EAAA,OAAO,MAAMA,oBAAA,CAAY,MAAA,CAAO,QAAA,EAAU,OAAO,CAAA;AACnD,CAAA;AAKA,IAAM,uBAAuB,MAAsB;AACjD,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,CAAC,UAAA,EAAoB,OAAA,KAAyD;AACpF,MAAA,MAAA,CAAO,YAAY,OAAO,CAAA;AAAA,IAC5B,CAAA;AAAA,IACA,YAAA,EAAc,CAAC,UAAA,KAA6B;AAC1C,MAAA,YAAA,CAAa,UAAU,CAAA;AAAA,IACzB,CAAA;AAAA,IACA,IAAA,EAAM,CAAC,UAAA,EAAoB,MAAA,KAA+B;AACxD,MAAA,OAAO,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,IAChC;AAAA,GACF;AACF,CAAA;AAGO,IAAM,cAA8B,oBAAA;AAG3C,IAAI,QAAQ,eAAA,EAAiB;AAC3B,EAAA,IAAI;AACF,IAAAC,sBAAA,CAAc,iBAAA,CAAkB,eAAe,WAAW,CAAA;AAAA,EAC5D,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,0CAA0C,KAAK,CAAA;AAAA,EAC/D;AACF,CAAA,MAAO;AACL,EAAC,WAAmB,WAAA,GAAc,WAAA;AACpC","file":"index.js","sourcesContent":["/**\n * High-performance process-unique ID generator for renderer process.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `r-${prefix}-${(++counter).toString(36)}`;\n};\n","import { contextBridge, ipcRenderer } from 'electron';\nimport { XpcPayload, XpcRendererApi } from '../shared/xpc.type';\nimport { generateXpcId } from './xpc-id.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/** Global handlers map, extracted from XpcRenderer class */\nexport const xpcHandlers = new Map<string, XpcHandler>();\n\nexport type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Register a handleName with the main process and bind a local async handler.\n * When another renderer calls send() with this handleName, xpcCenter will forward\n * the payload to this renderer, the handler executes, and result is sent back via __xpc_finish__.\n */\nconst handle = (handleName: string, handler: XpcHandler): void => {\n xpcHandlers.set(handleName, handler);\n\n // Notify main process about this registration\n ipcRenderer.send(XPC_REGISTER, { handleName });\n\n // Listen for incoming handleName events forwarded by xpcCenter\n ipcRenderer.on(handleName, async (_event, payload: XpcPayload) => {\n let ret: any = null;\n const localHandler = xpcHandlers.get(handleName);\n if (localHandler) {\n try {\n ret = await localHandler(payload);\n } catch (_e) {\n ret = null;\n }\n }\n // Send __xpc_finish__ back to main with result\n ipcRenderer.send(XPC_FINISH, {\n id: payload.id,\n handleName: payload.handleName,\n params: payload.params,\n ret,\n } as XpcPayload);\n });\n};\n\n/**\n * Remove a registered handler.\n */\nconst removeHandle = (handleName: string): void => {\n xpcHandlers.delete(handleName);\n ipcRenderer.removeAllListeners(handleName);\n};\n\n/**\n * Send a message to another renderer (or any registered handler) via main process.\n * Uses ipcRenderer.invoke(__xpc_exec__) which blocks until the target finishes.\n * Returns the ret value from the target handler, or null.\n */\nconst send = async (handleName: string, params?: any): Promise<any> => {\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n ret: null,\n };\n\n return await ipcRenderer.invoke(XPC_EXEC, payload);\n};\n\n/**\n * Returns a contextBridge-safe object for exposeInMainWorld.\n */\nconst createXpcRendererApi = (): XpcRendererApi => {\n return {\n handle: (handleName: string, handler: (payload: XpcPayload) => Promise<any>): void => {\n handle(handleName, handler);\n },\n removeHandle: (handleName: string): void => {\n removeHandle(handleName);\n },\n send: (handleName: string, params?: any): Promise<any> => {\n return send(handleName, params);\n },\n };\n};\n\n/** The xpcRenderer API instance */\nexport const xpcRenderer: XpcRendererApi = createXpcRendererApi();\n\n// Auto-expose xpcRenderer to window on import\nif (process.contextIsolated) {\n try {\n contextBridge.exposeInMainWorld('xpcRenderer', xpcRenderer);\n } catch (error) {\n console.error('[xpcPreload] exposeInMainWorld failed:', error);\n }\n} else {\n (globalThis as any).xpcRenderer = xpcRenderer;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/xpc/preload/xpcId.helper.ts","../../../src/xpc/preload/xpcPreload.helper.ts","../../../src/xpc/shared/xpcHandler.type.ts","../../../src/xpc/preload/xpcPreload.handler.ts","../../../src/xpc/preload/xpcPreload.emitter.ts"],"names":["ipcRenderer","contextBridge"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * High-performance process-unique ID generator for renderer process.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `r-${prefix}-${(++counter).toString(36)}`;\n};\n","import { contextBridge, ipcRenderer } from 'electron';\nimport { XpcPayload, XpcRendererApi } from '../shared/xpc.type';\nimport { generateXpcId } from './xpcId.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/** Global handlers map, extracted from XpcRenderer class */\nexport const xpcHandlers = new Map<string, XpcHandler>();\n\nexport type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Register a handleName with the main process and bind a local async handler.\n * When another renderer calls send() with this handleName, xpcCenter will forward\n * the payload to this renderer, the handler executes, and result is sent back via __xpc_finish__.\n */\nconst handle = (handleName: string, handler: XpcHandler): void => {\n xpcHandlers.set(handleName, handler);\n\n // Notify main process about this registration\n ipcRenderer.send(XPC_REGISTER, { handleName });\n\n // Listen for incoming handleName events forwarded by xpcCenter\n ipcRenderer.on(handleName, async (_event, payload: XpcPayload) => {\n let ret: any = null;\n const localHandler = xpcHandlers.get(handleName);\n if (localHandler) {\n try {\n ret = await localHandler(payload);\n } catch (_e) {\n ret = null;\n }\n }\n // Send __xpc_finish__ back to main with result\n ipcRenderer.send(XPC_FINISH, {\n id: payload.id,\n handleName: payload.handleName,\n params: payload.params,\n ret,\n } as XpcPayload);\n });\n};\n\n/**\n * Remove a registered handler.\n */\nconst removeHandle = (handleName: string): void => {\n xpcHandlers.delete(handleName);\n ipcRenderer.removeAllListeners(handleName);\n};\n\n/**\n * Send a message to another renderer (or any registered handler) via main process.\n * Uses ipcRenderer.invoke(__xpc_exec__) which blocks until the target finishes.\n * Returns the ret value from the target handler, or null.\n */\nconst send = async (handleName: string, params?: any): Promise<any> => {\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n ret: null,\n };\n\n return await ipcRenderer.invoke(XPC_EXEC, payload);\n};\n\n/**\n * Returns a contextBridge-safe object for exposeInMainWorld.\n */\nconst createXpcRendererApi = (): XpcRendererApi => {\n return {\n handle: (handleName: string, handler: (payload: XpcPayload) => Promise<any>): void => {\n handle(handleName, handler);\n },\n removeHandle: (handleName: string): void => {\n removeHandle(handleName);\n },\n send: (handleName: string, params?: any): Promise<any> => {\n return send(handleName, params);\n },\n };\n};\n\n/** The xpcRenderer API instance */\nexport const xpcRenderer: XpcRendererApi = createXpcRendererApi();\n\n// Auto-expose xpcRenderer to window on import\nif (process.contextIsolated) {\n try {\n contextBridge.exposeInMainWorld('xpcRenderer', xpcRenderer);\n } catch (error) {\n console.error('[xpcPreload] exposeInMainWorld failed:', error);\n }\n} else {\n (globalThis as any).xpcRenderer = xpcRenderer;\n}\n","/**\n * Prefix for all auto-registered xpc handler channels.\n * Channel format: `xpc:ClassName/methodName`\n */\nexport const XPC_HANDLER_PREFIX = 'xpc:';\n\n/**\n * Build the xpc channel name from class name and method name.\n * e.g. buildChannel('UserTable', 'getUserList') => 'xpc:UserTable/getUserList'\n */\nexport const buildXpcChannel = (className: string, methodName: string): string => {\n return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;\n};\n\n/**\n * Extract own method names from a class prototype, excluding constructor.\n */\nexport const getHandlerMethodNames = (prototype: object): string[] => {\n const names: string[] = [];\n const keys = Object.getOwnPropertyNames(prototype);\n for (const key of keys) {\n if (key === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n if (descriptor && typeof descriptor.value === 'function') {\n names.push(key);\n }\n }\n return names;\n};\n\n/**\n * Constraint: handler methods must accept 0 or 1 parameter.\n * Methods with 2+ parameters will fail type checking.\n */\nexport type XpcHandlerMethod = (() => Promise<any>) | ((params: any) => Promise<any>);\n\n/**\n * Helper: checks if a function type has at most 1 parameter.\n * Returns the function type itself if valid, `never` otherwise.\n */\ntype AssertSingleParam<F> =\n F extends () => any ? F :\n F extends (p: any) => any ?\n F extends (p: any, q: any, ...rest: any[]) => any ? never : F\n : never;\n\n/**\n * Utility type: extracts the method signatures from a handler class,\n * turning each method into an emitter-compatible signature.\n * Methods with 2+ parameters are mapped to `never`, causing a compile error on use.\n */\nexport type XpcEmitterOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:\n AssertSingleParam<T[K]> extends never\n ? never\n : T[K] extends (params: infer P) => any\n ? (params: P) => Promise<any>\n : () => Promise<any>;\n};\n","import { XpcPayload } from '../shared/xpc.type';\nimport { buildXpcChannel, getHandlerMethodNames } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcPreload.helper';\n\n/**\n * Base class for preload-process xpc handlers.\n * Subclass this and define async methods — they will be auto-registered\n * as xpc handlers with channel `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcPreloadHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTable = new UserTable();\n * // auto-registers handler for 'xpc:UserTable/getUserList'\n * ```\n */\nexport class XpcPreloadHandler {\n constructor() {\n const className = this.constructor.name;\n const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));\n for (const methodName of methodNames) {\n const channel = buildXpcChannel(className, methodName);\n const method = (this as any)[methodName].bind(this);\n xpcRenderer.handle(channel, async (payload: XpcPayload) => {\n return await method(payload.params);\n });\n }\n }\n}\n","import { buildXpcChannel, XpcEmitterOf } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcPreload.helper';\n\n/**\n * Create a type-safe emitter proxy for a preload-process xpc handler.\n * The emitter mirrors the handler's method signatures, but each call\n * sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcPreloadHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTableEmitter = createXpcPreloadEmitter<UserTable>('UserTable');\n * const list = await userTableEmitter.getUserList({ page: 1 });\n * // sends to 'xpc:UserTable/getUserList'\n * ```\n */\nexport const createXpcPreloadEmitter = <T>(className: string): XpcEmitterOf<T> => {\n return new Proxy({} as XpcEmitterOf<T>, {\n get(_target, prop: string) {\n const channel = buildXpcChannel(className, prop);\n return (params?: any) => xpcRenderer.send(channel, params);\n },\n });\n};\n"]}
|
|
@@ -2,7 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron';
|
|
|
2
2
|
|
|
3
3
|
// src/xpc/preload/xpcPreload.helper.ts
|
|
4
4
|
|
|
5
|
-
// src/xpc/preload/
|
|
5
|
+
// src/xpc/preload/xpcId.helper.ts
|
|
6
6
|
var prefix = Math.random().toString(36).slice(2, 8);
|
|
7
7
|
var counter = 0;
|
|
8
8
|
var generateXpcId = () => {
|
|
@@ -72,6 +72,49 @@ if (process.contextIsolated) {
|
|
|
72
72
|
globalThis.xpcRenderer = xpcRenderer;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
// src/xpc/shared/xpcHandler.type.ts
|
|
76
|
+
var XPC_HANDLER_PREFIX = "xpc:";
|
|
77
|
+
var buildXpcChannel = (className, methodName) => {
|
|
78
|
+
return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;
|
|
79
|
+
};
|
|
80
|
+
var getHandlerMethodNames = (prototype) => {
|
|
81
|
+
const names = [];
|
|
82
|
+
const keys = Object.getOwnPropertyNames(prototype);
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
if (key === "constructor") continue;
|
|
85
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
86
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
87
|
+
names.push(key);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return names;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/xpc/preload/xpcPreload.handler.ts
|
|
94
|
+
var XpcPreloadHandler = class {
|
|
95
|
+
constructor() {
|
|
96
|
+
const className = this.constructor.name;
|
|
97
|
+
const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));
|
|
98
|
+
for (const methodName of methodNames) {
|
|
99
|
+
const channel = buildXpcChannel(className, methodName);
|
|
100
|
+
const method = this[methodName].bind(this);
|
|
101
|
+
xpcRenderer.handle(channel, async (payload) => {
|
|
102
|
+
return await method(payload.params);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/xpc/preload/xpcPreload.emitter.ts
|
|
109
|
+
var createXpcPreloadEmitter = (className) => {
|
|
110
|
+
return new Proxy({}, {
|
|
111
|
+
get(_target, prop) {
|
|
112
|
+
const channel = buildXpcChannel(className, prop);
|
|
113
|
+
return (params) => xpcRenderer.send(channel, params);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export { XpcPreloadHandler, createXpcPreloadEmitter, xpcHandlers, xpcRenderer };
|
|
76
119
|
//# sourceMappingURL=index.mjs.map
|
|
77
120
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/xpc/preload/xpc-id.helper.ts","../../../src/xpc/preload/xpcPreload.helper.ts"],"names":[],"mappings":";;;;;AAKA,IAAM,MAAA,GAAS,KAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AACpD,IAAI,OAAA,GAAU,CAAA;AAEP,IAAM,gBAAgB,MAAc;AACzC,EAAA,OAAO,KAAK,MAAM,CAAA,CAAA,EAAA,CAAK,EAAE,OAAA,EAAS,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA;AAChD,CAAA;;;ACNA,IAAM,YAAA,GAAe,kBAAA;AACrB,IAAM,QAAA,GAAW,cAAA;AACjB,IAAM,UAAA,GAAa,gBAAA;AAKZ,IAAM,WAAA,uBAAkB,GAAA;AAS/B,IAAM,MAAA,GAAS,CAAC,UAAA,EAAoB,OAAA,KAA8B;AAChE,EAAA,WAAA,CAAY,GAAA,CAAI,YAAY,OAAO,CAAA;AAGnC,EAAA,WAAA,CAAY,IAAA,CAAK,YAAA,EAAc,EAAE,UAAA,EAAY,CAAA;AAG7C,EAAA,WAAA,CAAY,EAAA,CAAG,UAAA,EAAY,OAAO,MAAA,EAAQ,OAAA,KAAwB;AAChE,IAAA,IAAI,GAAA,GAAW,IAAA;AACf,IAAA,MAAM,YAAA,GAAe,WAAA,CAAY,GAAA,CAAI,UAAU,CAAA;AAC/C,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,IAAI;AACF,QAAA,GAAA,GAAM,MAAM,aAAa,OAAO,CAAA;AAAA,MAClC,SAAS,EAAA,EAAI;AACX,QAAA,GAAA,GAAM,IAAA;AAAA,MACR;AAAA,IACF;AAEA,IAAA,WAAA,CAAY,KAAK,UAAA,EAAY;AAAA,MAC3B,IAAI,OAAA,CAAQ,EAAA;AAAA,MACZ,YAAY,OAAA,CAAQ,UAAA;AAAA,MACpB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB;AAAA,KACa,CAAA;AAAA,EACjB,CAAC,CAAA;AACH,CAAA;AAKA,IAAM,YAAA,GAAe,CAAC,UAAA,KAA6B;AACjD,EAAA,WAAA,CAAY,OAAO,UAAU,CAAA;AAC7B,EAAA,WAAA,CAAY,mBAAmB,UAAU,CAAA;AAC3C,CAAA;AAOA,IAAM,IAAA,GAAO,OAAO,UAAA,EAAoB,MAAA,KAA+B;AACrE,EAAA,MAAM,OAAA,GAAsB;AAAA,IAC1B,IAAI,aAAA,EAAc;AAAA,IAClB,UAAA;AAAA,IACA,MAAA;AAAA,IACA,GAAA,EAAK;AAAA,GACP;AAEA,EAAA,OAAO,MAAM,WAAA,CAAY,MAAA,CAAO,QAAA,EAAU,OAAO,CAAA;AACnD,CAAA;AAKA,IAAM,uBAAuB,MAAsB;AACjD,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,CAAC,UAAA,EAAoB,OAAA,KAAyD;AACpF,MAAA,MAAA,CAAO,YAAY,OAAO,CAAA;AAAA,IAC5B,CAAA;AAAA,IACA,YAAA,EAAc,CAAC,UAAA,KAA6B;AAC1C,MAAA,YAAA,CAAa,UAAU,CAAA;AAAA,IACzB,CAAA;AAAA,IACA,IAAA,EAAM,CAAC,UAAA,EAAoB,MAAA,KAA+B;AACxD,MAAA,OAAO,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,IAChC;AAAA,GACF;AACF,CAAA;AAGO,IAAM,cAA8B,oBAAA;AAG3C,IAAI,QAAQ,eAAA,EAAiB;AAC3B,EAAA,IAAI;AACF,IAAA,aAAA,CAAc,iBAAA,CAAkB,eAAe,WAAW,CAAA;AAAA,EAC5D,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,0CAA0C,KAAK,CAAA;AAAA,EAC/D;AACF,CAAA,MAAO;AACL,EAAC,WAAmB,WAAA,GAAc,WAAA;AACpC","file":"index.mjs","sourcesContent":["/**\n * High-performance process-unique ID generator for renderer process.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `r-${prefix}-${(++counter).toString(36)}`;\n};\n","import { contextBridge, ipcRenderer } from 'electron';\nimport { XpcPayload, XpcRendererApi } from '../shared/xpc.type';\nimport { generateXpcId } from './xpc-id.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/** Global handlers map, extracted from XpcRenderer class */\nexport const xpcHandlers = new Map<string, XpcHandler>();\n\nexport type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Register a handleName with the main process and bind a local async handler.\n * When another renderer calls send() with this handleName, xpcCenter will forward\n * the payload to this renderer, the handler executes, and result is sent back via __xpc_finish__.\n */\nconst handle = (handleName: string, handler: XpcHandler): void => {\n xpcHandlers.set(handleName, handler);\n\n // Notify main process about this registration\n ipcRenderer.send(XPC_REGISTER, { handleName });\n\n // Listen for incoming handleName events forwarded by xpcCenter\n ipcRenderer.on(handleName, async (_event, payload: XpcPayload) => {\n let ret: any = null;\n const localHandler = xpcHandlers.get(handleName);\n if (localHandler) {\n try {\n ret = await localHandler(payload);\n } catch (_e) {\n ret = null;\n }\n }\n // Send __xpc_finish__ back to main with result\n ipcRenderer.send(XPC_FINISH, {\n id: payload.id,\n handleName: payload.handleName,\n params: payload.params,\n ret,\n } as XpcPayload);\n });\n};\n\n/**\n * Remove a registered handler.\n */\nconst removeHandle = (handleName: string): void => {\n xpcHandlers.delete(handleName);\n ipcRenderer.removeAllListeners(handleName);\n};\n\n/**\n * Send a message to another renderer (or any registered handler) via main process.\n * Uses ipcRenderer.invoke(__xpc_exec__) which blocks until the target finishes.\n * Returns the ret value from the target handler, or null.\n */\nconst send = async (handleName: string, params?: any): Promise<any> => {\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n ret: null,\n };\n\n return await ipcRenderer.invoke(XPC_EXEC, payload);\n};\n\n/**\n * Returns a contextBridge-safe object for exposeInMainWorld.\n */\nconst createXpcRendererApi = (): XpcRendererApi => {\n return {\n handle: (handleName: string, handler: (payload: XpcPayload) => Promise<any>): void => {\n handle(handleName, handler);\n },\n removeHandle: (handleName: string): void => {\n removeHandle(handleName);\n },\n send: (handleName: string, params?: any): Promise<any> => {\n return send(handleName, params);\n },\n };\n};\n\n/** The xpcRenderer API instance */\nexport const xpcRenderer: XpcRendererApi = createXpcRendererApi();\n\n// Auto-expose xpcRenderer to window on import\nif (process.contextIsolated) {\n try {\n contextBridge.exposeInMainWorld('xpcRenderer', xpcRenderer);\n } catch (error) {\n console.error('[xpcPreload] exposeInMainWorld failed:', error);\n }\n} else {\n (globalThis as any).xpcRenderer = xpcRenderer;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/xpc/preload/xpcId.helper.ts","../../../src/xpc/preload/xpcPreload.helper.ts","../../../src/xpc/shared/xpcHandler.type.ts","../../../src/xpc/preload/xpcPreload.handler.ts","../../../src/xpc/preload/xpcPreload.emitter.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * High-performance process-unique ID generator for renderer process.\n * Combines a random prefix (per process) with an incrementing counter.\n * Guaranteed unique within a single process lifetime.\n */\nconst prefix = Math.random().toString(36).slice(2, 8);\nlet counter = 0;\n\nexport const generateXpcId = (): string => {\n return `r-${prefix}-${(++counter).toString(36)}`;\n};\n","import { contextBridge, ipcRenderer } from 'electron';\nimport { XpcPayload, XpcRendererApi } from '../shared/xpc.type';\nimport { generateXpcId } from './xpcId.helper';\n\nconst XPC_REGISTER = '__xpc_register__';\nconst XPC_EXEC = '__xpc_exec__';\nconst XPC_FINISH = '__xpc_finish__';\n\ntype XpcHandler = (payload: XpcPayload) => Promise<any>;\n\n/** Global handlers map, extracted from XpcRenderer class */\nexport const xpcHandlers = new Map<string, XpcHandler>();\n\nexport type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Register a handleName with the main process and bind a local async handler.\n * When another renderer calls send() with this handleName, xpcCenter will forward\n * the payload to this renderer, the handler executes, and result is sent back via __xpc_finish__.\n */\nconst handle = (handleName: string, handler: XpcHandler): void => {\n xpcHandlers.set(handleName, handler);\n\n // Notify main process about this registration\n ipcRenderer.send(XPC_REGISTER, { handleName });\n\n // Listen for incoming handleName events forwarded by xpcCenter\n ipcRenderer.on(handleName, async (_event, payload: XpcPayload) => {\n let ret: any = null;\n const localHandler = xpcHandlers.get(handleName);\n if (localHandler) {\n try {\n ret = await localHandler(payload);\n } catch (_e) {\n ret = null;\n }\n }\n // Send __xpc_finish__ back to main with result\n ipcRenderer.send(XPC_FINISH, {\n id: payload.id,\n handleName: payload.handleName,\n params: payload.params,\n ret,\n } as XpcPayload);\n });\n};\n\n/**\n * Remove a registered handler.\n */\nconst removeHandle = (handleName: string): void => {\n xpcHandlers.delete(handleName);\n ipcRenderer.removeAllListeners(handleName);\n};\n\n/**\n * Send a message to another renderer (or any registered handler) via main process.\n * Uses ipcRenderer.invoke(__xpc_exec__) which blocks until the target finishes.\n * Returns the ret value from the target handler, or null.\n */\nconst send = async (handleName: string, params?: any): Promise<any> => {\n const payload: XpcPayload = {\n id: generateXpcId(),\n handleName,\n params,\n ret: null,\n };\n\n return await ipcRenderer.invoke(XPC_EXEC, payload);\n};\n\n/**\n * Returns a contextBridge-safe object for exposeInMainWorld.\n */\nconst createXpcRendererApi = (): XpcRendererApi => {\n return {\n handle: (handleName: string, handler: (payload: XpcPayload) => Promise<any>): void => {\n handle(handleName, handler);\n },\n removeHandle: (handleName: string): void => {\n removeHandle(handleName);\n },\n send: (handleName: string, params?: any): Promise<any> => {\n return send(handleName, params);\n },\n };\n};\n\n/** The xpcRenderer API instance */\nexport const xpcRenderer: XpcRendererApi = createXpcRendererApi();\n\n// Auto-expose xpcRenderer to window on import\nif (process.contextIsolated) {\n try {\n contextBridge.exposeInMainWorld('xpcRenderer', xpcRenderer);\n } catch (error) {\n console.error('[xpcPreload] exposeInMainWorld failed:', error);\n }\n} else {\n (globalThis as any).xpcRenderer = xpcRenderer;\n}\n","/**\n * Prefix for all auto-registered xpc handler channels.\n * Channel format: `xpc:ClassName/methodName`\n */\nexport const XPC_HANDLER_PREFIX = 'xpc:';\n\n/**\n * Build the xpc channel name from class name and method name.\n * e.g. buildChannel('UserTable', 'getUserList') => 'xpc:UserTable/getUserList'\n */\nexport const buildXpcChannel = (className: string, methodName: string): string => {\n return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;\n};\n\n/**\n * Extract own method names from a class prototype, excluding constructor.\n */\nexport const getHandlerMethodNames = (prototype: object): string[] => {\n const names: string[] = [];\n const keys = Object.getOwnPropertyNames(prototype);\n for (const key of keys) {\n if (key === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n if (descriptor && typeof descriptor.value === 'function') {\n names.push(key);\n }\n }\n return names;\n};\n\n/**\n * Constraint: handler methods must accept 0 or 1 parameter.\n * Methods with 2+ parameters will fail type checking.\n */\nexport type XpcHandlerMethod = (() => Promise<any>) | ((params: any) => Promise<any>);\n\n/**\n * Helper: checks if a function type has at most 1 parameter.\n * Returns the function type itself if valid, `never` otherwise.\n */\ntype AssertSingleParam<F> =\n F extends () => any ? F :\n F extends (p: any) => any ?\n F extends (p: any, q: any, ...rest: any[]) => any ? never : F\n : never;\n\n/**\n * Utility type: extracts the method signatures from a handler class,\n * turning each method into an emitter-compatible signature.\n * Methods with 2+ parameters are mapped to `never`, causing a compile error on use.\n */\nexport type XpcEmitterOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:\n AssertSingleParam<T[K]> extends never\n ? never\n : T[K] extends (params: infer P) => any\n ? (params: P) => Promise<any>\n : () => Promise<any>;\n};\n","import { XpcPayload } from '../shared/xpc.type';\nimport { buildXpcChannel, getHandlerMethodNames } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcPreload.helper';\n\n/**\n * Base class for preload-process xpc handlers.\n * Subclass this and define async methods — they will be auto-registered\n * as xpc handlers with channel `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcPreloadHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTable = new UserTable();\n * // auto-registers handler for 'xpc:UserTable/getUserList'\n * ```\n */\nexport class XpcPreloadHandler {\n constructor() {\n const className = this.constructor.name;\n const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));\n for (const methodName of methodNames) {\n const channel = buildXpcChannel(className, methodName);\n const method = (this as any)[methodName].bind(this);\n xpcRenderer.handle(channel, async (payload: XpcPayload) => {\n return await method(payload.params);\n });\n }\n }\n}\n","import { buildXpcChannel, XpcEmitterOf } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcPreload.helper';\n\n/**\n * Create a type-safe emitter proxy for a preload-process xpc handler.\n * The emitter mirrors the handler's method signatures, but each call\n * sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcPreloadHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTableEmitter = createXpcPreloadEmitter<UserTable>('UserTable');\n * const list = await userTableEmitter.getUserList({ page: 1 });\n * // sends to 'xpc:UserTable/getUserList'\n * ```\n */\nexport const createXpcPreloadEmitter = <T>(className: string): XpcEmitterOf<T> => {\n return new Proxy({} as XpcEmitterOf<T>, {\n get(_target, prop: string) {\n const channel = buildXpcChannel(className, prop);\n return (params?: any) => xpcRenderer.send(channel, params);\n },\n });\n};\n"]}
|
|
@@ -20,4 +20,53 @@ type XpcRendererApi = {
|
|
|
20
20
|
*/
|
|
21
21
|
declare const xpcRenderer: XpcRendererApi;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Base class for renderer-process xpc handlers.
|
|
25
|
+
* Subclass this and define async methods — they will be auto-registered
|
|
26
|
+
* as xpc handlers with channel `xpc:ClassName/methodName`.
|
|
27
|
+
*
|
|
28
|
+
* Example:
|
|
29
|
+
* ```ts
|
|
30
|
+
* class UserTable extends XpcRendererHandler {
|
|
31
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
32
|
+
* }
|
|
33
|
+
* const userTable = new UserTable();
|
|
34
|
+
* // auto-registers handler for 'xpc:UserTable/getUserList'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare class XpcRendererHandler {
|
|
38
|
+
constructor();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Helper: checks if a function type has at most 1 parameter.
|
|
43
|
+
* Returns the function type itself if valid, `never` otherwise.
|
|
44
|
+
*/
|
|
45
|
+
type AssertSingleParam<F> = F extends () => any ? F : F extends (p: any) => any ? F extends (p: any, q: any, ...rest: any[]) => any ? never : F : never;
|
|
46
|
+
/**
|
|
47
|
+
* Utility type: extracts the method signatures from a handler class,
|
|
48
|
+
* turning each method into an emitter-compatible signature.
|
|
49
|
+
* Methods with 2+ parameters are mapped to `never`, causing a compile error on use.
|
|
50
|
+
*/
|
|
51
|
+
type XpcEmitterOf<T> = {
|
|
52
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: AssertSingleParam<T[K]> extends never ? never : T[K] extends (params: infer P) => any ? (params: P) => Promise<any> : () => Promise<any>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a type-safe emitter proxy for a renderer-process xpc handler.
|
|
57
|
+
* The emitter mirrors the handler's method signatures, but each call
|
|
58
|
+
* sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.
|
|
59
|
+
*
|
|
60
|
+
* Example:
|
|
61
|
+
* ```ts
|
|
62
|
+
* class UserTable extends XpcRendererHandler {
|
|
63
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
64
|
+
* }
|
|
65
|
+
* const userTableEmitter = createXpcRendererEmitter<UserTable>('UserTable');
|
|
66
|
+
* const list = await userTableEmitter.getUserList({ page: 1 });
|
|
67
|
+
* // sends to 'xpc:UserTable/getUserList'
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare const createXpcRendererEmitter: <T>(className: string) => XpcEmitterOf<T>;
|
|
71
|
+
|
|
72
|
+
export { type XpcEmitterOf, type XpcPayload, type XpcRendererApi, XpcRendererHandler, createXpcRendererEmitter, xpcRenderer };
|
|
@@ -20,4 +20,53 @@ type XpcRendererApi = {
|
|
|
20
20
|
*/
|
|
21
21
|
declare const xpcRenderer: XpcRendererApi;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Base class for renderer-process xpc handlers.
|
|
25
|
+
* Subclass this and define async methods — they will be auto-registered
|
|
26
|
+
* as xpc handlers with channel `xpc:ClassName/methodName`.
|
|
27
|
+
*
|
|
28
|
+
* Example:
|
|
29
|
+
* ```ts
|
|
30
|
+
* class UserTable extends XpcRendererHandler {
|
|
31
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
32
|
+
* }
|
|
33
|
+
* const userTable = new UserTable();
|
|
34
|
+
* // auto-registers handler for 'xpc:UserTable/getUserList'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare class XpcRendererHandler {
|
|
38
|
+
constructor();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Helper: checks if a function type has at most 1 parameter.
|
|
43
|
+
* Returns the function type itself if valid, `never` otherwise.
|
|
44
|
+
*/
|
|
45
|
+
type AssertSingleParam<F> = F extends () => any ? F : F extends (p: any) => any ? F extends (p: any, q: any, ...rest: any[]) => any ? never : F : never;
|
|
46
|
+
/**
|
|
47
|
+
* Utility type: extracts the method signatures from a handler class,
|
|
48
|
+
* turning each method into an emitter-compatible signature.
|
|
49
|
+
* Methods with 2+ parameters are mapped to `never`, causing a compile error on use.
|
|
50
|
+
*/
|
|
51
|
+
type XpcEmitterOf<T> = {
|
|
52
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: AssertSingleParam<T[K]> extends never ? never : T[K] extends (params: infer P) => any ? (params: P) => Promise<any> : () => Promise<any>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a type-safe emitter proxy for a renderer-process xpc handler.
|
|
57
|
+
* The emitter mirrors the handler's method signatures, but each call
|
|
58
|
+
* sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.
|
|
59
|
+
*
|
|
60
|
+
* Example:
|
|
61
|
+
* ```ts
|
|
62
|
+
* class UserTable extends XpcRendererHandler {
|
|
63
|
+
* async getUserList(params?: any): Promise<any> { ... }
|
|
64
|
+
* }
|
|
65
|
+
* const userTableEmitter = createXpcRendererEmitter<UserTable>('UserTable');
|
|
66
|
+
* const list = await userTableEmitter.getUserList({ page: 1 });
|
|
67
|
+
* // sends to 'xpc:UserTable/getUserList'
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare const createXpcRendererEmitter: <T>(className: string) => XpcEmitterOf<T>;
|
|
71
|
+
|
|
72
|
+
export { type XpcEmitterOf, type XpcPayload, type XpcRendererApi, XpcRendererHandler, createXpcRendererEmitter, xpcRenderer };
|
|
@@ -3,6 +3,51 @@
|
|
|
3
3
|
// src/xpc/renderer/xpcRenderer.helper.ts
|
|
4
4
|
var xpcRenderer = globalThis.xpcRenderer;
|
|
5
5
|
|
|
6
|
+
// src/xpc/shared/xpcHandler.type.ts
|
|
7
|
+
var XPC_HANDLER_PREFIX = "xpc:";
|
|
8
|
+
var buildXpcChannel = (className, methodName) => {
|
|
9
|
+
return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;
|
|
10
|
+
};
|
|
11
|
+
var getHandlerMethodNames = (prototype) => {
|
|
12
|
+
const names = [];
|
|
13
|
+
const keys = Object.getOwnPropertyNames(prototype);
|
|
14
|
+
for (const key of keys) {
|
|
15
|
+
if (key === "constructor") continue;
|
|
16
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
17
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
18
|
+
names.push(key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return names;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/xpc/renderer/xpcRenderer.handler.ts
|
|
25
|
+
var XpcRendererHandler = class {
|
|
26
|
+
constructor() {
|
|
27
|
+
const className = this.constructor.name;
|
|
28
|
+
const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));
|
|
29
|
+
for (const methodName of methodNames) {
|
|
30
|
+
const channel = buildXpcChannel(className, methodName);
|
|
31
|
+
const method = this[methodName].bind(this);
|
|
32
|
+
xpcRenderer.handle(channel, async (payload) => {
|
|
33
|
+
return await method(payload.params);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/xpc/renderer/xpcRenderer.emitter.ts
|
|
40
|
+
var createXpcRendererEmitter = (className) => {
|
|
41
|
+
return new Proxy({}, {
|
|
42
|
+
get(_target, prop) {
|
|
43
|
+
const channel = buildXpcChannel(className, prop);
|
|
44
|
+
return (params) => xpcRenderer.send(channel, params);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
exports.XpcRendererHandler = XpcRendererHandler;
|
|
50
|
+
exports.createXpcRendererEmitter = createXpcRendererEmitter;
|
|
6
51
|
exports.xpcRenderer = xpcRenderer;
|
|
7
52
|
//# sourceMappingURL=index.js.map
|
|
8
53
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/xpc/renderer/xpcRenderer.helper.ts"],"names":[],"mappings":";;;AAMO,IAAM,cAAe,UAAA,CAAmB","file":"index.js","sourcesContent":["import type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Direct reference to window.xpcRenderer exposed by the preload script.\n * Import this in renderer (browser) code to use xpcRenderer without manual window casting.\n */\nexport const xpcRenderer = (globalThis as any).xpcRenderer as XpcRendererApi;\n\nexport type { XpcRendererApi, XpcPayload } from '../shared/xpc.type';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/xpc/renderer/xpcRenderer.helper.ts","../../../src/xpc/shared/xpcHandler.type.ts","../../../src/xpc/renderer/xpcRenderer.handler.ts","../../../src/xpc/renderer/xpcRenderer.emitter.ts"],"names":[],"mappings":";;;AAMO,IAAM,cAAe,UAAA,CAAmB;;;ACFxC,IAAM,kBAAA,GAAqB,MAAA;AAM3B,IAAM,eAAA,GAAkB,CAAC,SAAA,EAAmB,UAAA,KAA+B;AAChF,EAAA,OAAO,CAAA,EAAG,kBAAkB,CAAA,EAAG,SAAS,IAAI,UAAU,CAAA,CAAA;AACxD,CAAA;AAKO,IAAM,qBAAA,GAAwB,CAAC,SAAA,KAAgC;AACpE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,mBAAA,CAAoB,SAAS,CAAA;AACjD,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,QAAQ,aAAA,EAAe;AAC3B,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,wBAAA,CAAyB,SAAA,EAAW,GAAG,CAAA;AACjE,IAAA,IAAI,UAAA,IAAc,OAAO,UAAA,CAAW,KAAA,KAAU,UAAA,EAAY;AACxD,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT,CAAA;;;ACVO,IAAM,qBAAN,MAAyB;AAAA,EAC9B,WAAA,GAAc;AACZ,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,CAAY,IAAA;AACnC,IAAA,MAAM,WAAA,GAAc,qBAAA,CAAsB,MAAA,CAAO,cAAA,CAAe,IAAI,CAAC,CAAA;AACrE,IAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,SAAA,EAAW,UAAU,CAAA;AACrD,MAAA,MAAM,MAAA,GAAU,IAAA,CAAa,UAAU,CAAA,CAAE,KAAK,IAAI,CAAA;AAClD,MAAA,WAAA,CAAY,MAAA,CAAO,OAAA,EAAS,OAAO,OAAA,KAAwB;AACzD,QAAA,OAAO,MAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA;AAAA,MACpC,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AACF;;;ACZO,IAAM,wBAAA,GAA2B,CAAI,SAAA,KAAuC;AACjF,EAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAsB;AAAA,IACtC,GAAA,CAAI,SAAS,IAAA,EAAc;AACzB,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,SAAA,EAAW,IAAI,CAAA;AAC/C,MAAA,OAAO,CAAC,MAAA,KAAiB,WAAA,CAAY,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,IAC3D;AAAA,GACD,CAAA;AACH","file":"index.js","sourcesContent":["import type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Direct reference to window.xpcRenderer exposed by the preload script.\n * Import this in renderer (browser) code to use xpcRenderer without manual window casting.\n */\nexport const xpcRenderer = (globalThis as any).xpcRenderer as XpcRendererApi;\n\nexport type { XpcRendererApi, XpcPayload } from '../shared/xpc.type';\n","/**\n * Prefix for all auto-registered xpc handler channels.\n * Channel format: `xpc:ClassName/methodName`\n */\nexport const XPC_HANDLER_PREFIX = 'xpc:';\n\n/**\n * Build the xpc channel name from class name and method name.\n * e.g. buildChannel('UserTable', 'getUserList') => 'xpc:UserTable/getUserList'\n */\nexport const buildXpcChannel = (className: string, methodName: string): string => {\n return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;\n};\n\n/**\n * Extract own method names from a class prototype, excluding constructor.\n */\nexport const getHandlerMethodNames = (prototype: object): string[] => {\n const names: string[] = [];\n const keys = Object.getOwnPropertyNames(prototype);\n for (const key of keys) {\n if (key === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n if (descriptor && typeof descriptor.value === 'function') {\n names.push(key);\n }\n }\n return names;\n};\n\n/**\n * Constraint: handler methods must accept 0 or 1 parameter.\n * Methods with 2+ parameters will fail type checking.\n */\nexport type XpcHandlerMethod = (() => Promise<any>) | ((params: any) => Promise<any>);\n\n/**\n * Helper: checks if a function type has at most 1 parameter.\n * Returns the function type itself if valid, `never` otherwise.\n */\ntype AssertSingleParam<F> =\n F extends () => any ? F :\n F extends (p: any) => any ?\n F extends (p: any, q: any, ...rest: any[]) => any ? never : F\n : never;\n\n/**\n * Utility type: extracts the method signatures from a handler class,\n * turning each method into an emitter-compatible signature.\n * Methods with 2+ parameters are mapped to `never`, causing a compile error on use.\n */\nexport type XpcEmitterOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:\n AssertSingleParam<T[K]> extends never\n ? never\n : T[K] extends (params: infer P) => any\n ? (params: P) => Promise<any>\n : () => Promise<any>;\n};\n","import { XpcPayload } from '../shared/xpc.type';\nimport { buildXpcChannel, getHandlerMethodNames } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcRenderer.helper';\n\n/**\n * Base class for renderer-process xpc handlers.\n * Subclass this and define async methods — they will be auto-registered\n * as xpc handlers with channel `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcRendererHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTable = new UserTable();\n * // auto-registers handler for 'xpc:UserTable/getUserList'\n * ```\n */\nexport class XpcRendererHandler {\n constructor() {\n const className = this.constructor.name;\n const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));\n for (const methodName of methodNames) {\n const channel = buildXpcChannel(className, methodName);\n const method = (this as any)[methodName].bind(this);\n xpcRenderer.handle(channel, async (payload: XpcPayload) => {\n return await method(payload.params);\n });\n }\n }\n}\n","import { buildXpcChannel, XpcEmitterOf } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcRenderer.helper';\n\n/**\n * Create a type-safe emitter proxy for a renderer-process xpc handler.\n * The emitter mirrors the handler's method signatures, but each call\n * sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcRendererHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTableEmitter = createXpcRendererEmitter<UserTable>('UserTable');\n * const list = await userTableEmitter.getUserList({ page: 1 });\n * // sends to 'xpc:UserTable/getUserList'\n * ```\n */\nexport const createXpcRendererEmitter = <T>(className: string): XpcEmitterOf<T> => {\n return new Proxy({} as XpcEmitterOf<T>, {\n get(_target, prop: string) {\n const channel = buildXpcChannel(className, prop);\n return (params?: any) => xpcRenderer.send(channel, params);\n },\n });\n};\n"]}
|
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
// src/xpc/renderer/xpcRenderer.helper.ts
|
|
2
2
|
var xpcRenderer = globalThis.xpcRenderer;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// src/xpc/shared/xpcHandler.type.ts
|
|
5
|
+
var XPC_HANDLER_PREFIX = "xpc:";
|
|
6
|
+
var buildXpcChannel = (className, methodName) => {
|
|
7
|
+
return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;
|
|
8
|
+
};
|
|
9
|
+
var getHandlerMethodNames = (prototype) => {
|
|
10
|
+
const names = [];
|
|
11
|
+
const keys = Object.getOwnPropertyNames(prototype);
|
|
12
|
+
for (const key of keys) {
|
|
13
|
+
if (key === "constructor") continue;
|
|
14
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
15
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
16
|
+
names.push(key);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return names;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/xpc/renderer/xpcRenderer.handler.ts
|
|
23
|
+
var XpcRendererHandler = class {
|
|
24
|
+
constructor() {
|
|
25
|
+
const className = this.constructor.name;
|
|
26
|
+
const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));
|
|
27
|
+
for (const methodName of methodNames) {
|
|
28
|
+
const channel = buildXpcChannel(className, methodName);
|
|
29
|
+
const method = this[methodName].bind(this);
|
|
30
|
+
xpcRenderer.handle(channel, async (payload) => {
|
|
31
|
+
return await method(payload.params);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/xpc/renderer/xpcRenderer.emitter.ts
|
|
38
|
+
var createXpcRendererEmitter = (className) => {
|
|
39
|
+
return new Proxy({}, {
|
|
40
|
+
get(_target, prop) {
|
|
41
|
+
const channel = buildXpcChannel(className, prop);
|
|
42
|
+
return (params) => xpcRenderer.send(channel, params);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { XpcRendererHandler, createXpcRendererEmitter, xpcRenderer };
|
|
5
48
|
//# sourceMappingURL=index.mjs.map
|
|
6
49
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/xpc/renderer/xpcRenderer.helper.ts"],"names":[],"mappings":";AAMO,IAAM,cAAe,UAAA,CAAmB","file":"index.mjs","sourcesContent":["import type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Direct reference to window.xpcRenderer exposed by the preload script.\n * Import this in renderer (browser) code to use xpcRenderer without manual window casting.\n */\nexport const xpcRenderer = (globalThis as any).xpcRenderer as XpcRendererApi;\n\nexport type { XpcRendererApi, XpcPayload } from '../shared/xpc.type';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/xpc/renderer/xpcRenderer.helper.ts","../../../src/xpc/shared/xpcHandler.type.ts","../../../src/xpc/renderer/xpcRenderer.handler.ts","../../../src/xpc/renderer/xpcRenderer.emitter.ts"],"names":[],"mappings":";AAMO,IAAM,cAAe,UAAA,CAAmB;;;ACFxC,IAAM,kBAAA,GAAqB,MAAA;AAM3B,IAAM,eAAA,GAAkB,CAAC,SAAA,EAAmB,UAAA,KAA+B;AAChF,EAAA,OAAO,CAAA,EAAG,kBAAkB,CAAA,EAAG,SAAS,IAAI,UAAU,CAAA,CAAA;AACxD,CAAA;AAKO,IAAM,qBAAA,GAAwB,CAAC,SAAA,KAAgC;AACpE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,mBAAA,CAAoB,SAAS,CAAA;AACjD,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,QAAQ,aAAA,EAAe;AAC3B,IAAA,MAAM,UAAA,GAAa,MAAA,CAAO,wBAAA,CAAyB,SAAA,EAAW,GAAG,CAAA;AACjE,IAAA,IAAI,UAAA,IAAc,OAAO,UAAA,CAAW,KAAA,KAAU,UAAA,EAAY;AACxD,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT,CAAA;;;ACVO,IAAM,qBAAN,MAAyB;AAAA,EAC9B,WAAA,GAAc;AACZ,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,CAAY,IAAA;AACnC,IAAA,MAAM,WAAA,GAAc,qBAAA,CAAsB,MAAA,CAAO,cAAA,CAAe,IAAI,CAAC,CAAA;AACrE,IAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,SAAA,EAAW,UAAU,CAAA;AACrD,MAAA,MAAM,MAAA,GAAU,IAAA,CAAa,UAAU,CAAA,CAAE,KAAK,IAAI,CAAA;AAClD,MAAA,WAAA,CAAY,MAAA,CAAO,OAAA,EAAS,OAAO,OAAA,KAAwB;AACzD,QAAA,OAAO,MAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA;AAAA,MACpC,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AACF;;;ACZO,IAAM,wBAAA,GAA2B,CAAI,SAAA,KAAuC;AACjF,EAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAsB;AAAA,IACtC,GAAA,CAAI,SAAS,IAAA,EAAc;AACzB,MAAA,MAAM,OAAA,GAAU,eAAA,CAAgB,SAAA,EAAW,IAAI,CAAA;AAC/C,MAAA,OAAO,CAAC,MAAA,KAAiB,WAAA,CAAY,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,IAC3D;AAAA,GACD,CAAA;AACH","file":"index.mjs","sourcesContent":["import type { XpcRendererApi } from '../shared/xpc.type';\n\n/**\n * Direct reference to window.xpcRenderer exposed by the preload script.\n * Import this in renderer (browser) code to use xpcRenderer without manual window casting.\n */\nexport const xpcRenderer = (globalThis as any).xpcRenderer as XpcRendererApi;\n\nexport type { XpcRendererApi, XpcPayload } from '../shared/xpc.type';\n","/**\n * Prefix for all auto-registered xpc handler channels.\n * Channel format: `xpc:ClassName/methodName`\n */\nexport const XPC_HANDLER_PREFIX = 'xpc:';\n\n/**\n * Build the xpc channel name from class name and method name.\n * e.g. buildChannel('UserTable', 'getUserList') => 'xpc:UserTable/getUserList'\n */\nexport const buildXpcChannel = (className: string, methodName: string): string => {\n return `${XPC_HANDLER_PREFIX}${className}/${methodName}`;\n};\n\n/**\n * Extract own method names from a class prototype, excluding constructor.\n */\nexport const getHandlerMethodNames = (prototype: object): string[] => {\n const names: string[] = [];\n const keys = Object.getOwnPropertyNames(prototype);\n for (const key of keys) {\n if (key === 'constructor') continue;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, key);\n if (descriptor && typeof descriptor.value === 'function') {\n names.push(key);\n }\n }\n return names;\n};\n\n/**\n * Constraint: handler methods must accept 0 or 1 parameter.\n * Methods with 2+ parameters will fail type checking.\n */\nexport type XpcHandlerMethod = (() => Promise<any>) | ((params: any) => Promise<any>);\n\n/**\n * Helper: checks if a function type has at most 1 parameter.\n * Returns the function type itself if valid, `never` otherwise.\n */\ntype AssertSingleParam<F> =\n F extends () => any ? F :\n F extends (p: any) => any ?\n F extends (p: any, q: any, ...rest: any[]) => any ? never : F\n : never;\n\n/**\n * Utility type: extracts the method signatures from a handler class,\n * turning each method into an emitter-compatible signature.\n * Methods with 2+ parameters are mapped to `never`, causing a compile error on use.\n */\nexport type XpcEmitterOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:\n AssertSingleParam<T[K]> extends never\n ? never\n : T[K] extends (params: infer P) => any\n ? (params: P) => Promise<any>\n : () => Promise<any>;\n};\n","import { XpcPayload } from '../shared/xpc.type';\nimport { buildXpcChannel, getHandlerMethodNames } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcRenderer.helper';\n\n/**\n * Base class for renderer-process xpc handlers.\n * Subclass this and define async methods — they will be auto-registered\n * as xpc handlers with channel `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcRendererHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTable = new UserTable();\n * // auto-registers handler for 'xpc:UserTable/getUserList'\n * ```\n */\nexport class XpcRendererHandler {\n constructor() {\n const className = this.constructor.name;\n const methodNames = getHandlerMethodNames(Object.getPrototypeOf(this));\n for (const methodName of methodNames) {\n const channel = buildXpcChannel(className, methodName);\n const method = (this as any)[methodName].bind(this);\n xpcRenderer.handle(channel, async (payload: XpcPayload) => {\n return await method(payload.params);\n });\n }\n }\n}\n","import { buildXpcChannel, XpcEmitterOf } from '../shared/xpcHandler.type';\nimport { xpcRenderer } from './xpcRenderer.helper';\n\n/**\n * Create a type-safe emitter proxy for a renderer-process xpc handler.\n * The emitter mirrors the handler's method signatures, but each call\n * sends a message via xpcRenderer.send() to `xpc:ClassName/methodName`.\n *\n * Example:\n * ```ts\n * class UserTable extends XpcRendererHandler {\n * async getUserList(params?: any): Promise<any> { ... }\n * }\n * const userTableEmitter = createXpcRendererEmitter<UserTable>('UserTable');\n * const list = await userTableEmitter.getUserList({ page: 1 });\n * // sends to 'xpc:UserTable/getUserList'\n * ```\n */\nexport const createXpcRendererEmitter = <T>(className: string): XpcEmitterOf<T> => {\n return new Proxy({} as XpcEmitterOf<T>, {\n get(_target, prop: string) {\n const channel = buildXpcChannel(className, prop);\n return (params?: any) => xpcRenderer.send(channel, params);\n },\n });\n};\n"]}
|