@owloops/browserbird 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +106 -0
- package/README.md +329 -0
- package/bin/browserbird +11 -0
- package/package.json +68 -0
- package/src/channel/blocks.ts +485 -0
- package/src/channel/coalesce.ts +79 -0
- package/src/channel/commands.ts +216 -0
- package/src/channel/handler.ts +272 -0
- package/src/channel/slack.ts +573 -0
- package/src/channel/types.ts +59 -0
- package/src/cli/banner.ts +10 -0
- package/src/cli/birds.ts +396 -0
- package/src/cli/config.ts +77 -0
- package/src/cli/doctor.ts +63 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/jobs.ts +166 -0
- package/src/cli/logs.ts +67 -0
- package/src/cli/run.ts +148 -0
- package/src/cli/sessions.ts +158 -0
- package/src/cli/style.ts +19 -0
- package/src/config.ts +291 -0
- package/src/core/logger.ts +78 -0
- package/src/core/redact.ts +75 -0
- package/src/core/types.ts +83 -0
- package/src/core/uid.ts +26 -0
- package/src/core/utils.ts +137 -0
- package/src/cron/parse.ts +146 -0
- package/src/cron/scheduler.ts +242 -0
- package/src/daemon.ts +169 -0
- package/src/db/auth.ts +49 -0
- package/src/db/birds.ts +357 -0
- package/src/db/core.ts +377 -0
- package/src/db/index.ts +10 -0
- package/src/db/jobs.ts +289 -0
- package/src/db/logs.ts +64 -0
- package/src/db/messages.ts +79 -0
- package/src/db/path.ts +30 -0
- package/src/db/sessions.ts +165 -0
- package/src/jobs.ts +140 -0
- package/src/provider/claude.test.ts +95 -0
- package/src/provider/claude.ts +196 -0
- package/src/provider/opencode.test.ts +169 -0
- package/src/provider/opencode.ts +248 -0
- package/src/provider/session.ts +65 -0
- package/src/provider/spawn.ts +173 -0
- package/src/provider/stream.ts +67 -0
- package/src/provider/types.ts +24 -0
- package/src/server/auth.ts +135 -0
- package/src/server/health.ts +87 -0
- package/src/server/http.ts +132 -0
- package/src/server/index.ts +6 -0
- package/src/server/lifecycle.ts +135 -0
- package/src/server/routes.ts +1199 -0
- package/src/server/sse.ts +54 -0
- package/src/server/static.ts +45 -0
- package/src/server/vnc-proxy.ts +75 -0
- package/web/dist/assets/index-C6MBAUmO.js +7 -0
- package/web/dist/assets/index-JMPJCJ2F.css +1 -0
- package/web/dist/favicon.svg +5 -0
- package/web/dist/index.html +20 -0
- package/web/dist/logo-icon.png +0 -0
- package/web/dist/logo-icon.svg +5 -0
- package/web/dist/logo.svg +7 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** @fileoverview Web server lifecycle: creation, request routing, and shutdown. */
|
|
2
|
+
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import type { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
5
|
+
import type { Config } from '../core/types.ts';
|
|
6
|
+
import { logger } from '../core/logger.ts';
|
|
7
|
+
import { insertLog } from '../db/index.ts';
|
|
8
|
+
import type { WebServerDeps, WebServerHandle } from './http.ts';
|
|
9
|
+
import { checkAuth, jsonError } from './http.ts';
|
|
10
|
+
import { buildRoutes } from './routes.ts';
|
|
11
|
+
import type { RouteOptions } from './routes.ts';
|
|
12
|
+
import { handleSSE, closeAllSSE } from './sse.ts';
|
|
13
|
+
import { serveStatic } from './static.ts';
|
|
14
|
+
import { handleVncUpgrade } from './vnc-proxy.ts';
|
|
15
|
+
|
|
16
|
+
function setCorsHeaders(getConfig: () => Config, res: ServerResponse): void {
|
|
17
|
+
const origin = getConfig().web.corsOrigin;
|
|
18
|
+
if (!origin) return;
|
|
19
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
20
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
21
|
+
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
|
|
22
|
+
res.setHeader('Access-Control-Max-Age', '86400');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createWebServer(
|
|
26
|
+
getConfig: () => Config,
|
|
27
|
+
signal: AbortSignal,
|
|
28
|
+
getDeps: () => WebServerDeps,
|
|
29
|
+
options: RouteOptions,
|
|
30
|
+
): WebServerHandle {
|
|
31
|
+
const startedAt = Date.now();
|
|
32
|
+
const routes = buildRoutes(getConfig, startedAt, getDeps, options);
|
|
33
|
+
let server: Server | null = null;
|
|
34
|
+
|
|
35
|
+
const requestHandler = async (req: IncomingMessage, res: ServerResponse) => {
|
|
36
|
+
const method = req.method ?? 'GET';
|
|
37
|
+
const urlPath = req.url ?? '/';
|
|
38
|
+
|
|
39
|
+
const qIndex = urlPath.indexOf('?');
|
|
40
|
+
const pathOnly = qIndex !== -1 ? urlPath.slice(0, qIndex) : urlPath;
|
|
41
|
+
|
|
42
|
+
setCorsHeaders(getConfig, res);
|
|
43
|
+
|
|
44
|
+
if (method === 'OPTIONS') {
|
|
45
|
+
res.writeHead(204);
|
|
46
|
+
res.end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (method === 'GET' && pathOnly === '/api/events') {
|
|
51
|
+
handleSSE(getConfig, startedAt, getDeps, req, res);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const route of routes) {
|
|
56
|
+
if (route.method !== method) continue;
|
|
57
|
+
const match = pathOnly.match(route.pattern);
|
|
58
|
+
if (!match) continue;
|
|
59
|
+
|
|
60
|
+
if (!route.skipAuth && !checkAuth(req, res)) return;
|
|
61
|
+
|
|
62
|
+
const params = (match.groups ?? {}) as Record<string, string>;
|
|
63
|
+
try {
|
|
64
|
+
await route.handler(req, res, params);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
67
|
+
logger.error(`api error: ${msg}`);
|
|
68
|
+
insertLog('error', 'api', msg);
|
|
69
|
+
jsonError(res, 'Internal server error', 500);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (method === 'GET') {
|
|
75
|
+
serveStatic(res, pathOnly);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
jsonError(res, 'Not found', 404);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
start() {
|
|
84
|
+
return new Promise<void>((resolve, reject) => {
|
|
85
|
+
server = createServer((req, res) => {
|
|
86
|
+
requestHandler(req, res).catch((err: unknown) => {
|
|
87
|
+
logger.error(
|
|
88
|
+
`unhandled request error: ${err instanceof Error ? err.message : String(err)}`,
|
|
89
|
+
);
|
|
90
|
+
if (!res.headersSent) {
|
|
91
|
+
jsonError(res, 'Internal server error', 500);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
server.on('upgrade', (req, socket, head) => {
|
|
97
|
+
const url = req.url ?? '/';
|
|
98
|
+
const pathOnly = url.indexOf('?') !== -1 ? url.slice(0, url.indexOf('?')) : url;
|
|
99
|
+
if (pathOnly === '/vnc') {
|
|
100
|
+
handleVncUpgrade(getConfig, req, socket, head);
|
|
101
|
+
} else {
|
|
102
|
+
socket.destroy();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
server.on('error', (err) => {
|
|
107
|
+
reject(err);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const config = getConfig();
|
|
111
|
+
server.listen(config.web.port, config.web.host, () => {
|
|
112
|
+
logger.info(`web server listening on http://${config.web.host}:${config.web.port}`);
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
signal.addEventListener('abort', () => {
|
|
117
|
+
server?.close();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
stop() {
|
|
122
|
+
return new Promise<void>((resolve) => {
|
|
123
|
+
if (!server) {
|
|
124
|
+
resolve();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
closeAllSSE();
|
|
128
|
+
server.close(() => {
|
|
129
|
+
logger.info('web server stopped');
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|