|
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