| ... | ... | @@ -3,8 +3,11 @@ import { |
| 3 | 3 | fetchRemoteEnvironmentDescriptor, |
| 4 | 4 | fetchRemoteSessionState, |
| 5 | 5 | issueRemoteWebSocketTicket, |
| 6 | RemoteEnvironmentAuthUndeclaredStatusError, |
| 7 | type RemoteEnvironmentAuthError, |
| 6 | 8 | } from "@t3tools/client-runtime"; |
| 7 | 9 | import { |
| 10 | EnvironmentAuthInvalidError, |
| 8 | 11 | DesktopDiscoveredSshHostSchema, |
| 9 | 12 | DesktopSshBearerBootstrapInputSchema, |
| 10 | 13 | DesktopSshBearerRequestInputSchema, |
| ... | ... | @@ -15,10 +18,15 @@ import { |
| 15 | 18 | DesktopSshPasswordPromptCancelledType, |
| 16 | 19 | DesktopSshPasswordPromptResolutionInputSchema, |
| 17 | 20 | ExecutionEnvironmentDescriptor, |
| 21 | EnvironmentInternalError, |
| 22 | EnvironmentOperationForbiddenError, |
| 23 | EnvironmentRequestInvalidError, |
| 24 | EnvironmentScopeRequiredError, |
| 18 | 25 | AuthAccessTokenResult, |
| 19 | 26 | AuthSessionState, |
| 20 | 27 | AuthWebSocketTicketResult, |
| 21 | 28 | } from "@t3tools/contracts"; |
| 29 | import { SshHttpBridgeError } from "@t3tools/ssh/errors"; |
| 22 | 30 | import { resolveLoopbackSshHttpBaseUrl } from "@t3tools/ssh/tunnel"; |
| 23 | 31 | import * as Data from "effect/Data"; |
| 24 | 32 | import * as Effect from "effect/Effect"; |
| ... | ... | @@ -35,26 +43,68 @@ type DesktopSshEnvironmentRequestOperation = |
| 35 | 43 | | "fetch-session-state" |
| 36 | 44 | | "issue-websocket-ticket"; |
| 37 | 45 | |
| 46 | type DesktopSshEnvironmentRequestCause = RemoteEnvironmentAuthError | SshHttpBridgeError; |
| 47 | |
| 48 | const isEnvironmentAuthInvalidError = Schema.is(EnvironmentAuthInvalidError); |
| 49 | const isEnvironmentInternalError = Schema.is(EnvironmentInternalError); |
| 50 | const isEnvironmentOperationForbiddenError = Schema.is(EnvironmentOperationForbiddenError); |
| 51 | const isEnvironmentRequestInvalidError = Schema.is(EnvironmentRequestInvalidError); |
| 52 | const isEnvironmentScopeRequiredError = Schema.is(EnvironmentScopeRequiredError); |
| 53 | |
| 54 | function readSshHttpStatus(cause: DesktopSshEnvironmentRequestCause): number | null { |
| 55 | if ( |
| 56 | cause instanceof RemoteEnvironmentAuthUndeclaredStatusError || |
| 57 | cause instanceof SshHttpBridgeError |
| 58 | ) { |
| 59 | return cause.status ?? null; |
| 60 | } |
| 61 | if (isEnvironmentRequestInvalidError(cause)) { |
| 62 | return 400; |
| 63 | } |
| 64 | if (isEnvironmentAuthInvalidError(cause)) { |
| 65 | return 401; |
| 66 | } |
| 67 | if (isEnvironmentScopeRequiredError(cause)) { |
| 68 | return 403; |
| 69 | } |
| 70 | if (isEnvironmentOperationForbiddenError(cause)) { |
| 71 | return 403; |
| 72 | } |
| 73 | if (isEnvironmentInternalError(cause)) { |
| 74 | return 500; |
| 75 | } |
| 76 | return null; |
| 77 | } |
| 78 | |
| 38 | 79 | export class DesktopSshEnvironmentRequestError extends Data.TaggedError( |
| 39 | 80 | "DesktopSshEnvironmentRequestError", |
| 40 | 81 | )<{ |
| 41 | 82 | readonly operation: DesktopSshEnvironmentRequestOperation; |
| 42 | | readonly cause: unknown; |
| 83 | readonly cause: DesktopSshEnvironmentRequestCause; |
| 84 | readonly sshHttpStatus: number | null; |
| 43 | 85 | }> { |
| 44 | 86 | override get message() { |
| 45 | | return `SSH remote API request failed during ${this.operation}.`; |
| 87 | const prefix = this.sshHttpStatus === null ? "" : `[ssh_http:${this.sshHttpStatus}] `; |
| 88 | return `${prefix}SSH remote API request failed during ${this.operation}.`; |
| 46 | 89 | } |
| 47 | 90 | } |
| 48 | 91 | |
| 49 | 92 | const withLoopbackSshApi = |
| 50 | | <A, E, R>( |
| 93 | <A, R>( |
| 51 | 94 | operation: DesktopSshEnvironmentRequestOperation, |
| 52 | | use: (httpBaseUrl: string) => Effect.Effect<A, E, R>, |
| 95 | use: (httpBaseUrl: string) => Effect.Effect<A, RemoteEnvironmentAuthError, R>, |
| 53 | 96 | ) => |
| 54 | 97 | (httpBaseUrl: string): Effect.Effect<A, DesktopSshEnvironmentRequestError, R> => |
| 55 | 98 | resolveLoopbackSshHttpBaseUrl(httpBaseUrl).pipe( |
| 56 | 99 | Effect.flatMap(use), |
| 57 | | Effect.mapError((cause) => new DesktopSshEnvironmentRequestError({ operation, cause })), |
| 100 | Effect.mapError( |
| 101 | (cause) => |
| 102 | new DesktopSshEnvironmentRequestError({ |
| 103 | operation, |
| 104 | cause, |
| 105 | sshHttpStatus: readSshHttpStatus(cause), |
| 106 | }), |
| 107 | ), |
| 58 | 108 | ); |
| 59 | 109 | |
| 60 | 110 | export const discoverSshHosts = makeIpcMethod({ |