Skip to content

Commit 5d311dc

Browse files
committed
feat: Added test for connect commands (apply, plan, import, etc) + improvements and fixes
1 parent 5584367 commit 5d311dc

12 files changed

Lines changed: 519 additions & 80 deletions

File tree

src/connect/http-routes/create-command.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import WebSocket from 'ws';
66
import { Session, SocketServer } from '../socket-server.js';
77

88
export enum ConnectCommand {
9-
TERMINAL = 'terminal',
9+
TERMINAL = 'start terminal',
1010
APPLY = 'apply',
1111
PLAN = 'plan',
1212
IMPORT = 'import',
1313
REFRESH = 'refresh',
14+
INIT = 'init',
1415
}
1516

1617
interface Params {

src/connect/http-routes/handlers/apply-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
22
import { ConfigFileSchema } from 'codify-schemas';
3-
import fs from 'node:fs/promises';
3+
import * as fs from 'node:fs/promises';
44
import os from 'node:os';
55
import path from 'node:path';
66
import { WebSocket } from 'ws';

src/connect/http-routes/handlers/import-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
22
import { ConfigFileSchema } from 'codify-schemas';
33
import { diffChars } from 'diff';
4-
import fs from 'node:fs/promises';
4+
import * as fs from 'node:fs/promises';
55
import os from 'node:os';
66
import path from 'node:path';
77
import { WebSocket } from 'ws';

src/connect/http-routes/handlers/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ router.post('/session', (req, res) => {
1818
}
1919

2020
const sessionId = socketServer.createSession(clientId);
21-
console.log('Terminal session created!', sessionId)
22-
2321
return res.status(200).json({ sessionId });
2422
})
2523

src/connect/http-routes/handlers/init-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
22
import { diffChars } from 'diff';
3-
import fs from 'node:fs/promises';
3+
import * as fs from 'node:fs/promises';
44
import os from 'node:os';
55
import path from 'node:path';
66
import { WebSocket } from 'ws';
@@ -46,7 +46,7 @@ export function initHandler() {
4646
}
4747

4848
return createCommandHandler({
49-
name: ConnectCommand.IMPORT,
49+
name: ConnectCommand.INIT,
5050
spawnCommand,
5151
onExit
5252
});

src/connect/http-routes/handlers/plan-handler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
22
import { ConfigFileSchema } from 'codify-schemas';
3-
import fs from 'node:fs/promises';
4-
import os from 'node:os';
3+
import * as fs from 'node:fs/promises';
4+
import * as os from 'node:os';
55
import path from 'node:path';
66
import { WebSocket } from 'ws';
77

@@ -25,7 +25,7 @@ export function planHandler() {
2525

2626
const tmpDir = await fs.mkdtemp(os.tmpdir());
2727
const filePath = path.join(tmpDir, 'codify.jsonc');
28-
await fs.writeFile(filePath, JSON.stringify(codifyConfig, null, 2));
28+
await fs.writeFile(filePath, JSON.stringify(codifyConfig, null, 2), { });
2929

3030
session.additionalData.filePath = filePath;
3131

src/connect/http-routes/handlers/refresh-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
22
import { ConfigFileSchema } from 'codify-schemas';
33
import { diffChars } from 'diff';
4-
import fs from 'node:fs/promises';
4+
import * as fs from 'node:fs/promises';
55
import os from 'node:os';
66
import path from 'node:path';
77
import { WebSocket } from 'ws';
@@ -26,7 +26,7 @@ export function refreshHandler() {
2626
}
2727

2828
if (!type || !Object.values(RefreshType).includes(type as RefreshType)) {
29-
throw new Error('Unable to parse import type');
29+
throw new Error('Unable to parse refresh type');
3030
}
3131

3232
if (type === RefreshType.REFRESH_SPECIFIC && (!resourceTypes || !Array.isArray(resourceTypes))) {

src/connect/http-server.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import cors from 'cors';
2+
import express, { json } from 'express';
3+
// @ts-ignore
4+
import killPort from 'kill-port'
5+
import { Server } from 'node:http';
6+
import open from 'open';
7+
8+
import { config } from '../config.js';
9+
import { ProcessName, ctx } from '../events/context.js';
10+
import { Reporter } from '../ui/reporters/reporter.js';
11+
import { registerKillListeners } from '../utils/register-kill-listeners.js';
12+
import router from './http-routes/router.js';
13+
14+
export async function createHttpServer(
15+
connectionSecret: string,
16+
reporter: Reporter,
17+
openBrowser = true,
18+
onOpen?: (connectionCode: string, server: Server) => void
19+
): Promise<Server> {
20+
const app = express();
21+
22+
app.use(cors({ origin: config.corsAllowedOrigins }))
23+
app.use(json())
24+
app.use(createAuthHandler(connectionSecret))
25+
app.use(router);
26+
app.use(errorHandler)
27+
28+
return listen(app, reporter, (server) => {
29+
if (openBrowser) {
30+
open(`${config.dashboardUrl}/connection/success?code=${connectionSecret}`)
31+
console.log(`Open browser window to store code.
32+
33+
If unsuccessful manually enter the code:
34+
${connectionSecret}`)
35+
}
36+
37+
onOpen?.(connectionSecret, server);
38+
});
39+
40+
}
41+
42+
function listen(app: express.Application, reporter: Reporter, onOpen: (server: Server) => void): Promise<Server> {
43+
return new Promise((resolve) => {
44+
const server = app.listen(config.connectServerPort, async (error) => {
45+
if (error) {
46+
47+
// This whole below allows the user to terminate the existing instance and continue
48+
// Use kill-port to terminate the existing instance
49+
if (error.message.includes('EADDRINUSE')) {
50+
const ifTerminate = await reporter.promptConfirmation('An instance of \'codify connect\' is already running. Do you want to terminate the existing instance and continue?');
51+
52+
if (!ifTerminate) {
53+
console.error('\n\nExiting...')
54+
process.exit(1);
55+
}
56+
57+
ctx.processStarted(ProcessName.TERMINATE)
58+
await reporter.displayProgress();
59+
await killPort(config.connectServerPort);
60+
ctx.processFinished(ProcessName.TERMINATE);
61+
await reporter.hide();
62+
63+
setTimeout(() => {
64+
ctx.log('Retrying connection...')
65+
listen(app, reporter, onOpen).then((server) => resolve(server));
66+
}, 300);
67+
}
68+
} else {
69+
resolve(server);
70+
onOpen(server);
71+
}
72+
});
73+
74+
registerKillListeners(() => server.close());
75+
});
76+
}
77+
78+
function createAuthHandler(connectionCode: string) {
79+
return (req, res, next) => {
80+
if (req.header('Authorization') !== connectionCode) {
81+
return res.status(400).json({ error: 'Invalid authorization' })
82+
}
83+
84+
next();
85+
}
86+
}
87+
88+
function errorHandler(err, req, res, next) {
89+
console.log(err.message);
90+
res.status(500).json({ error: err.message });
91+
}

src/connect/socket-server.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,7 @@ export class SocketServer {
7676
private onUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer): void => {
7777
const { pathname } = new URL(request.url!, 'ws://localhost:51040')
7878

79-
// Ignore all socket io so it does not interfere
80-
if (pathname.includes('socket.io')) {
81-
return;
82-
}
83-
84-
if (/*! this.validateOrigin(request.headers.origin ?? request.headers.referer ?? '') || */ !this.validateConnectionSecret(request)) {
79+
if (!this.validateConnectionSecret(request)) {
8580
console.error('Unauthorized request. Connection code:', request.headers['sec-websocket-protocol']);
8681
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
8782
socket.destroy();

src/orchestrators/connect.ts

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
import { Config } from '@oclif/core';
2-
import cors from 'cors';
3-
import express, { json } from 'express';
4-
// @ts-ignore
5-
import killPort from 'kill-port';
61
import { randomBytes } from 'node:crypto';
72
import { Server } from 'node:http';
8-
import open from 'open';
93

10-
import { config } from '../config.js';
11-
import router from '../connect/http-routes/router.js';
4+
import { createHttpServer } from '../connect/http-server.js';
125
import { LoginHelper } from '../connect/login-helper.js';
136
import { SocketServer } from '../connect/socket-server.js';
14-
import { ProcessName, ctx } from '../events/context.js';
7+
import { ctx } from '../events/context.js';
158
import { Reporter } from '../ui/reporters/reporter.js';
169
import { LoginOrchestrator } from './login.js';
17-
import { registerKillListeners } from '../utils/register-kill-listeners.js';
1810

1911
export class ConnectOrchestrator {
2012
static rootCommand: string;
@@ -31,60 +23,11 @@ export class ConnectOrchestrator {
3123
this.nodeBinary = process.execPath;
3224

3325
const connectionSecret = ConnectOrchestrator.tokenGenerate()
34-
const app = express();
35-
36-
app.use(cors({ origin: config.corsAllowedOrigins }))
37-
app.use(json())
38-
app.use(router);
39-
40-
const server = await ConnectOrchestrator.listen(app, reporter, (server) => {
41-
if (openBrowser) {
42-
open(`${config.dashboardUrl}/connection/success?code=${connectionSecret}`)
43-
console.log(`Open browser window to store code.
44-
45-
If unsuccessful manually enter the code:
46-
${connectionSecret}`)
47-
}
48-
49-
onOpen?.(connectionSecret, server);
50-
});
5126

27+
const server = await createHttpServer(connectionSecret, reporter, openBrowser, onOpen);
5228
SocketServer.init(server, connectionSecret);
5329
}
5430

55-
private static listen(app: express.Application, reporter: Reporter, onOpen: (server: Server) => void): Promise<Server> {
56-
return new Promise((resolve) => {
57-
const server = app.listen(config.connectServerPort, async (error) => {
58-
if (error) {
59-
if (error.message.includes('EADDRINUSE')) {
60-
const ifTerminate = await reporter.promptConfirmation('An instance of \'codify connect\' is already running. Do you want to terminate the existing instance and continue?');
61-
62-
if (!ifTerminate) {
63-
console.error('\n\nExiting...')
64-
process.exit(1);
65-
}
66-
67-
ctx.processStarted(ProcessName.TERMINATE)
68-
await reporter.displayProgress();
69-
await killPort(config.connectServerPort);
70-
ctx.processFinished(ProcessName.TERMINATE);
71-
await reporter.hide();
72-
73-
setTimeout(() => {
74-
ctx.log('Retrying connection...')
75-
ConnectOrchestrator.listen(app, reporter, onOpen).then((server) => resolve(server));
76-
}, 300);
77-
78-
}
79-
} else {
80-
resolve(server);
81-
onOpen(server);
82-
}
83-
});
84-
85-
registerKillListeners(() => server.close());
86-
});
87-
}
8831

8932
private static tokenGenerate(bytes = 4): string {
9033
return Buffer.from(randomBytes(bytes)).toString('hex')

0 commit comments

Comments
 (0)