| ... | @@ -3,7 +3,6 @@ import { DesktopSshPasswordPromptResolutionInputSchema } from "@t3tools/contract | ... | @@ -3,7 +3,6 @@ import { DesktopSshPasswordPromptResolutionInputSchema } from "@t3tools/contract |
| 3 | import type { SshPasswordRequest } from "@t3tools/ssh/auth"; | 3 | import type { SshPasswordRequest } from "@t3tools/ssh/auth"; |
| 4 | import * as Context from "effect/Context"; | 4 | import * as Context from "effect/Context"; |
| 5 | import * as Crypto from "effect/Crypto"; | 5 | import * as Crypto from "effect/Crypto"; |
| 6 | import * as Data from "effect/Data"; | | |
| 7 | import * as DateTime from "effect/DateTime"; | 6 | import * as DateTime from "effect/DateTime"; |
| 8 | import * as Deferred from "effect/Deferred"; | 7 | import * as Deferred from "effect/Deferred"; |
| 9 | import * as Duration from "effect/Duration"; | 8 | import * as Duration from "effect/Duration"; |
| ... | @@ -11,93 +10,134 @@ import * as Effect from "effect/Effect"; | ... | @@ -11,93 +10,134 @@ import * as Effect from "effect/Effect"; |
| 11 | import * as Layer from "effect/Layer"; | 10 | import * as Layer from "effect/Layer"; |
| 12 | import * as Option from "effect/Option"; | 11 | import * as Option from "effect/Option"; |
| 13 | import * as Ref from "effect/Ref"; | 12 | import * as Ref from "effect/Ref"; |
| | 13 | import * as Schema from "effect/Schema"; |
| 14 | | 14 | |
| 15 | import * as IpcChannels from "../ipc/channels.ts"; | | |
| 16 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; | 15 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; |
| | 16 | import { SSH_PASSWORD_PROMPT_CHANNEL } from "../ipc/channels.ts"; |
| 17 | | 17 | |
| 18 | const DEFAULT_SSH_PASSWORD_PROMPT_TIMEOUT_MS = 3 * 60 * 1000; | 18 | const DEFAULT_SSH_PASSWORD_PROMPT_TIMEOUT_MS = 3 * 60 * 1000; |
| 19 | const WINDOW_UNAVAILABLE_MESSAGE = "T3 Code window is not available for SSH authentication."; | | |
| 20 | | 19 | |
| 21 | type DesktopSshPasswordPromptResolutionInput = | 20 | type DesktopSshPasswordPromptResolutionInput = |
| 22 | typeof DesktopSshPasswordPromptResolutionInputSchema.Type; | 21 | typeof DesktopSshPasswordPromptResolutionInputSchema.Type; |
| 23 | | 22 | |
| 24 | export class DesktopSshPromptUnavailableError extends Data.TaggedError( | 23 | const WINDOW_UNAVAILABLE_MESSAGE = "T3 Code window is not available for SSH authentication."; |
| 25 | "DesktopSshPromptUnavailableError", | 24 | |
| 26 | )<{ | 25 | export class DesktopSshPromptRequestIdGenerationError extends Schema.TaggedErrorClass<DesktopSshPromptRequestIdGenerationError>()( |
| 27 | readonly reason: string; | 26 | "DesktopSshPromptRequestIdGenerationError", |
| 28 | }> { | 27 | { |
| 29 | override get message() { | 28 | destination: Schema.String, |
| 30 | return this.reason; | 29 | cause: Schema.Defect(), |
| | 30 | }, |
| | 31 | ) { |
| | 32 | override get message(): string { |
| | 33 | return "Secure randomness is unavailable."; |
| 31 | } | 34 | } |
| 32 | } | 35 | } |
| 33 | | 36 | |
| 34 | export class DesktopSshPromptWindowUnavailableError extends Data.TaggedError( | 37 | export class DesktopSshPromptWindowUnavailableError extends Schema.TaggedErrorClass<DesktopSshPromptWindowUnavailableError>()( |
| 35 | "DesktopSshPromptWindowUnavailableError", | 38 | "DesktopSshPromptWindowUnavailableError", |
| 36 | )<{ | 39 | { |
| 37 | readonly destination: string; | 40 | destination: Schema.String, |
| 38 | }> { | 41 | }, |
| 39 | override get message() { | 42 | ) { |
| | 43 | override get message(): string { |
| 40 | return WINDOW_UNAVAILABLE_MESSAGE; | 44 | return WINDOW_UNAVAILABLE_MESSAGE; |
| 41 | } | 45 | } |
| 42 | } | 46 | } |
| 43 | | 47 | |
| 44 | export class DesktopSshPromptSendError extends Data.TaggedError("DesktopSshPromptSendError")<{ | 48 | const isDesktopSshPromptWindowUnavailableError = Schema.is(DesktopSshPromptWindowUnavailableError); |
| 45 | readonly requestId: string; | 49 | |
| 46 | readonly destination: string; | 50 | export class DesktopSshPromptPresentationError extends Schema.TaggedErrorClass<DesktopSshPromptPresentationError>()( |
| 47 | readonly cause: unknown; | 51 | "DesktopSshPromptPresentationError", |
| 48 | }> { | 52 | { |
| 49 | override get message() { | 53 | requestId: Schema.String, |
| 50 | return WINDOW_UNAVAILABLE_MESSAGE; | 54 | destination: Schema.String, |
| | 55 | cause: Schema.Defect(), |
| | 56 | }, |
| | 57 | ) { |
| | 58 | override get message(): string { |
| | 59 | return `Failed to present SSH password prompt for ${this.destination}.`; |
| 51 | } | 60 | } |
| 52 | } | 61 | } |
| 53 | | 62 | |
| 54 | export class DesktopSshPromptTimedOutError extends Data.TaggedError( | 63 | export class DesktopSshPromptTimedOutError extends Schema.TaggedErrorClass<DesktopSshPromptTimedOutError>()( |
| 55 | "DesktopSshPromptTimedOutError", | 64 | "DesktopSshPromptTimedOutError", |
| 56 | )<{ | 65 | { |
| 57 | readonly requestId: string; | 66 | requestId: Schema.String, |
| 58 | readonly destination: string; | 67 | destination: Schema.String, |
| 59 | }> { | 68 | }, |
| 60 | override get message() { | 69 | ) { |
| | 70 | override get message(): string { |
| 61 | return `SSH authentication timed out for ${this.destination}.`; | 71 | return `SSH authentication timed out for ${this.destination}.`; |
| 62 | } | 72 | } |
| 63 | } | 73 | } |
| 64 | | 74 | |
| 65 | export class DesktopSshPromptCancelledError extends Data.TaggedError( | 75 | export class DesktopSshPromptCancelledError extends Schema.TaggedErrorClass<DesktopSshPromptCancelledError>()( |
| 66 | "DesktopSshPromptCancelledError", | 76 | "DesktopSshPromptCancelledError", |
| 67 | )<{ | 77 | { |
| 68 | readonly requestId: string; | 78 | requestId: Schema.String, |
| 69 | readonly destination: string; | 79 | destination: Schema.String, |
| 70 | readonly reason: string; | 80 | }, |
| 71 | }> { | 81 | ) { |
| 72 | override get message() { | 82 | override get message(): string { |
| 73 | return this.reason; | 83 | return `SSH authentication cancelled for ${this.destination}.`; |
| 74 | } | 84 | } |
| 75 | } | 85 | } |
| 76 | | 86 | |
| 77 | export class DesktopSshPromptInvalidRequestIdError extends Data.TaggedError( | 87 | export class DesktopSshPromptWindowClosedError extends Schema.TaggedErrorClass<DesktopSshPromptWindowClosedError>()( |
| | 88 | "DesktopSshPromptWindowClosedError", |
| | 89 | { |
| | 90 | requestId: Schema.String, |
| | 91 | destination: Schema.String, |
| | 92 | }, |
| | 93 | ) { |
| | 94 | override get message(): string { |
| | 95 | return "SSH authentication was cancelled because the app window closed."; |
| | 96 | } |
| | 97 | } |
| | 98 | |
| | 99 | export class DesktopSshPromptServiceStoppedError extends Schema.TaggedErrorClass<DesktopSshPromptServiceStoppedError>()( |
| | 100 | "DesktopSshPromptServiceStoppedError", |
| | 101 | { |
| | 102 | requestId: Schema.String, |
| | 103 | destination: Schema.String, |
| | 104 | }, |
| | 105 | ) { |
| | 106 | override get message(): string { |
| | 107 | return "SSH password prompt service stopped."; |
| | 108 | } |
| | 109 | } |
| | 110 | |
| | 111 | export class DesktopSshPromptInvalidRequestIdError extends Schema.TaggedErrorClass<DesktopSshPromptInvalidRequestIdError>()( |
| 78 | "DesktopSshPromptInvalidRequestIdError", | 112 | "DesktopSshPromptInvalidRequestIdError", |
| 79 | )<{ | 113 | { |
| 80 | readonly requestId: string; | 114 | requestId: Schema.String, |
| 81 | }> { | 115 | }, |
| 82 | override get message() { | 116 | ) { |
| | 117 | override get message(): string { |
| 83 | return "Invalid SSH password prompt id."; | 118 | return "Invalid SSH password prompt id."; |
| 84 | } | 119 | } |
| 85 | } | 120 | } |
| 86 | | 121 | |
| 87 | export class DesktopSshPromptExpiredError extends Data.TaggedError("DesktopSshPromptExpiredError")<{ | 122 | export class DesktopSshPromptExpiredError extends Schema.TaggedErrorClass<DesktopSshPromptExpiredError>()( |
| 88 | readonly requestId: string; | 123 | "DesktopSshPromptExpiredError", |
| 89 | }> { | 124 | { |
| 90 | override get message() { | 125 | requestId: Schema.String, |
| | 126 | }, |
| | 127 | ) { |
| | 128 | override get message(): string { |
| 91 | return "SSH password prompt expired. Try connecting again."; | 129 | return "SSH password prompt expired. Try connecting again."; |
| 92 | } | 130 | } |
| 93 | } | 131 | } |
| 94 | | 132 | |
| 95 | export type DesktopSshPasswordPromptRequestError = | 133 | export type DesktopSshPasswordPromptRequestError = |
| 96 | | DesktopSshPromptUnavailableError | 134 | | DesktopSshPromptRequestIdGenerationError |
| 97 | | DesktopSshPromptWindowUnavailableError | 135 | | DesktopSshPromptWindowUnavailableError |
| 98 | | DesktopSshPromptSendError | 136 | | DesktopSshPromptPresentationError |
| 99 | | DesktopSshPromptTimedOutError | 137 | | DesktopSshPromptTimedOutError |
| 100 | | DesktopSshPromptCancelledError; | 138 | | DesktopSshPromptCancelledError |
| | 139 | | DesktopSshPromptWindowClosedError |
| | 140 | | DesktopSshPromptServiceStoppedError; |
| 101 | | 141 | |
| 102 | export type DesktopSshPasswordPromptResolveError = | 142 | export type DesktopSshPasswordPromptResolveError = |
| 103 | | DesktopSshPromptInvalidRequestIdError | 143 | | DesktopSshPromptInvalidRequestIdError |
| ... | @@ -107,28 +147,28 @@ export type DesktopSshPasswordPromptError = | ... | @@ -107,28 +147,28 @@ export type DesktopSshPasswordPromptError = |
| 107 | | DesktopSshPasswordPromptRequestError | 147 | | DesktopSshPasswordPromptRequestError |
| 108 | | DesktopSshPasswordPromptResolveError; | 148 | | DesktopSshPasswordPromptResolveError; |
| 109 | | 149 | |
| 110 | export function isDesktopSshPasswordPromptCancellation( | 150 | export const DesktopSshPasswordPromptCancellation = Schema.Union([ |
| 111 | error: unknown, | 151 | DesktopSshPromptCancelledError, |
| 112 | ): error is DesktopSshPromptCancelledError | DesktopSshPromptTimedOutError { | 152 | DesktopSshPromptWindowClosedError, |
| 113 | return ( | 153 | DesktopSshPromptServiceStoppedError, |
| 114 | error instanceof DesktopSshPromptCancelledError || | 154 | DesktopSshPromptTimedOutError, |
| 115 | error instanceof DesktopSshPromptTimedOutError | 155 | ]); |
| 116 | ); | 156 | export type DesktopSshPasswordPromptCancellation = typeof DesktopSshPasswordPromptCancellation.Type; |
| 117 | } | | |
| 118 | | 157 | |
| 119 | export interface DesktopSshPasswordPromptsShape { | 158 | export const isDesktopSshPasswordPromptCancellation = Schema.is( |
| 120 | readonly request: ( | 159 | DesktopSshPasswordPromptCancellation, |
| 121 | request: SshPasswordRequest, | 160 | ); |
| 122 | ) => Effect.Effect<string, DesktopSshPasswordPromptRequestError>; | | |
| 123 | readonly resolve: ( | | |
| 124 | input: DesktopSshPasswordPromptResolutionInput, | | |
| 125 | ) => Effect.Effect<void, DesktopSshPasswordPromptResolveError>; | | |
| 126 | readonly cancelPending: (reason: string) => Effect.Effect<void>; | | |
| 127 | } | | |
| 128 | | 161 | |
| 129 | export class DesktopSshPasswordPrompts extends Context.Service< | 162 | export class DesktopSshPasswordPrompts extends Context.Service< |
| 130 | DesktopSshPasswordPrompts, | 163 | DesktopSshPasswordPrompts, |
| 131 | DesktopSshPasswordPromptsShape | 164 | { |
| | 165 | readonly request: ( |
| | 166 | request: SshPasswordRequest, |
| | 167 | ) => Effect.Effect<string, DesktopSshPasswordPromptRequestError>; |
| | 168 | readonly resolve: ( |
| | 169 | input: DesktopSshPasswordPromptResolutionInput, |
| | 170 | ) => Effect.Effect<void, DesktopSshPasswordPromptResolveError>; |
| | 171 | } |
| 132 | >()("@t3tools/desktop/ssh/DesktopSshPasswordPrompts") {} | 172 | >()("@t3tools/desktop/ssh/DesktopSshPasswordPrompts") {} |
| 133 | | 173 | |
| 134 | interface PendingSshPasswordPrompt { | 174 | interface PendingSshPasswordPrompt { |
| ... | @@ -137,7 +177,7 @@ interface PendingSshPasswordPrompt { | ... | @@ -137,7 +177,7 @@ interface PendingSshPasswordPrompt { |
| 137 | readonly deferred: Deferred.Deferred<string, DesktopSshPasswordPromptRequestError>; | 177 | readonly deferred: Deferred.Deferred<string, DesktopSshPasswordPromptRequestError>; |
| 138 | } | 178 | } |
| 139 | | 179 | |
| 140 | interface LayerOptions { | 180 | export interface DesktopSshPasswordPromptsOptions { |
| 141 | readonly passwordPromptTimeoutMs?: number; | 181 | readonly passwordPromptTimeoutMs?: number; |
| 142 | } | 182 | } |
| 143 | | 183 | |
| ... | @@ -161,14 +201,16 @@ const failPending = ( | ... | @@ -161,14 +201,16 @@ const failPending = ( |
| 161 | error: DesktopSshPasswordPromptRequestError, | 201 | error: DesktopSshPasswordPromptRequestError, |
| 162 | ) => Deferred.fail(pending.deferred, error).pipe(Effect.asVoid); | 202 | ) => Deferred.fail(pending.deferred, error).pipe(Effect.asVoid); |
| 163 | | 203 | |
| 164 | const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: LayerOptions = {}) { | 204 | export const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* ( |
| | 205 | options: DesktopSshPasswordPromptsOptions = {}, |
| | 206 | ) { |
| 165 | const electronWindow = yield* ElectronWindow.ElectronWindow; | 207 | const electronWindow = yield* ElectronWindow.ElectronWindow; |
| 166 | const crypto = yield* Crypto.Crypto; | 208 | const crypto = yield* Crypto.Crypto; |
| 167 | const pendingRef = yield* Ref.make(new Map<string, PendingSshPasswordPrompt>()); | 209 | const pendingRef = yield* Ref.make(new Map<string, PendingSshPasswordPrompt>()); |
| 168 | const passwordPromptTimeoutMs = | 210 | const passwordPromptTimeoutMs = |
| 169 | options.passwordPromptTimeoutMs ?? DEFAULT_SSH_PASSWORD_PROMPT_TIMEOUT_MS; | 211 | options.passwordPromptTimeoutMs ?? DEFAULT_SSH_PASSWORD_PROMPT_TIMEOUT_MS; |
| 170 | | 212 | |
| 171 | const cancelPending = (reason: string): Effect.Effect<void> => | 213 | const cancelPending = () => |
| 172 | Ref.getAndSet(pendingRef, new Map()).pipe( | 214 | Ref.getAndSet(pendingRef, new Map()).pipe( |
| 173 | Effect.flatMap((pending) => | 215 | Effect.flatMap((pending) => |
| 174 | Effect.forEach( | 216 | Effect.forEach( |
| ... | @@ -176,10 +218,9 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -176,10 +218,9 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 176 | (entry) => | 218 | (entry) => |
| 177 | failPending( | 219 | failPending( |
| 178 | entry, | 220 | entry, |
| 179 | new DesktopSshPromptCancelledError({ | 221 | new DesktopSshPromptServiceStoppedError({ |
| 180 | requestId: entry.requestId, | 222 | requestId: entry.requestId, |
| 181 | destination: entry.destination, | 223 | destination: entry.destination, |
| 182 | reason, | | |
| 183 | }), | 224 | }), |
| 184 | ), | 225 | ), |
| 185 | { discard: true }, | 226 | { discard: true }, |
| ... | @@ -188,13 +229,11 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -188,13 +229,11 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 188 | Effect.asVoid, | 229 | Effect.asVoid, |
| 189 | ); | 230 | ); |
| 190 | | 231 | |
| 191 | yield* Effect.addFinalizer(() => | 232 | yield* Effect.addFinalizer(() => cancelPending().pipe(Effect.ignore)); |
| 192 | cancelPending("SSH password prompt service stopped.").pipe(Effect.ignore), | | |
| 193 | ); | | |
| 194 | | 233 | |
| 195 | const resolve = Effect.fn("desktop.sshPasswordPrompts.resolve")(function* ( | 234 | const resolve: DesktopSshPasswordPrompts["Service"]["resolve"] = Effect.fn( |
| 196 | input: DesktopSshPasswordPromptResolutionInput, | 235 | "desktop.sshPasswordPrompts.resolve", |
| 197 | ): Effect.fn.Return<void, DesktopSshPasswordPromptResolveError> { | 236 | )(function* (input) { |
| 198 | const requestId = input.requestId.trim(); | 237 | const requestId = input.requestId.trim(); |
| 199 | if (requestId.length === 0) { | 238 | if (requestId.length === 0) { |
| 200 | return yield* new DesktopSshPromptInvalidRequestIdError({ requestId: input.requestId }); | 239 | return yield* new DesktopSshPromptInvalidRequestIdError({ requestId: input.requestId }); |
| ... | @@ -212,7 +251,6 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -212,7 +251,6 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 212 | new DesktopSshPromptCancelledError({ | 251 | new DesktopSshPromptCancelledError({ |
| 213 | requestId, | 252 | requestId, |
| 214 | destination: entry.destination, | 253 | destination: entry.destination, |
| 215 | reason: `SSH authentication cancelled for ${entry.destination}.`, | | |
| 216 | }), | 254 | }), |
| 217 | ); | 255 | ); |
| 218 | return; | 256 | return; |
| ... | @@ -221,9 +259,9 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -221,9 +259,9 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 221 | yield* Deferred.succeed(entry.deferred, input.password).pipe(Effect.asVoid); | 259 | yield* Deferred.succeed(entry.deferred, input.password).pipe(Effect.asVoid); |
| 222 | }); | 260 | }); |
| 223 | | 261 | |
| 224 | const request = Effect.fn("desktop.sshPasswordPrompts.request")(function* ( | 262 | const request: DesktopSshPasswordPrompts["Service"]["request"] = Effect.fn( |
| 225 | input: SshPasswordRequest, | 263 | "desktop.sshPasswordPrompts.request", |
| 226 | ): Effect.fn.Return<string, DesktopSshPasswordPromptRequestError> { | 264 | )(function* (input) { |
| 227 | const window = yield* electronWindow.main; | 265 | const window = yield* electronWindow.main; |
| 228 | if (Option.isNone(window) || window.value.isDestroyed()) { | 266 | if (Option.isNone(window) || window.value.isDestroyed()) { |
| 229 | return yield* new DesktopSshPromptWindowUnavailableError({ | 267 | return yield* new DesktopSshPromptWindowUnavailableError({ |
| ... | @@ -233,7 +271,11 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -233,7 +271,11 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 233 | | 271 | |
| 234 | const requestId = yield* crypto.randomUUIDv4.pipe( | 272 | const requestId = yield* crypto.randomUUIDv4.pipe( |
| 235 | Effect.mapError( | 273 | Effect.mapError( |
| 236 | () => new DesktopSshPromptUnavailableError({ reason: "Secure randomness is unavailable." }), | 274 | (cause) => |
| | 275 | new DesktopSshPromptRequestIdGenerationError({ |
| | 276 | destination: input.destination, |
| | 277 | cause, |
| | 278 | }), |
| 237 | ), | 279 | ), |
| 238 | ); | 280 | ); |
| 239 | const now = yield* DateTime.now; | 281 | const now = yield* DateTime.now; |
| ... | @@ -267,10 +309,9 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -267,10 +309,9 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 267 | onSome: (pending) => | 309 | onSome: (pending) => |
| 268 | failPending( | 310 | failPending( |
| 269 | pending, | 311 | pending, |
| 270 | new DesktopSshPromptCancelledError({ | 312 | new DesktopSshPromptWindowClosedError({ |
| 271 | requestId, | 313 | requestId, |
| 272 | destination: input.destination, | 314 | destination: input.destination, |
| 273 | reason: "SSH authentication was cancelled because the app window closed.", | | |
| 274 | }), | 315 | }), |
| 275 | ), | 316 | ), |
| 276 | }), | 317 | }), |
| ... | @@ -302,36 +343,43 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La | ... | @@ -302,36 +343,43 @@ const make = Effect.fn("desktop.sshPasswordPrompts.make")(function* (options: La |
| 302 | return yield* Effect.try({ | 343 | return yield* Effect.try({ |
| 303 | try: () => { | 344 | try: () => { |
| 304 | if (window.value.isDestroyed()) { | 345 | if (window.value.isDestroyed()) { |
| 305 | throw new Error(WINDOW_UNAVAILABLE_MESSAGE); | 346 | throw new DesktopSshPromptWindowUnavailableError({ |
| | 347 | destination: input.destination, |
| | 348 | }); |
| 306 | } | 349 | } |
| 307 | window.value.once("closed", cancelOnWindowClosed); | 350 | window.value.once("closed", cancelOnWindowClosed); |
| 308 | window.value.webContents.send(IpcChannels.SSH_PASSWORD_PROMPT_CHANNEL, promptRequest); | 351 | window.value.webContents.send(SSH_PASSWORD_PROMPT_CHANNEL, promptRequest); |
| 309 | if (window.value.isDestroyed()) { | 352 | if (window.value.isDestroyed()) { |
| 310 | throw new Error(WINDOW_UNAVAILABLE_MESSAGE); | 353 | throw new DesktopSshPromptWindowUnavailableError({ |
| | 354 | destination: input.destination, |
| | 355 | }); |
| 311 | } | 356 | } |
| 312 | if (window.value.isMinimized()) { | 357 | if (window.value.isMinimized()) { |
| 313 | window.value.restore(); | 358 | window.value.restore(); |
| 314 | } | 359 | } |
| 315 | if (window.value.isDestroyed()) { | 360 | if (window.value.isDestroyed()) { |
| 316 | throw new Error(WINDOW_UNAVAILABLE_MESSAGE); | 361 | throw new DesktopSshPromptWindowUnavailableError({ |
| | 362 | destination: input.destination, |
| | 363 | }); |
| 317 | } | 364 | } |
| 318 | window.value.focus(); | 365 | window.value.focus(); |
| 319 | }, | 366 | }, |
| 320 | catch: (cause) => | 367 | catch: (cause) => |
| 321 | new DesktopSshPromptSendError({ | 368 | isDesktopSshPromptWindowUnavailableError(cause) |
| 322 | requestId, | 369 | ? cause |
| 323 | destination: input.destination, | 370 | : new DesktopSshPromptPresentationError({ |
| 324 | cause, | 371 | requestId, |
| 325 | }), | 372 | destination: input.destination, |
| | 373 | cause, |
| | 374 | }), |
| 326 | }).pipe(Effect.andThen(waitForPassword), Effect.ensuring(cleanup)); | 375 | }).pipe(Effect.andThen(waitForPassword), Effect.ensuring(cleanup)); |
| 327 | }); | 376 | }); |
| 328 | | 377 | |
| 329 | return DesktopSshPasswordPrompts.of({ | 378 | return DesktopSshPasswordPrompts.of({ |
| 330 | request, | 379 | request, |
| 331 | resolve, | 380 | resolve, |
| 332 | cancelPending, | | |
| 333 | }); | 381 | }); |
| 334 | }); | 382 | }); |
| 335 | | 383 | |
| 336 | export const layer = (options: LayerOptions = {}) => | 384 | export const layer = (options: DesktopSshPasswordPromptsOptions = {}) => |
| 337 | Layer.effect(DesktopSshPasswordPrompts, make(options)); | 385 | Layer.effect(DesktopSshPasswordPrompts, make(options)); |