|
1 | 1 | "use strict";
|
2 |
| -import axios, { isAxiosError } from "axios"; |
3 |
| -import { getErrorMessage } from "coder/site/src/api/errors"; |
4 | 2 | import * as module from "module";
|
5 | 3 | import * as vscode from "vscode";
|
6 | 4 | import { makeCoderSdk, needToken } from "./api";
|
7 |
| -import { errToStr } from "./api-helper"; |
8 | 5 | import { Commands } from "./commands";
|
9 |
| -import { getErrorDetail } from "./error"; |
| 6 | +import { ExtensionDependencies } from "./extension/dependencies"; |
| 7 | +import { ExtensionInitializer } from "./extension/initializer"; |
10 | 8 | import { Logger } from "./logger";
|
11 |
| -import { Remote } from "./remote"; |
12 | 9 | import { Storage } from "./storage";
|
13 |
| -import { DefaultUIProvider } from "./uiProvider"; |
14 | 10 | import { toSafeHost } from "./util";
|
15 | 11 | import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider";
|
16 | 12 |
|
17 |
| -class ExtensionDependencies { |
18 |
| - public readonly vscodeProposed: typeof vscode; |
19 |
| - public readonly remoteSSHExtension: vscode.Extension<unknown> | undefined; |
20 |
| - public readonly output: vscode.OutputChannel; |
21 |
| - public readonly storage: Storage; |
22 |
| - public readonly logger: Logger; |
23 |
| - public readonly restClient: ReturnType<typeof makeCoderSdk>; |
24 |
| - public readonly uiProvider: DefaultUIProvider; |
25 |
| - public readonly commands: Commands; |
26 |
| - public readonly myWorkspacesProvider: WorkspaceProvider; |
27 |
| - public readonly allWorkspacesProvider: WorkspaceProvider; |
28 |
| - |
29 |
| - private constructor( |
30 |
| - vscodeProposed: typeof vscode, |
31 |
| - remoteSSHExtension: vscode.Extension<unknown> | undefined, |
32 |
| - output: vscode.OutputChannel, |
33 |
| - storage: Storage, |
34 |
| - logger: Logger, |
35 |
| - restClient: ReturnType<typeof makeCoderSdk>, |
36 |
| - uiProvider: DefaultUIProvider, |
37 |
| - commands: Commands, |
38 |
| - myWorkspacesProvider: WorkspaceProvider, |
39 |
| - allWorkspacesProvider: WorkspaceProvider, |
40 |
| - ) { |
41 |
| - this.vscodeProposed = vscodeProposed; |
42 |
| - this.remoteSSHExtension = remoteSSHExtension; |
43 |
| - this.output = output; |
44 |
| - this.storage = storage; |
45 |
| - this.logger = logger; |
46 |
| - this.restClient = restClient; |
47 |
| - this.uiProvider = uiProvider; |
48 |
| - this.commands = commands; |
49 |
| - this.myWorkspacesProvider = myWorkspacesProvider; |
50 |
| - this.allWorkspacesProvider = allWorkspacesProvider; |
51 |
| - } |
52 |
| - |
53 |
| - static async create( |
54 |
| - ctx: vscode.ExtensionContext, |
55 |
| - ): Promise<ExtensionDependencies> { |
56 |
| - // Setup remote SSH extension |
57 |
| - const { vscodeProposed, remoteSSHExtension } = setupRemoteSSHExtension(); |
58 |
| - |
59 |
| - // Create output channel |
60 |
| - const output = vscode.window.createOutputChannel("Coder"); |
61 |
| - |
62 |
| - // Initialize infrastructure |
63 |
| - const { storage, logger } = await initializeInfrastructure(ctx, output); |
64 |
| - |
65 |
| - // Initialize REST client |
66 |
| - const restClient = await initializeRestClient(storage); |
67 |
| - |
68 |
| - // Setup tree views |
69 |
| - const { myWorkspacesProvider, allWorkspacesProvider } = setupTreeViews( |
70 |
| - restClient, |
71 |
| - storage, |
72 |
| - ); |
73 |
| - |
74 |
| - // Create UI provider and commands |
75 |
| - const uiProvider = new DefaultUIProvider(vscodeProposed.window); |
76 |
| - const commands = new Commands( |
77 |
| - vscodeProposed, |
78 |
| - restClient, |
79 |
| - storage, |
80 |
| - uiProvider, |
81 |
| - ); |
82 |
| - |
83 |
| - return new ExtensionDependencies( |
84 |
| - vscodeProposed, |
85 |
| - remoteSSHExtension, |
86 |
| - output, |
87 |
| - storage, |
88 |
| - logger, |
89 |
| - restClient, |
90 |
| - uiProvider, |
91 |
| - commands, |
92 |
| - myWorkspacesProvider, |
93 |
| - allWorkspacesProvider, |
94 |
| - ); |
95 |
| - } |
96 |
| -} |
97 |
| - |
98 | 13 | export function setupRemoteSSHExtension(): {
|
99 | 14 | vscodeProposed: typeof vscode;
|
100 | 15 | remoteSSHExtension: vscode.Extension<unknown> | undefined;
|
@@ -367,153 +282,6 @@ export function registerCommands(
|
367 | 282 | );
|
368 | 283 | }
|
369 | 284 |
|
370 |
| -class RemoteEnvironmentHandler { |
371 |
| - private readonly vscodeProposed: typeof vscode; |
372 |
| - private readonly remoteSSHExtension: vscode.Extension<unknown> | undefined; |
373 |
| - private readonly restClient: ReturnType<typeof makeCoderSdk>; |
374 |
| - private readonly storage: Storage; |
375 |
| - private readonly commands: Commands; |
376 |
| - private readonly extensionMode: vscode.ExtensionMode; |
377 |
| - |
378 |
| - constructor( |
379 |
| - deps: ExtensionDependencies, |
380 |
| - extensionMode: vscode.ExtensionMode, |
381 |
| - ) { |
382 |
| - this.vscodeProposed = deps.vscodeProposed; |
383 |
| - this.remoteSSHExtension = deps.remoteSSHExtension; |
384 |
| - this.restClient = deps.restClient; |
385 |
| - this.storage = deps.storage; |
386 |
| - this.commands = deps.commands; |
387 |
| - this.extensionMode = extensionMode; |
388 |
| - } |
389 |
| - |
390 |
| - async initialize(): Promise<boolean> { |
391 |
| - // Skip if no remote SSH extension or no remote authority |
392 |
| - if (!this.remoteSSHExtension || !this.vscodeProposed.env.remoteAuthority) { |
393 |
| - return true; // No remote environment to handle |
394 |
| - } |
395 |
| - |
396 |
| - const remote = new Remote( |
397 |
| - this.vscodeProposed, |
398 |
| - this.storage, |
399 |
| - this.commands, |
400 |
| - this.extensionMode, |
401 |
| - ); |
402 |
| - |
403 |
| - try { |
404 |
| - const details = await remote.setup( |
405 |
| - this.vscodeProposed.env.remoteAuthority, |
406 |
| - ); |
407 |
| - if (details) { |
408 |
| - // Authenticate the plugin client |
409 |
| - this.restClient.setHost(details.url); |
410 |
| - this.restClient.setSessionToken(details.token); |
411 |
| - } |
412 |
| - return true; // Success |
413 |
| - } catch (ex) { |
414 |
| - await this.handleRemoteError(ex); |
415 |
| - // Always close remote session when we fail to open a workspace |
416 |
| - await remote.closeRemote(); |
417 |
| - return false; // Failed |
418 |
| - } |
419 |
| - } |
420 |
| - |
421 |
| - private async handleRemoteError(error: unknown): Promise<void> { |
422 |
| - if ( |
423 |
| - error && |
424 |
| - typeof error === "object" && |
425 |
| - "x509Err" in error && |
426 |
| - "showModal" in error |
427 |
| - ) { |
428 |
| - const certError = error as { |
429 |
| - x509Err?: string; |
430 |
| - message?: string; |
431 |
| - showModal: (title: string) => Promise<void>; |
432 |
| - }; |
433 |
| - this.storage.writeToCoderOutputChannel( |
434 |
| - certError.x509Err || certError.message || "Certificate error", |
435 |
| - ); |
436 |
| - await certError.showModal("Failed to open workspace"); |
437 |
| - } else if (isAxiosError(error)) { |
438 |
| - const msg = getErrorMessage(error, "None"); |
439 |
| - const detail = getErrorDetail(error) || "None"; |
440 |
| - const urlString = axios.getUri(error.config); |
441 |
| - const method = error.config?.method?.toUpperCase() || "request"; |
442 |
| - const status = error.response?.status || "None"; |
443 |
| - const message = `API ${method} to '${urlString}' failed.\nStatus code: ${status}\nMessage: ${msg}\nDetail: ${detail}`; |
444 |
| - this.storage.writeToCoderOutputChannel(message); |
445 |
| - await this.vscodeProposed.window.showErrorMessage( |
446 |
| - "Failed to open workspace", |
447 |
| - { |
448 |
| - detail: message, |
449 |
| - modal: true, |
450 |
| - useCustom: true, |
451 |
| - }, |
452 |
| - ); |
453 |
| - } else { |
454 |
| - const message = errToStr(error, "No error message was provided"); |
455 |
| - this.storage.writeToCoderOutputChannel(message); |
456 |
| - await this.vscodeProposed.window.showErrorMessage( |
457 |
| - "Failed to open workspace", |
458 |
| - { |
459 |
| - detail: message, |
460 |
| - modal: true, |
461 |
| - useCustom: true, |
462 |
| - }, |
463 |
| - ); |
464 |
| - } |
465 |
| - } |
466 |
| -} |
467 |
| - |
468 |
| -class ExtensionInitializer { |
469 |
| - private readonly deps: ExtensionDependencies; |
470 |
| - private readonly ctx: vscode.ExtensionContext; |
471 |
| - |
472 |
| - constructor(deps: ExtensionDependencies, ctx: vscode.ExtensionContext) { |
473 |
| - this.deps = deps; |
474 |
| - this.ctx = ctx; |
475 |
| - } |
476 |
| - |
477 |
| - async initialize(): Promise<void> { |
478 |
| - // Register URI handler and commands |
479 |
| - this.registerHandlers(); |
480 |
| - |
481 |
| - // Handle remote environment if applicable |
482 |
| - const remoteHandler = new RemoteEnvironmentHandler( |
483 |
| - this.deps, |
484 |
| - this.ctx.extensionMode, |
485 |
| - ); |
486 |
| - const remoteHandled = await remoteHandler.initialize(); |
487 |
| - if (!remoteHandled) { |
488 |
| - return; // Exit early if remote setup failed |
489 |
| - } |
490 |
| - |
491 |
| - // Initialize authentication |
492 |
| - await initializeAuthentication( |
493 |
| - this.deps.restClient, |
494 |
| - this.deps.storage, |
495 |
| - this.deps.myWorkspacesProvider, |
496 |
| - this.deps.allWorkspacesProvider, |
497 |
| - ); |
498 |
| - } |
499 |
| - |
500 |
| - private registerHandlers(): void { |
501 |
| - // Register URI handler |
502 |
| - registerUriHandler( |
503 |
| - this.deps.commands, |
504 |
| - this.deps.restClient, |
505 |
| - this.deps.storage, |
506 |
| - ); |
507 |
| - |
508 |
| - // Register commands |
509 |
| - registerCommands( |
510 |
| - this.deps.commands, |
511 |
| - this.deps.myWorkspacesProvider, |
512 |
| - this.deps.allWorkspacesProvider, |
513 |
| - ); |
514 |
| - } |
515 |
| -} |
516 |
| - |
517 | 285 | export async function initializeAuthentication(
|
518 | 286 | restClient: ReturnType<typeof makeCoderSdk>,
|
519 | 287 | storage: Storage,
|
|
0 commit comments