Skip to content

Commit b1fa27e

Browse files
committed
[scramjet] create proxy-bootstrap and scramjet-controller packages
1 parent 11b3de1 commit b1fa27e

File tree

11 files changed

+650
-52
lines changed

11 files changed

+650
-52
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@mercuryworkshop/proxy-bootstrap",
3+
"version": "0.0.1",
4+
"type": "module",
5+
"packageManager": "[email protected]",
6+
"main": "./dist/bootstrap.js",
7+
"dependencies": {
8+
"server": "link:@mercuryworkshop/wisp-js/server",
9+
"tar": "^7.5.1"
10+
}
11+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { server as wisp } from "@mercuryworkshop/wisp-js/server";
2+
import http from "http";
3+
import { extract } from "tar";
4+
import { Readable } from "stream";
5+
import fs from "fs/promises";
6+
7+
const REGISTRY_URL = "https://registry.npmjs.org/";
8+
const SCRAMJET_PACKAGE_NAME = "@mercuryworkshop/scramjet";
9+
const SCRAMJET_CONTROLLER_PACKAGE_NAME = "@mercuryworkshop/scramjet-controller";
10+
11+
export type TransportOptions = "epoxy" | "libcurl" | "bare";
12+
13+
export type ScramjetBootstrapOptions = {
14+
transport: TransportOptions;
15+
swPath: string;
16+
17+
wispPath: string;
18+
19+
scramjetBundlePath: string;
20+
scramjetWasmPath: string;
21+
22+
epoxyClientPath: string;
23+
libcurlClientPath: string;
24+
bareClientPath: string;
25+
26+
scramjetVersionPin: string;
27+
scramjetControllerVersionPin: string;
28+
29+
downloadedFilesDir: string;
30+
};
31+
32+
const defaultConfig: Partial<ScramjetBootstrapOptions> = {
33+
transport: "libcurl",
34+
swPath: "/bootstrap-sw.js",
35+
wispPath: "/wisp/",
36+
37+
epoxyClientPath: "/clients/epoxy-client.js",
38+
libcurlClientPath: "/clients/libcurl-client.js",
39+
bareClientPath: "/clients/bare-client.js",
40+
41+
scramjetBundlePath: "/scram/scramjet.js",
42+
scramjetWasmPath: "/scram/scramjet.wasm",
43+
44+
downloadedFilesDir: import.meta.dirname + "/.downloads/",
45+
};
46+
47+
let config: ScramjetBootstrapOptions;
48+
49+
function routeRequest(
50+
req: http.IncomingMessage,
51+
res: http.ServerResponse
52+
): boolean {
53+
if (!req.url) return false;
54+
55+
if (req.url === config.swPath) {
56+
res.writeHead(200, { "Content-Type": "application/javascript" });
57+
}
58+
59+
return true;
60+
}
61+
62+
function routeUpgrade(
63+
req: http.IncomingMessage,
64+
socket: any,
65+
head: Buffer
66+
): boolean {
67+
if (!req.url) return false;
68+
if (!req.url.startsWith("/wisp/")) return false;
69+
70+
wisp.routeRequest(req, socket, head);
71+
return true;
72+
}
73+
74+
export async function unpack(tarball: string, name: string) {
75+
if (!name) throw new Error("no package name!");
76+
const response = await fetch(tarball);
77+
if (!response.ok) {
78+
throw new Error(`Failed to download tarball: ${response.statusText}`);
79+
}
80+
81+
const arrayBuffer = await response.arrayBuffer();
82+
const buffer = Buffer.from(arrayBuffer);
83+
84+
await fs.mkdir(config.downloadedFilesDir, { recursive: true });
85+
const file = `${config.downloadedFilesDir}${name}.tgz`;
86+
await fs.writeFile(file, buffer);
87+
88+
const packagedir = `${config.downloadedFilesDir}/${name}`;
89+
90+
if (await fs.stat(packagedir).catch(() => false)) {
91+
await fs.rmdir(packagedir, { recursive: true });
92+
}
93+
await fs.mkdir(packagedir, { recursive: true });
94+
95+
try {
96+
await extract({
97+
f: file,
98+
cwd: packagedir,
99+
});
100+
await fs.unlink(file);
101+
} catch (err) {
102+
console.error("Error extracting tarball:", err);
103+
await fs.unlink(file);
104+
throw err;
105+
}
106+
}
107+
108+
async function getDownloadedControllerVersion(): Promise<string | null> {
109+
const packagedir = `${config.downloadedFilesDir}/controller`;
110+
try {
111+
const pkgJson = JSON.parse(
112+
(await fs.readFile(
113+
`${packagedir}/package/package.json`,
114+
"utf-8"
115+
)) as unknown as string
116+
);
117+
return pkgJson.version;
118+
} catch {
119+
return null;
120+
}
121+
}
122+
123+
async function updateScramjet(controllerMeta: any) {
124+
const scramjetVersion =
125+
controllerMeta.dependencies["@mercuryworkshop/scramjet"];
126+
127+
const scramjetRes = await fetch(
128+
`${REGISTRY_URL}${SCRAMJET_PACKAGE_NAME}/${scramjetVersion}`
129+
);
130+
const scramjetMeta = await scramjetRes.json();
131+
132+
await unpack(scramjetMeta.dist.tarball, "scramjet");
133+
await unpack(controllerMeta.dist.tarball, "controller");
134+
}
135+
136+
export async function bootstrap(
137+
cfg: Partial<ScramjetBootstrapOptions> = {}
138+
): Promise<{
139+
routeRequest: typeof routeRequest;
140+
routeUpgrade: typeof routeUpgrade;
141+
}> {
142+
config = { ...defaultConfig, ...cfg } as ScramjetBootstrapOptions;
143+
144+
const downloadedControllerVersion = await getDownloadedControllerVersion();
145+
if (downloadedControllerVersion) {
146+
console.log(
147+
`Found downloaded Scramjet Controller version: ${downloadedControllerVersion}`
148+
);
149+
}
150+
151+
const controllerRes = await fetch(
152+
`${REGISTRY_URL}${SCRAMJET_CONTROLLER_PACKAGE_NAME}/0.0.2`
153+
);
154+
const controllerMeta = await controllerRes.json();
155+
156+
if (downloadedControllerVersion === controllerMeta.version) {
157+
console.log(
158+
`Scramjet Controller is up to date (version: ${downloadedControllerVersion}), skipping download.`
159+
);
160+
} else {
161+
await updateScramjet(controllerMeta);
162+
console.log(
163+
`Downloaded Scramjet Controller version: ${controllerMeta.version}`
164+
);
165+
}
166+
167+
return {
168+
routeRequest,
169+
routeUpgrade,
170+
};
171+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@mercuryworkshop/scramjet-controller",
3+
"version": "0.0.2",
4+
"type": "module",
5+
"packageManager": "[email protected]",
6+
"main": "index.ts",
7+
"dependencies": {
8+
"@mercuryworkshop/scramjet": "workspace:*"
9+
}
10+
}

packages/scramjet/packages/controller/src/index.ts

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@mercuryworkshop/scramjet-app",
3+
"version": "0.0.1",
4+
"type": "module",
5+
"packageManager": "[email protected]",
6+
"dependencies": {
7+
"@mercuryworkshop/proxy-bootstrap": "workspace:*",
8+
"@types/express": "^5.0.5",
9+
"express": "^5.1.0"
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { bootstrap } from "@mercuryworkshop/proxy-bootstrap";
2+
import express from "express";
3+
4+
const PORT = process.env.PORT || 8080;
5+
const app = express();
6+
7+
app.use(express.static("public"));
8+
9+
const { routeRequest, routeUpgrade } = await bootstrap();
10+
11+
const server = app.listen(PORT, () => {
12+
console.log(`Server is running on http://localhost:${PORT}`);
13+
});
14+
15+
server.on("request", routeRequest);
16+
server.on("upgrade", routeUpgrade);

0 commit comments

Comments
 (0)