Open History to see the last commit
1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
4import * as Effect from "effect/Effect";
5import * as Base64 from "effect/encoding/Base64";
6import * as FileSystem from "effect/FileSystem";
7import * as Layer from "effect/Layer";
8import * as Option from "effect/Option";
9import * as Schema from "effect/Schema";
10
11import * as DesktopConfig from "../app/DesktopConfig.ts";
12import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
13import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
14import * as DesktopSavedEnvironments from "./DesktopSavedEnvironments.ts";
15
16const textDecoder = new TextDecoder();
17const textEncoder = new TextEncoder();
18
19const savedRegistryRecord: PersistedSavedEnvironmentRecord = {
20 environmentId: EnvironmentId.make("environment-1"),
21 label: "Remote environment",
22 httpBaseUrl: "https://remote.example.com/",
23 wsBaseUrl: "wss://remote.example.com/",
24 createdAt: "2026-04-09T00:00:00.000Z",
25 lastConnectedAt: "2026-04-09T01:00:00.000Z",
26 desktopSsh: {
27 alias: "devbox",
28 hostname: "devbox.example.com",
29 username: "julius",
30 port: 22,
31 },
32};
33
34const SavedEnvironmentRegistryDocumentProbe = Schema.Struct({
35 version: Schema.Number,
36 records: Schema.Array(Schema.Unknown),
37});
38const SavedEnvironmentRegistryDocumentProbeJson = Schema.fromJsonString(
39 SavedEnvironmentRegistryDocumentProbe,
40);
41const encodeSavedEnvironmentRegistryDocumentProbe = Schema.encodeEffect(
42 SavedEnvironmentRegistryDocumentProbeJson,
43);
44
45const seedSavedEnvironmentRegistry = Effect.fn(function* (encryptedBearerToken?: string) {
46 const environment = yield* DesktopEnvironment.DesktopEnvironment;
47 const fileSystem = yield* FileSystem.FileSystem;
48 yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
49 const encoded = yield* encodeSavedEnvironmentRegistryDocumentProbe({
50 version: 1,
51 records: [
52 {
53 ...savedRegistryRecord,
54 ...(encryptedBearerToken === undefined ? {} : { encryptedBearerToken }),
55 },
56 ],
57 });
58 yield* fileSystem.writeFileString(environment.savedEnvironmentRegistryPath, `${encoded}\n`);
59});
60
61function layerSafeStorageFor(input: {
62 readonly available: boolean;
63 readonly availabilityError?: unknown;
64 readonly decryptError?: unknown;
65}) {
66 return Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, {
67 isEncryptionAvailable:
68 input.availabilityError === undefined
69 ? Effect.succeed(input.available)
70 : Effect.fail(
71 new ElectronSafeStorage.ElectronSafeStorageAvailabilityError({
72 cause: input.availabilityError,
73 }),
74 ),
75 encryptString: (value) => Effect.succeed(textEncoder.encode(`enc:${value}`)),
76 decryptString: (value) => {
77 if (input.decryptError !== undefined) {
78 return Effect.fail(
79 new ElectronSafeStorage.ElectronSafeStorageDecryptError({
80 cause: input.decryptError,
81 }),
82 );
83 }
84
85 const decoded = textDecoder.decode(value);
86 if (!decoded.startsWith("enc:")) {
87 return Effect.fail(
88 new ElectronSafeStorage.ElectronSafeStorageDecryptError({
89 cause: new Error("invalid secret"),
90 }),
91 );
92 }
93 return Effect.succeed(decoded.slice("enc:".length));
94 },
95 selectedStorageBackend: Effect.succeedNone,
96 } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]);
97}
98
99function layer(
100 baseDir: string,
101 options?: {
102 readonly availableSecretStorage?: boolean;
103 readonly availabilityError?: unknown;
104 readonly decryptError?: unknown;
105 },
106 fileSystemLayer: Layer.Layer<FileSystem.FileSystem> = NodeServices.layer,
107) {
108 const layerEnvironment = DesktopEnvironment.layer({
109 dirname: "/repo/apps/desktop/src",
110 homeDirectory: baseDir,
111 platform: "darwin",
112 processArch: "x64",
113 appVersion: "1.2.3",
114 appPath: "/repo",
115 isPackaged: true,
116 resourcesPath: "/missing/resources",
117 runningUnderArm64Translation: false,
118 }).pipe(
119 Layer.provide(
120 Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({ T3CODE_HOME: baseDir })),
121 ),
122 );
123
124 const layerSafeStorage = layerSafeStorageFor({
125 available: options?.availableSecretStorage ?? true,
126 availabilityError: options?.availabilityError,
127 decryptError: options?.decryptError,
128 });
129 const layerDependencies = Layer.mergeAll(
130 layerEnvironment,
131 layerSafeStorage,
132 NodeServices.layer,
133 fileSystemLayer,
134 );
135
136 return DesktopSavedEnvironments.layer.pipe(Layer.provideMerge(layerDependencies));
137}
138
139const withSavedEnvironments = <A, E, R>(
140 effect: Effect.Effect<A, E, R | DesktopSavedEnvironments.DesktopSavedEnvironments>,
141 options?: {
142 readonly availableSecretStorage?: boolean;
143 readonly availabilityError?: unknown;
144 readonly decryptError?: unknown;
145 },
146) =>
147 Effect.gen(function* () {
148 const fileSystem = yield* FileSystem.FileSystem;
149 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
150 prefix: "t3-desktop-saved-environments-test-",
151 });
152 return yield* effect.pipe(Effect.provide(layer(baseDir, options)));
153 }).pipe(Effect.provide(NodeServices.layer));
154
155describe("DesktopSavedEnvironments", () => {
156 it.effect("loads lenient saved environment registry documents", () =>
157 withSavedEnvironments(
158 Effect.gen(function* () {
159 const environment = yield* DesktopEnvironment.DesktopEnvironment;
160 const fileSystem = yield* FileSystem.FileSystem;
161 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
162 yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
163 yield* fileSystem.writeFileString(
164 environment.savedEnvironmentRegistryPath,
165 `{
166 // Same optional envelope shape as browser saved environments.
167 "version": 1,
168 "records": [
169 {
170 "environmentId": "${savedRegistryRecord.environmentId}",
171 "label": "Remote environment",
172 "httpBaseUrl": "https://remote.example.com/",
173 "wsBaseUrl": "wss://remote.example.com/",
174 "createdAt": "2026-04-09T00:00:00.000Z",
175 "lastConnectedAt": "2026-04-09T01:00:00.000Z",
176 "desktopSsh": {
177 "alias": "devbox",
178 "hostname": "devbox.example.com",
179 "username": "julius",
180 "port": 22,
181 },
182 },
183 ],
184 }\n`,
185 );
186
187 assert.deepEqual(yield* savedEnvironments.getRegistry, [savedRegistryRecord]);
188 }),
189 ),
190 );
191
192 it.effect("reads encrypted saved environment secrets when encryption is available", () =>
193 withSavedEnvironments(
194 Effect.gen(function* () {
195 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
196 yield* seedSavedEnvironmentRegistry(Base64.encode(textEncoder.encode("enc:bearer-token")));
197
198 assert.deepEqual(
199 yield* savedEnvironments.getSecret(savedRegistryRecord.environmentId),
200 Option.some("bearer-token"),
201 );
202 }),
203 ),
204 );
205
206 it.effect("returns no secret while encryption is unavailable", () =>
207 withSavedEnvironments(
208 Effect.gen(function* () {
209 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
210 yield* seedSavedEnvironmentRegistry(Base64.encode(textEncoder.encode("enc:bearer-token")));
211
212 assert.deepEqual(
213 yield* savedEnvironments.getSecret(savedRegistryRecord.environmentId),
214 Option.none(),
215 );
216 }),
217 { availableSecretStorage: false },
218 ),
219 );
220});