Skip to content
Source
Sign inSign up

main.ts

256 lines10.8 KB

1import * as MacPermissions from "./permissions/MacPermissions.ts";
2for (const stream of [process.stdout, process.stderr]) {
3 stream.on("error", (err: NodeJS.ErrnoException) => {
4 if (err.code !== "EPIPE") throw err;
5 });
6}
7​
8import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
9import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
10import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
11import * as NodeServices from "@effect/platform-node/NodeServices";
12// @effect-diagnostics-next-line nodeBuiltinImport:off - Version output must flush before Electron exits, without acquiring the runtime.
13import * as NodeFS from "node:fs";
14import * as NodeOS from "node:os";
15import * as Effect from "effect/Effect";
16import * as Layer from "effect/Layer";
17import * as Option from "effect/Option";
18​
19import * as Electron from "electron";
20​
21import * as NetService from "@t3tools/shared/Net";
22import * as HostProcess from "@t3tools/shared/HostProcess";
23import type { RemoteT3RunnerOptions } from "@t3tools/ssh/tunnel";
24import serverPackageJson from "../../server/package.json" with { type: "json" };
25​
26import * as DesktopIpc from "./ipc/DesktopIpc.ts";
27import * as ElectronApp from "./electron/ElectronApp.ts";
28import * as ElectronDialog from "./electron/ElectronDialog.ts";
29import * as ElectronMenu from "./electron/ElectronMenu.ts";
30import * as ElectronPowerMonitor from "./electron/ElectronPowerMonitor.ts";
31import * as ElectronProtocol from "./electron/ElectronProtocol.ts";
32import * as ElectronSafeStorage from "./electron/ElectronSafeStorage.ts";
33import * as ElectronShell from "./electron/ElectronShell.ts";
34import * as ElectronTheme from "./electron/ElectronTheme.ts";
35import * as ElectronUpdater from "./electron/ElectronUpdater.ts";
36import * as ElectronWindow from "./electron/ElectronWindow.ts";
37import * as DesktopApp from "./app/DesktopApp.ts";
38import * as DesktopAppActivation from "./app/DesktopAppActivation.ts";
39import * as DesktopWebLinks from "./app/DesktopWebLinks.ts";
40import * as DesktopAppIdentity from "./app/DesktopAppIdentity.ts";
41import * as DesktopConnectionCatalogStore from "./app/DesktopConnectionCatalogStore.ts";
42import * as DesktopClerk from "./app/DesktopClerk.ts";
43import * as DesktopCliCommand from "./app/DesktopCliCommand.ts";
44import * as DesktopApplicationMenu from "./window/DesktopApplicationMenu.ts";
45import * as DesktopAssets from "./app/DesktopAssets.ts";
46import * as DesktopPrimaryPaste from "./app/DesktopPrimaryPaste.ts";
47import * as DesktopBackendConfiguration from "./backend/DesktopBackendConfiguration.ts";
48import * as DesktopBackendPool from "./backend/DesktopBackendPool.ts";
49import * as DesktopLocalEnvironmentAuth from "./backend/DesktopLocalEnvironmentAuth.ts";
50import * as DesktopNetworkInterfaces from "./backend/DesktopNetworkInterfaces.ts";
51import * as DesktopEnvironment from "./app/DesktopEnvironment.ts";
52import * as DesktopManagedPolicy from "./app/DesktopManagedPolicy.ts";
53import * as DesktopLifecycle from "./app/DesktopLifecycle.ts";
54import * as DesktopLinuxUrlHandler from "./app/DesktopLinuxUrlHandler.ts";
55import * as DesktopShutdown from "./app/DesktopShutdown.ts";
56import * as DesktopObservability from "./app/DesktopObservability.ts";
57import * as DesktopServerExposure from "./backend/DesktopServerExposure.ts";
58import * as DesktopClientSettings from "./settings/DesktopClientSettings.ts";
59import * as DesktopSavedEnvironments from "./settings/DesktopSavedEnvironments.ts";
60import * as DesktopSnapShot from "./snapShot/DesktopSnapShot.ts";
61import * as DesktopAppSettings from "./settings/DesktopAppSettings.ts";
62import * as DesktopPreReadyFileSystem from "./app/DesktopPreReadyFileSystem.ts";
63import * as DesktopPreReadyPlatform from "./app/DesktopPreReadyPlatform.ts";
64import * as DesktopShellEnvironment from "./shell/DesktopShellEnvironment.ts";
65import * as DesktopSshEnvironment from "./ssh/DesktopSshEnvironment.ts";
66import * as DesktopSshPasswordPrompts from "./ssh/DesktopSshPasswordPrompts.ts";
67import * as DesktopState from "./app/DesktopState.ts";
68import * as DesktopLegacyLocalStorage from "./app/DesktopLegacyLocalStorage.ts";
69import * as DesktopTelemetryPublisher from "./telemetry/DesktopTelemetryPublisher.ts";
70import * as DesktopRendererHistory from "./telemetry/DesktopRendererHistory.ts";
71import * as DesktopUpdatePolicy from "./updates/DesktopUpdatePolicy.ts";
72import * as DesktopUpdates from "./updates/DesktopUpdates.ts";
73import * as BrowserImport from "./preview/BrowserImport/BrowserImport.ts";
74import * as LinuxBrowserSecret from "./preview/BrowserImport/LinuxBrowserSecret.ts";
75import * as BrowserSession from "./preview/BrowserSession.ts";
76import * as DesktopBrowserHost from "./preview/DesktopBrowserHost.ts";
77import * as PreviewManager from "./preview/Manager.ts";
78import * as PreviewPasskeys from "./preview/Passkeys.ts";
79import * as DesktopWindow from "./window/DesktopWindow.ts";
80import * as DesktopWslBackend from "./wsl/DesktopWslBackend.ts";
81import * as DesktopWslEnvironment from "./wsl/DesktopWslEnvironment.ts";
82import * as DesktopWslServerTree from "./wsl/DesktopWslServerTree.ts";
83​
84if (process.argv.includes("--version")) {
85 try {
86 NodeFS.writeSync(process.stdout.fd, `${Electron.app.getVersion()}\n`);
87 } catch (error) {
88 if (!(error instanceof Error) || !("code" in error) || error.code !== "EPIPE") throw error;
89 }
90 Electron.app.exit(0);
92​
93const layerDesktopEnvironment = Layer.unwrap(
94 Effect.gen(function* () {
95 const metadata = yield* Effect.service(ElectronApp.ElectronApp).pipe(
96 Effect.flatMap((app) => app.metadata),
97 );
98 const platform = yield* HostProcess.Platform;
99 const processArch = yield* HostProcess.Architecture;
100 return DesktopEnvironment.layer({
101 dirname: __dirname,
102 homeDirectory: NodeOS.homedir(),
103 platform,
104 processArch,
105 ...metadata,
106 });
107 }),
108);
109​
110const resolveDesktopSshCliRunner = (
111 environment: DesktopEnvironment.DesktopEnvironment["Service"],
112): RemoteT3RunnerOptions => {
113 const devRemoteEntryPath = Option.getOrUndefined(environment.devRemoteT3ServerEntryPath);
114 if (environment.isDevelopment && devRemoteEntryPath !== undefined) {
115 return {
116 nodeScriptPath: devRemoteEntryPath,
117 nodeEngineRange: serverPackageJson.engines.node,
118 };
119 }
120 return { archiveVersion: environment.appVersion };
121};
122​
123const layerDesktopSshEnvironment = Layer.unwrap(
124 Effect.gen(function* () {
125 const environment = yield* DesktopEnvironment.DesktopEnvironment;
126 return DesktopSshEnvironment.layer({
127 resolveCliRunner: Effect.succeed(resolveDesktopSshCliRunner(environment)),
128 });
129 }),
130);
131​
132const layerElectron = Layer.mergeAll(
133 ElectronApp.layer,
134 ElectronDialog.layer,
135 ElectronMenu.layer,
136 ElectronPowerMonitor.layer,
137 ElectronProtocol.layer,
138 ElectronSafeStorage.layer,
139 ElectronShell.layer,
140 ElectronTheme.layer,
141 ElectronUpdater.layer,
142 ElectronWindow.layer,
143 DesktopIpc.layer(Electron.ipcMain).pipe(Layer.provide(ElectronWindow.layer)),
144);
145​
146const layerDesktopFoundation = Layer.mergeAll(
147 MacPermissions.layer,
148 DesktopManagedPolicy.layer,
149 DesktopState.layer,
150 DesktopShutdown.layer,
151 DesktopLegacyLocalStorage.layer,
152 DesktopAppSettings.layer,
153 DesktopClientSettings.layer,
154 DesktopConnectionCatalogStore.layer.pipe(Layer.provideMerge(DesktopSavedEnvironments.layer)),
155 DesktopAssets.layer,
156 DesktopPrimaryPaste.layer,
157 DesktopObservability.layer,
158 DesktopRendererHistory.layer,
159).pipe(Layer.provideMerge(layerDesktopEnvironment));
160​
161const layerDesktopSsh = layerDesktopSshEnvironment.pipe(
162 Layer.provideMerge(DesktopSshPasswordPrompts.layer()),
163);
164​
165const layerDesktopServerExposure = DesktopServerExposure.layer.pipe(
166 Layer.provideMerge(DesktopNetworkInterfaces.layer),
167 Layer.provideMerge(layerDesktopFoundation),
168);
169​
170const layerDesktopPreview = PreviewManager.layer.pipe(
171 Layer.provideMerge(DesktopBrowserHost.layer),
172 // IPC needs both services on the same BrowserSession.
173 Layer.provideMerge(BrowserImport.layer.pipe(Layer.provide(LinuxBrowserSecret.layer))),
174 Layer.provideMerge(BrowserSession.layer),
175 Layer.provideMerge(PreviewPasskeys.layer),
176 Layer.provideMerge(layerDesktopFoundation),
177);
178​
179const layerDesktopWindow = DesktopWindow.layer.pipe(
180 Layer.provideMerge(layerDesktopServerExposure),
181 Layer.provideMerge(layerDesktopPreview),
182);
183​
184const layerDesktopSnapShot = DesktopSnapShot.layer.pipe(
185 Layer.provideMerge(layerDesktopWindow),
186 Layer.provideMerge(layerDesktopFoundation),
187);
188const layerDesktopAppActivation = DesktopAppActivation.layer.pipe(
189 Layer.provide(layerDesktopWindow),
190);
191​
192const layerDesktopBackend = DesktopBackendPool.layer.pipe(
193 Layer.provideMerge(DesktopAppIdentity.layer),
194 Layer.provideMerge(DesktopBackendConfiguration.layer),
195 Layer.provideMerge(DesktopWslEnvironment.layer),
196 Layer.provideMerge(DesktopWslServerTree.layer),
197 Layer.provideMerge(DesktopTelemetryPublisher.layer),
198 Layer.provideMerge(DesktopShellEnvironment.layer),
199 Layer.provideMerge(layerDesktopWindow),
200);
201​
202const layerDesktopWslBackend = DesktopWslBackend.layer.pipe(
203 Layer.provideMerge(layerDesktopBackend),
204);
205​
206const layerDesktopLocalEnvironmentAuth = DesktopLocalEnvironmentAuth.layer.pipe(
207 Layer.provideMerge(layerDesktopBackend),
208);
209​
210const layerDesktopApplication = Layer.mergeAll(
211 DesktopLifecycle.layer,
212 layerDesktopAppActivation,
213 DesktopWebLinks.layer,
214 DesktopApplicationMenu.layer,
215 DesktopLinuxUrlHandler.layer,
216 DesktopCliCommand.layer,
217).pipe(
218 Layer.provideMerge(layerDesktopSnapShot),
219 Layer.provideMerge(
220 DesktopUpdates.layer.pipe(
221 Layer.provide(DesktopUpdatePolicy.layer),
222 // The same layer as the foundation's, so the policy is still read once.
223 Layer.provide(DesktopManagedPolicy.layer),
224 ),
225 ),
226 // Updates closes the same tunnels the SSH IPC opens before quitAndInstall.
227 Layer.provideMerge(layerDesktopSsh),
228 Layer.provideMerge(layerDesktopWslBackend),
229 Layer.provideMerge(layerDesktopLocalEnvironmentAuth),
230);
231​
232// Clerk resolves userData before Electron is ready, so it gets the synchronous FileSystem.
233const layerDesktopClerk = DesktopClerk.layer.pipe(
234 Layer.provide(DesktopPreReadyFileSystem.layer),
235 Layer.provideMerge(ElectronShell.layer),
236 Layer.provideMerge(layerDesktopEnvironment),
237 Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
238 Layer.provideMerge(ElectronApp.layer),
239);
240​
241const layerDesktopApplicationRuntime = layerDesktopApplication.pipe(
242 Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
243 Layer.provideMerge(NodeHttpClient.layerUndici),
244 Layer.provideMerge(NetService.layer),
245 Layer.provideMerge(layerElectron),
246);
247​
248// Pre-ready setup and Clerk must not yield before Clerk registers its scheme.
249const layerDesktopRuntime = layerDesktopClerk.pipe(
250 Layer.flatMap((clerkContext) =>
251 layerDesktopApplicationRuntime.pipe(Layer.provideMerge(Layer.succeedContext(clerkContext))),
252 ),
253 Layer.provideMerge(DesktopPreReadyPlatform.layer),
254);
255​
256DesktopApp.program.pipe(Effect.provide(layerDesktopRuntime), NodeRuntime.runMain);