Skip to content

Commit 442dd0a

Browse files
committed
add scramjetserver package
1 parent 5a9730d commit 442dd0a

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

packages/scramjetserver/index.ts

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import { createServer } from "http";
2+
import {
3+
handleFetch,
4+
CookieJar,
5+
loadCodecs,
6+
setConfig,
7+
ScramjetHeaders,
8+
} from "@mercuryworkshop/scramjet/bundled";
9+
import { createBareServer } from "@nebula-services/bare-server-node";
10+
11+
import { readFileSync } from "fs";
12+
13+
const cookieJar = new CookieJar();
14+
15+
const config = {
16+
wisp: "ws://localhost:1337/",
17+
prefix: "/scramjet/",
18+
globals: {
19+
wrapfn: "$scramjet$wrap",
20+
wrappropertybase: "$scramjet__",
21+
wrappropertyfn: "$scramjet$prop",
22+
cleanrestfn: "$scramjet$clean",
23+
importfn: "$scramjet$import",
24+
rewritefn: "$scramjet$rewrite",
25+
metafn: "$scramjet$meta",
26+
setrealmfn: "$scramjet$setrealm",
27+
pushsourcemapfn: "$scramjet$pushsourcemap",
28+
trysetfn: "$scramjet$tryset",
29+
templocid: "$scramjet$temploc",
30+
tempunusedid: "$scramjet$tempunused",
31+
},
32+
files: {
33+
wasm: "/scram/scramjet.wasm.wasm",
34+
wasmJs: "/scram/scramjet.wasm.wasm.js",
35+
all: "/scram/scramjet.all.js",
36+
sync: "/scram/scramjet.sync.js",
37+
},
38+
flags: {
39+
serviceworkers: false,
40+
syncxhr: false,
41+
strictRewrites: true,
42+
rewriterLogs: false,
43+
captureErrors: true,
44+
cleanErrors: false,
45+
scramitize: false,
46+
sourcemaps: true,
47+
destructureRewrites: false,
48+
interceptDownloads: false,
49+
allowInvalidJs: false,
50+
allowFailedIntercepts: false,
51+
antiAntiDebugger: false,
52+
},
53+
siteFlags: {},
54+
codec: {
55+
encode: `(url) => {
56+
if (!url) return url;
57+
return encodeURIComponent(url);
58+
}`,
59+
decode: `(url) => {
60+
if (!url) return url;
61+
return decodeURIComponent(url);
62+
}`,
63+
},
64+
};
65+
66+
setConfig(config);
67+
loadCodecs();
68+
69+
const bare = createBareServer("/bare/", {
70+
logErrors: true,
71+
blockLocal: false,
72+
});
73+
74+
const wasmData = readFileSync("../scramjet/dist/scramjet.wasm.wasm");
75+
const allData = readFileSync("../scramjet/dist/scramjet.all.js");
76+
77+
const wasmBase64 = wasmData.toString("base64");
78+
let wasmJsPayload = "";
79+
wasmJsPayload +=
80+
"if ('document' in self && document.currentScript) { document.currentScript.remove(); }\n";
81+
wasmJsPayload += `self.WASM = '${wasmBase64}';`;
82+
83+
const server = createServer(async (req, res) => {
84+
try {
85+
if (bare.shouldRoute(req)) {
86+
bare.routeRequest(req, res);
87+
return;
88+
}
89+
90+
const url = new URL(req.url!, `http://${req.headers.host}`);
91+
92+
if (url.pathname === config.files.wasm) {
93+
res.writeHead(200, {
94+
"Content-Type": "application/javascript",
95+
"Content-Length": Buffer.byteLength(wasmJsPayload),
96+
});
97+
res.end(wasmJsPayload);
98+
return;
99+
}
100+
101+
if (url.pathname === config.files.all) {
102+
res.writeHead(200, { "Content-Type": "application/javascript" });
103+
res.end(allData);
104+
return;
105+
}
106+
107+
const initHeaders = new ScramjetHeaders();
108+
for (const [key, value] of Object.entries(req.headers)) {
109+
if (value) {
110+
initHeaders.set(key, value as string);
111+
}
112+
}
113+
114+
if (url.pathname.startsWith(config.prefix)) {
115+
const data = {
116+
rawUrl: url,
117+
rawClientUrl: undefined,
118+
initialHeaders: initHeaders,
119+
method: req.method,
120+
body: null as null | Buffer,
121+
cookieStore: cookieJar,
122+
destination: req.headers["sec-fetch-dest"],
123+
};
124+
125+
if (req.method === "POST" || req.method === "PUT") {
126+
const buffers: any = [];
127+
for await (const chunk of req) {
128+
buffers.push(chunk);
129+
}
130+
data.body = Buffer.concat(buffers);
131+
}
132+
133+
const fetchResponse = await handleFetch.call(
134+
{ dispatchEvent() {} },
135+
data,
136+
config,
137+
{
138+
async fetch(url, options) {
139+
let fetchres = await fetch(url, options);
140+
let rawHeaders = {};
141+
fetchres.headers.forEach((value, key) => {
142+
rawHeaders[key] = value;
143+
});
144+
rawHeaders["set-cookie"] = fetchres.headers.getSetCookie();
145+
146+
// @ts-expect-error
147+
fetchres.rawHeaders = rawHeaders;
148+
return fetchres;
149+
},
150+
},
151+
new URL(`http://${req.headers.host}${config.prefix}`)
152+
);
153+
154+
delete fetchResponse.headers["transfer-encoding"];
155+
delete fetchResponse.headers["content-encoding"];
156+
delete fetchResponse.headers["content-length"];
157+
res.writeHead(
158+
fetchResponse.status,
159+
fetchResponse.statusText,
160+
fetchResponse.headers
161+
);
162+
if (fetchResponse.body) {
163+
if (fetchResponse.body instanceof ReadableStream) {
164+
// Stream the response instead of concatenating chunks
165+
const reader = fetchResponse.body.getReader();
166+
167+
// Process the readable stream properly
168+
const processStream = async () => {
169+
try {
170+
while (true) {
171+
const { done, value } = await reader.read();
172+
if (done) break;
173+
// Write each chunk directly to the response
174+
res.write(Buffer.from(value));
175+
}
176+
res.end();
177+
} catch (error) {
178+
console.error("Error streaming response:", error);
179+
res.end();
180+
}
181+
};
182+
183+
processStream();
184+
} else if (fetchResponse.body instanceof ArrayBuffer) {
185+
res.end(Buffer.from(fetchResponse.body));
186+
} else if (typeof fetchResponse.body === "string") {
187+
res.end(fetchResponse.body);
188+
} else {
189+
res.end(String(fetchResponse.body));
190+
}
191+
} else {
192+
res.end();
193+
}
194+
return;
195+
}
196+
197+
res.writeHead(200, { "Content-Type": "text/html" });
198+
res.end(`<html>
199+
<head><title>Scramjet Server</title></head>
200+
<body>
201+
<h1>Scramjet Serverside Demo</h1>
202+
<form id="proxyForm" onsubmit="event.preventDefault(); navigate();">
203+
<input type="url" id="urlInput" placeholder="https://example.com">
204+
<button type="submit"">Go</button>
205+
</form>
206+
207+
<script>
208+
function navigate() {
209+
const url = document.getElementById('urlInput').value;
210+
if (url) {
211+
const encodedUrl = encodeURIComponent(url);
212+
window.location.href = '/scramjet/' + encodedUrl;
213+
}
214+
}
215+
</script>
216+
`);
217+
} catch (error) {
218+
console.log("what");
219+
console.error("Error processing request:", error);
220+
res.writeHead(500, { "Content-Type": "text/plain" });
221+
res.end("Internal Server Errorr");
222+
}
223+
});
224+
225+
server.on("upgrade", (req, socket, head) => {
226+
if (bare.shouldRoute(req)) {
227+
bare.routeUpgrade(req, socket, head);
228+
} else {
229+
socket.end();
230+
}
231+
});
232+
233+
const PORT = process.env.PORT || 3001;
234+
server.listen(PORT, () => {
235+
console.log(`Scramjet HTTP server running on port ${PORT}`);
236+
console.log(`Bare server running on http://localhost:${PORT}/bare/`);
237+
console.log(
238+
`Scramjet proxy available at http://localhost:${PORT}${config.prefix}{url}`
239+
);
240+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "scramjetserver",
3+
"version": "1.0.0",
4+
"description": "",
5+
"type": "module",
6+
"scripts": {
7+
"start": "node index.ts"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"packageManager": "[email protected]",
13+
"dependencies": {
14+
"@mercuryworkshop/scramjet": "workspace:*",
15+
"@nebula-services/bare-server-node": "^2.0.4"
16+
}
17+
}

0 commit comments

Comments
 (0)