| ... | ... | @@ -24,6 +24,39 @@ export interface DesktopIpcMain { |
| 24 | 24 | on(channel: string, listener: DesktopIpcSyncListener): void; |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | export class DesktopIpcRegistrationError extends Schema.TaggedErrorClass<DesktopIpcRegistrationError>()( |
| 28 | "DesktopIpcRegistrationError", |
| 29 | { |
| 30 | handlerKind: Schema.Literals(["invoke", "sync"]), |
| 31 | channel: Schema.String, |
| 32 | cause: Schema.Defect(), |
| 33 | }, |
| 34 | ) { |
| 35 | override get message(): string { |
| 36 | return `Failed to register the ${this.handlerKind} IPC handler for ${this.channel}.`; |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | export class DesktopIpcUnregistrationError extends Schema.TaggedErrorClass<DesktopIpcUnregistrationError>()( |
| 41 | "DesktopIpcUnregistrationError", |
| 42 | { |
| 43 | handlerKind: Schema.Literals(["invoke", "sync"]), |
| 44 | channel: Schema.String, |
| 45 | cause: Schema.Defect(), |
| 46 | }, |
| 47 | ) { |
| 48 | override get message(): string { |
| 49 | return `Failed to unregister the ${this.handlerKind} IPC handler for ${this.channel}.`; |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | export const DesktopIpcError = Schema.Union([ |
| 54 | DesktopIpcRegistrationError, |
| 55 | DesktopIpcUnregistrationError, |
| 56 | ]); |
| 57 | export type DesktopIpcError = typeof DesktopIpcError.Type; |
| 58 | export const isDesktopIpcError = Schema.is(DesktopIpcError); |
| 59 | |
| 27 | 60 | export interface DesktopIpcMethod<E, R> { |
| 28 | 61 | readonly channel: string; |
| 29 | 62 | readonly handler: (raw: unknown) => Effect.Effect<unknown, E, R>; |
| ... | ... | @@ -39,10 +72,10 @@ export class DesktopIpc extends Context.Service< |
| 39 | 72 | { |
| 40 | 73 | readonly handle: <E, R>( |
| 41 | 74 | input: DesktopIpcMethod<E, R>, |
| 42 | | ) => Effect.Effect<void, never, R | Scope.Scope>; |
| 75 | ) => Effect.Effect<void, DesktopIpcRegistrationError, R | Scope.Scope>; |
| 43 | 76 | readonly handleSync: <E, R>( |
| 44 | 77 | input: DesktopSyncIpcMethod<E, R>, |
| 45 | | ) => Effect.Effect<void, never, R | Scope.Scope>; |
| 78 | ) => Effect.Effect<void, DesktopIpcRegistrationError, R | Scope.Scope>; |
| 46 | 79 | } |
| 47 | 80 | >()("@t3tools/desktop/ipc/DesktopIpc") {} |
| 48 | 81 | |
| ... | ... | @@ -57,18 +90,27 @@ export const make = (ipcMain: DesktopIpcMain): DesktopIpc["Service"] => |
| 57 | 90 | const runPromise = Effect.runPromiseWith(context); |
| 58 | 91 | |
| 59 | 92 | yield* Effect.acquireRelease( |
| 60 | | Effect.sync(() => { |
| 61 | | ipcMain.removeHandler(channel); |
| 62 | | ipcMain.handle(channel, (_event, raw) => |
| 63 | | runPromise( |
| 64 | | Effect.gen(function* () { |
| 65 | | yield* Effect.annotateCurrentSpan({ channel }); |
| 66 | | return yield* handler(raw); |
| 67 | | }).pipe(Effect.annotateLogs({ channel }), Effect.withSpan("desktop.ipc.invoke")), |
| 68 | | ), |
| 69 | | ); |
| 93 | Effect.try({ |
| 94 | try: () => { |
| 95 | ipcMain.removeHandler(channel); |
| 96 | ipcMain.handle(channel, (_event, raw) => |
| 97 | runPromise( |
| 98 | Effect.gen(function* () { |
| 99 | yield* Effect.annotateCurrentSpan({ channel }); |
| 100 | return yield* handler(raw); |
| 101 | }).pipe(Effect.annotateLogs({ channel }), Effect.withSpan("desktop.ipc.invoke")), |
| 102 | ), |
| 103 | ); |
| 104 | }, |
| 105 | catch: (cause) => |
| 106 | new DesktopIpcRegistrationError({ handlerKind: "invoke", channel, cause }), |
| 70 | 107 | }), |
| 71 | | () => Effect.sync(() => ipcMain.removeHandler(channel)), |
| 108 | () => |
| 109 | Effect.try({ |
| 110 | try: () => ipcMain.removeHandler(channel), |
| 111 | catch: (cause) => |
| 112 | new DesktopIpcUnregistrationError({ handlerKind: "invoke", channel, cause }), |
| 113 | }).pipe(Effect.orDie), |
| 72 | 114 | ); |
| 73 | 115 | }), |
| 74 | 116 | |
| ... | ... | @@ -81,18 +123,30 @@ export const make = (ipcMain: DesktopIpcMain): DesktopIpc["Service"] => |
| 81 | 123 | const runSync = Effect.runSyncWith(context); |
| 82 | 124 | |
| 83 | 125 | yield* Effect.acquireRelease( |
| 84 | | Effect.sync(() => { |
| 85 | | ipcMain.removeAllListeners(channel); |
| 86 | | ipcMain.on(channel, (event) => { |
| 87 | | event.returnValue = runSync( |
| 88 | | Effect.gen(function* () { |
| 89 | | yield* Effect.annotateCurrentSpan({ channel }); |
| 90 | | return yield* handler(); |
| 91 | | }).pipe(Effect.annotateLogs({ channel }), Effect.withSpan("desktop.ipc.invokeSync")), |
| 92 | | ); |
| 93 | | }); |
| 126 | Effect.try({ |
| 127 | try: () => { |
| 128 | ipcMain.removeAllListeners(channel); |
| 129 | ipcMain.on(channel, (event) => { |
| 130 | event.returnValue = runSync( |
| 131 | Effect.gen(function* () { |
| 132 | yield* Effect.annotateCurrentSpan({ channel }); |
| 133 | return yield* handler(); |
| 134 | }).pipe( |
| 135 | Effect.annotateLogs({ channel }), |
| 136 | Effect.withSpan("desktop.ipc.invokeSync"), |
| 137 | ), |
| 138 | ); |
| 139 | }); |
| 140 | }, |
| 141 | catch: (cause) => |
| 142 | new DesktopIpcRegistrationError({ handlerKind: "sync", channel, cause }), |
| 94 | 143 | }), |
| 95 | | () => Effect.sync(() => ipcMain.removeAllListeners(channel)), |
| 144 | () => |
| 145 | Effect.try({ |
| 146 | try: () => ipcMain.removeAllListeners(channel), |
| 147 | catch: (cause) => |
| 148 | new DesktopIpcUnregistrationError({ handlerKind: "sync", channel, cause }), |
| 149 | }).pipe(Effect.orDie), |
| 96 | 150 | ); |
| 97 | 151 | }), |
| 98 | 152 | }); |