1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Option from "effect/Option";
5import * as Schema from "effect/Schema";
6
7import * as Electron from "electron";
8import * as HostProcess from "@t3tools/shared/HostProcess";
9
10const electronSafeStorageErrorFields = {
11 cause: Schema.Defect(),
12};
13
14export class ElectronSafeStorageAvailabilityError extends Schema.TaggedError<ElectronSafeStorageAvailabilityError>()(
15 "ElectronSafeStorageAvailabilityError",
16 {
17 ...electronSafeStorageErrorFields,
18 },
19) {
20 override get message(): string {
21 return "Electron safe storage failed to check encryption availability.";
22 }
23}
24
25export class ElectronSafeStorageEncryptError extends Schema.TaggedError<ElectronSafeStorageEncryptError>()(
26 "ElectronSafeStorageEncryptError",
27 {
28 ...electronSafeStorageErrorFields,
29 },
30) {
31 override get message(): string {
32 return "Electron safe storage failed to encrypt a string.";
33 }
34}
35
36export class ElectronSafeStorageDecryptError extends Schema.TaggedError<ElectronSafeStorageDecryptError>()(
37 "ElectronSafeStorageDecryptError",
38 {
39 ...electronSafeStorageErrorFields,
40 },
41) {
42 override get message(): string {
43 return "Electron safe storage failed to decrypt a string.";
44 }
45}
46
47export const ElectronSafeStorageError = Schema.Union([
48 ElectronSafeStorageAvailabilityError,
49 ElectronSafeStorageEncryptError,
50 ElectronSafeStorageDecryptError,
51]);
52export type ElectronSafeStorageError = typeof ElectronSafeStorageError.Type;
53
54export class ElectronSafeStorage extends Context.Service<
55 ElectronSafeStorage,
56 {
57 readonly isEncryptionAvailable: Effect.Effect<boolean, ElectronSafeStorageAvailabilityError>;
58 readonly encryptString: (
59 value: string,
60 ) => Effect.Effect<Uint8Array, ElectronSafeStorageEncryptError>;
61 readonly decryptString: (
62 value: Uint8Array,
63 ) => Effect.Effect<string, ElectronSafeStorageDecryptError>;
64 readonly selectedStorageBackend: Effect.Effect<Option.Option<string>>;
65 }
66>()("@t3tools/desktop/electron/ElectronSafeStorage") {}
67
68/** @public Service construction is part of the canonical Effect module API. */
69export const make = Effect.gen(function* () {
70 const platform = yield* HostProcess.Platform;
71
72 return ElectronSafeStorage.of({
73 isEncryptionAvailable: Effect.try({
74 try: () => Electron.safeStorage.isEncryptionAvailable(),
75 catch: (cause) => new ElectronSafeStorageAvailabilityError({ cause }),
76 }),
77 encryptString: (value) =>
78 Effect.try({
79 try: () => Electron.safeStorage.encryptString(value),
80 catch: (cause) => new ElectronSafeStorageEncryptError({ cause }),
81 }),
82 decryptString: (value) =>
83 Effect.try({
84 try: () => Electron.safeStorage.decryptString(Buffer.from(value)),
85 catch: (cause) => new ElectronSafeStorageDecryptError({ cause }),
86 }),
87 selectedStorageBackend: Effect.sync(() => {
88 if (platform !== "linux") {
89 return Option.none();
90 }
91 try {
92 return Option.fromNullishOr(Electron.safeStorage.getSelectedStorageBackend());
93 } catch {
94 return Option.none();
95 }
96 }),
97 });
98});
99
100export const layer = Layer.effect(ElectronSafeStorage, make);