import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import { autoUpdater } from "electron-updater"; type AutoUpdater = typeof autoUpdater; export type ElectronUpdaterFeedUrl = Parameters[0]; export class ElectronUpdaterCheckForUpdatesError extends Schema.TaggedErrorClass()( "ElectronUpdaterCheckForUpdatesError", { channel: Schema.NullOr(Schema.String), cause: Schema.Defect(), }, ) { override get message(): string { return `Electron updater failed to check for updates on channel ${this.channel ?? "default"}.`; } } export class ElectronUpdaterDownloadUpdateError extends Schema.TaggedErrorClass()( "ElectronUpdaterDownloadUpdateError", { channel: Schema.NullOr(Schema.String), cause: Schema.Defect(), }, ) { override get message(): string { return `Electron updater failed to download the update on channel ${this.channel ?? "default"}.`; } } export class ElectronUpdaterQuitAndInstallError extends Schema.TaggedErrorClass()( "ElectronUpdaterQuitAndInstallError", { channel: Schema.NullOr(Schema.String), isSilent: Schema.Boolean, isForceRunAfter: Schema.Boolean, cause: Schema.Defect(), }, ) { override get message(): string { return `Electron updater failed to quit and install the update on channel ${this.channel ?? "default"} (silent: ${this.isSilent}, force run after: ${this.isForceRunAfter}).`; } } export const ElectronUpdaterError = Schema.Union([ ElectronUpdaterCheckForUpdatesError, ElectronUpdaterDownloadUpdateError, ElectronUpdaterQuitAndInstallError, ]); export type ElectronUpdaterError = typeof ElectronUpdaterError.Type; export const isElectronUpdaterError = Schema.is(ElectronUpdaterError); export class ElectronUpdater extends Context.Service< ElectronUpdater, { readonly setFeedURL: (options: ElectronUpdaterFeedUrl) => Effect.Effect; readonly setAutoDownload: (value: boolean) => Effect.Effect; readonly setAutoInstallOnAppQuit: (value: boolean) => Effect.Effect; readonly setChannel: (channel: string) => Effect.Effect; readonly setAllowPrerelease: (value: boolean) => Effect.Effect; readonly allowDowngrade: Effect.Effect; readonly setAllowDowngrade: (value: boolean) => Effect.Effect; readonly setDisableDifferentialDownload: (value: boolean) => Effect.Effect; readonly checkForUpdates: Effect.Effect; readonly downloadUpdate: Effect.Effect; readonly quitAndInstall: (options: { readonly isSilent: boolean; readonly isForceRunAfter: boolean; }) => Effect.Effect; readonly on: >( eventName: string, listener: (...args: Args) => void, ) => Effect.Effect; } >()("@t3tools/desktop/electron/ElectronUpdater") {} export const make = ElectronUpdater.of({ setFeedURL: (options) => Effect.suspend(() => { autoUpdater.setFeedURL(options); return Effect.void; }), setAutoDownload: (value) => Effect.suspend(() => { autoUpdater.autoDownload = value; return Effect.void; }), setAutoInstallOnAppQuit: (value) => Effect.suspend(() => { autoUpdater.autoInstallOnAppQuit = value; return Effect.void; }), setChannel: (channel) => Effect.suspend(() => { autoUpdater.channel = channel; return Effect.void; }), setAllowPrerelease: (value) => Effect.suspend(() => { autoUpdater.allowPrerelease = value; return Effect.void; }), allowDowngrade: Effect.sync(() => autoUpdater.allowDowngrade), setAllowDowngrade: (value) => Effect.suspend(() => { autoUpdater.allowDowngrade = value; return Effect.void; }), setDisableDifferentialDownload: (value) => Effect.suspend(() => { autoUpdater.disableDifferentialDownload = value; return Effect.void; }), checkForUpdates: Effect.suspend(() => { const channel = autoUpdater.channel; return Effect.tryPromise({ try: () => autoUpdater.checkForUpdates(), catch: (cause) => new ElectronUpdaterCheckForUpdatesError({ channel, cause }), }).pipe(Effect.asVoid); }), downloadUpdate: Effect.suspend(() => { const channel = autoUpdater.channel; return Effect.tryPromise({ try: () => autoUpdater.downloadUpdate(), catch: (cause) => new ElectronUpdaterDownloadUpdateError({ channel, cause }), }).pipe(Effect.asVoid); }), quitAndInstall: ({ isSilent, isForceRunAfter }) => Effect.suspend(() => { const channel = autoUpdater.channel; return Effect.try({ try: () => autoUpdater.quitAndInstall(isSilent, isForceRunAfter), catch: (cause) => new ElectronUpdaterQuitAndInstallError({ channel, isSilent, isForceRunAfter, cause, }), }); }), on: (eventName, listener) => { const eventTarget = autoUpdater as unknown as { on: (eventName: string, listener: (...args: Array) => void) => void; removeListener: (eventName: string, listener: (...args: Array) => void) => void; }; const untypedListener = listener as unknown as (...args: Array) => void; return Effect.acquireRelease( Effect.sync(() => { eventTarget.on(eventName, untypedListener); }), () => Effect.sync(() => { eventTarget.removeListener(eventName, untypedListener); }), ).pipe(Effect.asVoid); }, }); export const layer = Layer.succeed(ElectronUpdater, make);