返回 DeepSeek-Reasonix
hostCalls.ts
根目录 / desktop / electron / src / main / hostCalls.ts
1 import { num, record, str, strList, type Params } from "./params.js";
2 import { RpcError } from "./rpc.js";
3
4 export interface WindowHostApi {
5 show(reason: string): void;
6 hide(): void;
7 maximise(): void;
8 unmaximise(): void;
9 minimise(): void;
10 unminimise(): void;
11 toggleMaximise(): void;
12 center(): void;
13 isMaximised(): boolean;
14 isMinimised(): boolean;
15 setPosition(x: number, y: number): void;
16 setTitle(title: string): void;
17 toggleDevTools(): void;
18 }
19
20 export interface DialogHostApi {
21 openDirectory(params: Params): Promise<{ path: string }>;
22 openFile(params: Params): Promise<{ paths: string[] }>;
23 saveFile(params: Params): Promise<{ path: string }>;
24 message(params: Params): Promise<{ button: string }>;
25 }
26
27 export interface TrayLabels {
28 openTitle: string;
29 openTooltip: string;
30 quitTitle: string;
31 quitTooltip: string;
32 tooltip: string;
33 }
34
35 export interface TrayHostApi {
36 ensure(labels: TrayLabels): { ready: boolean; reason: string };
37 destroy(): void;
38 }
39
40 export interface RemoteWindowHostApi {
41 open(input: { hostKey: string; url: string; title: string }): { windowId: string };
42 navigate(input: { hostKey: string; url: string; title: string }): void;
43 focus(hostKey: string): void;
44 close(hostKey: string): void;
45 }
46
47 export interface LifecycleHostApi {
48 approve(): void;
49 relaunch(args: string[], execPath?: string): void;
50 }
51
52 export interface ScreenInfo {
53 x: number;
54 y: number;
55 width: number;
56 height: number;
57 scale: number;
58 primary: boolean;
59 }
60
61 export type HostCall = (params: Params) => Promise<unknown> | unknown;
62 export type HostCallTable = Record<string, HostCall>;
63
64 export interface HostCallDeps {
65 window: WindowHostApi;
66 dialogs: DialogHostApi;
67 tray: TrayHostApi;
68 remote: RemoteWindowHostApi;
69 lifecycle: LifecycleHostApi;
70 openExternal(url: string): Promise<void>;
71 hideApp(): void;
72 screens(): ScreenInfo[];
73 browser?: HostCallTable;
74 }
75
76 const remoteInput = (params: Params) => ({ hostKey: str(params, "hostKey"), url: str(params, "url"), title: str(params, "title") });
77 const EXTERNAL_PROTOCOLS = new Set(["http:", "https:", "mailto:"]);
78
79 function externalURL(value: string): string {
80 let url: URL;
81 try {
82 url = new URL(value);
83 } catch {
84 throw new Error("refusing to open an invalid external URL");
85 }
86 if (!EXTERNAL_PROTOCOLS.has(url.protocol)) throw new Error(`refusing to open external URL with protocol ${url.protocol}`);
87 return url.href;
88 }
89
90 export function buildHostCallTable(deps: HostCallDeps): HostCallTable {
91 const done = (run: () => void): HostCall => (params) => {
92 void params;
93 run();
94 return {};
95 };
96 return {
97 "host/window.show": (params) => {
98 deps.window.show(str(params, "reason"));
99 return {};
100 },
101 "host/window.hide": done(() => deps.window.hide()),
102 "host/app.hide": done(() => deps.hideApp()),
103 "host/window.maximise": done(() => deps.window.maximise()),
104 "host/window.unmaximise": done(() => deps.window.unmaximise()),
105 "host/window.minimise": done(() => deps.window.minimise()),
106 "host/window.unminimise": done(() => deps.window.unminimise()),
107 "host/window.toggleMaximise": done(() => deps.window.toggleMaximise()),
108 "host/window.center": done(() => deps.window.center()),
109 "host/window.isMaximised": () => ({ value: deps.window.isMaximised() }),
110 "host/window.isMinimised": () => ({ value: deps.window.isMinimised() }),
111 "host/window.setPosition": (params) => {
112 deps.window.setPosition(num(params, "x"), num(params, "y"));
113 return {};
114 },
115 "host/window.setTitle": (params) => {
116 deps.window.setTitle(str(params, "title"));
117 return {};
118 },
119 "host/screen.list": () => ({ screens: deps.screens() }),
120 "host/dialog.openDirectory": (params) => deps.dialogs.openDirectory(params),
121 "host/dialog.openFile": (params) => deps.dialogs.openFile(params),
122 "host/dialog.saveFile": (params) => deps.dialogs.saveFile(params),
123 "host/dialog.message": (params) => deps.dialogs.message(params),
124 "host/shell.openExternal": async (params) => {
125 await deps.openExternal(externalURL(str(params, "url")));
126 return {};
127 },
128 "host/app.quit": done(() => deps.lifecycle.approve()),
129 "host/app.relaunch": (params) => {
130 const execPath = str(params, "execPath");
131 if (execPath) deps.lifecycle.relaunch(strList(params, "args"), execPath);
132 else deps.lifecycle.relaunch(strList(params, "args"));
133 return {};
134 },
135 "host/devtools.toggle": done(() => deps.window.toggleDevTools()),
136 "host/remoteWindow.open": (params) => deps.remote.open(remoteInput(params)),
137 "host/remoteWindow.navigate": (params) => {
138 deps.remote.navigate(remoteInput(params));
139 return {};
140 },
141 "host/remoteWindow.focus": (params) => {
142 deps.remote.focus(str(params, "hostKey"));
143 return {};
144 },
145 "host/remoteWindow.close": (params) => {
146 deps.remote.close(str(params, "hostKey"));
147 return {};
148 },
149 "host/tray.ensure": (params) => deps.tray.ensure({
150 openTitle: str(params, "openTitle", "Open"),
151 openTooltip: str(params, "openTooltip"),
152 quitTitle: str(params, "quitTitle", "Quit"),
153 quitTooltip: str(params, "quitTooltip"),
154 tooltip: str(params, "tooltip", "Reasonix"),
155 }),
156 "host/tray.destroy": done(() => deps.tray.destroy()),
157 ...(deps.browser ?? {}),
158 };
159 }
160
161 export async function dispatchHostCall(table: HostCallTable, method: string, params: unknown): Promise<unknown> {
162 const call = Object.prototype.hasOwnProperty.call(table, method) ? table[method] : undefined;
163 if (!call) throw new RpcError(-32601, `unknown host method: ${method}`);
164 return call(record(params));
165 }
166
166 lines TYPESCRIPT