| ... | @@ -3,8 +3,11 @@ import { | ... | @@ -3,8 +3,11 @@ import { |
| 3 | fetchRemoteEnvironmentDescriptor, | 3 | fetchRemoteEnvironmentDescriptor, |
| 4 | fetchRemoteSessionState, | 4 | fetchRemoteSessionState, |
| 5 | issueRemoteWebSocketTicket, | 5 | issueRemoteWebSocketTicket, |
| | 6 | RemoteEnvironmentAuthUndeclaredStatusError, |
| | 7 | type RemoteEnvironmentAuthError, |
| 6 | } from "@t3tools/client-runtime"; | 8 | } from "@t3tools/client-runtime"; |
| 7 | import { | 9 | import { |
| | 10 | EnvironmentAuthInvalidError, |
| 8 | DesktopDiscoveredSshHostSchema, | 11 | DesktopDiscoveredSshHostSchema, |
| 9 | DesktopSshBearerBootstrapInputSchema, | 12 | DesktopSshBearerBootstrapInputSchema, |
| 10 | DesktopSshBearerRequestInputSchema, | 13 | DesktopSshBearerRequestInputSchema, |
| ... | @@ -15,10 +18,15 @@ import { | ... | @@ -15,10 +18,15 @@ import { |
| 15 | DesktopSshPasswordPromptCancelledType, | 18 | DesktopSshPasswordPromptCancelledType, |
| 16 | DesktopSshPasswordPromptResolutionInputSchema, | 19 | DesktopSshPasswordPromptResolutionInputSchema, |
| 17 | ExecutionEnvironmentDescriptor, | 20 | ExecutionEnvironmentDescriptor, |
| | 21 | EnvironmentInternalError, |
| | 22 | EnvironmentOperationForbiddenError, |
| | 23 | EnvironmentRequestInvalidError, |
| | 24 | EnvironmentScopeRequiredError, |
| 18 | AuthAccessTokenResult, | 25 | AuthAccessTokenResult, |
| 19 | AuthSessionState, | 26 | AuthSessionState, |
| 20 | AuthWebSocketTicketResult, | 27 | AuthWebSocketTicketResult, |
| 21 | } from "@t3tools/contracts"; | 28 | } from "@t3tools/contracts"; |
| | 29 | import { SshHttpBridgeError } from "@t3tools/ssh/errors"; |
| 22 | import { resolveLoopbackSshHttpBaseUrl } from "@t3tools/ssh/tunnel"; | 30 | import { resolveLoopbackSshHttpBaseUrl } from "@t3tools/ssh/tunnel"; |
| 23 | import * as Data from "effect/Data"; | 31 | import * as Data from "effect/Data"; |
| 24 | import * as Effect from "effect/Effect"; | 32 | import * as Effect from "effect/Effect"; |
| ... | @@ -35,26 +43,68 @@ type DesktopSshEnvironmentRequestOperation = | ... | @@ -35,26 +43,68 @@ type DesktopSshEnvironmentRequestOperation = |
| 35 | | "fetch-session-state" | 43 | | "fetch-session-state" |
| 36 | | "issue-websocket-ticket"; | 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 | export class DesktopSshEnvironmentRequestError extends Data.TaggedError( | 79 | export class DesktopSshEnvironmentRequestError extends Data.TaggedError( |
| 39 | "DesktopSshEnvironmentRequestError", | 80 | "DesktopSshEnvironmentRequestError", |
| 40 | )<{ | 81 | )<{ |
| 41 | readonly operation: DesktopSshEnvironmentRequestOperation; | 82 | readonly operation: DesktopSshEnvironmentRequestOperation; |
| 42 | readonly cause: unknown; | 83 | readonly cause: DesktopSshEnvironmentRequestCause; |
| | 84 | readonly sshHttpStatus: number | null; |
| 43 | }> { | 85 | }> { |
| 44 | override get message() { | 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 | const withLoopbackSshApi = | 92 | const withLoopbackSshApi = |
| 50 | <A, E, R>( | 93 | <A, R>( |
| 51 | operation: DesktopSshEnvironmentRequestOperation, | 94 | operation: DesktopSshEnvironmentRequestOperation, |
| 52 | use: (httpBaseUrl: string) => Effect.Effect<A, E, R>, | 95 | use: (httpBaseUrl: string) => Effect.Effect<A, RemoteEnvironmentAuthError, R>, |
| 53 | ) => | 96 | ) => |
| 54 | (httpBaseUrl: string): Effect.Effect<A, DesktopSshEnvironmentRequestError, R> => | 97 | (httpBaseUrl: string): Effect.Effect<A, DesktopSshEnvironmentRequestError, R> => |
| 55 | resolveLoopbackSshHttpBaseUrl(httpBaseUrl).pipe( | 98 | resolveLoopbackSshHttpBaseUrl(httpBaseUrl).pipe( |
| 56 | Effect.flatMap(use), | 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 | export const discoverSshHosts = makeIpcMethod({ | 110 | export const discoverSshHosts = makeIpcMethod({ |