Skip to content

Commit 8f2cef5

Browse files
cpb8010Copilot
andauthored
feat: add CORS bundler (#33)
* feat: add CORS bundler Different config so it's the same port externally, otherwise the front-end can't use this * chore: linter toml Having problems with running a script on CI * Update bundler-proxy.js Preserve headers from Alto via CORS proxy Co-authored-by: Copilot <[email protected]> * fix: code review comments * removed unused test data file * moved javascript files into js folder * changed bundler startup * proxy all headers from alto --------- Co-authored-by: Copilot <[email protected]>
1 parent 263cacf commit 8f2cef5

File tree

7 files changed

+263
-9
lines changed

7 files changed

+263
-9
lines changed

alto-with-proxy.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"entrypoints": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
3+
"executor-private-keys": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
4+
"utility-private-key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
5+
"rpc-url": "http://localhost:8545",
6+
"port": 4338,
7+
"safe-mode": false
8+
}

alto.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"entrypoints": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
3-
"executor-private-keys": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
4-
"utility-private-key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
5-
"rpc-url": "http://localhost:8545",
6-
"port": 4337,
7-
"safe-mode": false
2+
"entrypoints": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
3+
"executor-private-keys": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
4+
"utility-private-key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
5+
"rpc-url": "http://localhost:8545",
6+
"port": 4337,
7+
"safe-mode": false
88
}

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ eth-infinitism-account-abstraction = "0.8.0"
2626
"@openzeppelin-contracts" = "5.4.0"
2727
solady = "0.1.24"
2828

29-
[lint]
29+
[profile.lint]
3030
lint_on_build = true
3131
severity = ["high", "med", "low", "info"]
3232
exclude_lints = [

js/bundler-proxy.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Simple CORS proxy for Alto bundler
5+
* Listens on port 4337 and forwards requests to Alto on port 4338
6+
*/
7+
8+
// eslint-disable-next-line @typescript-eslint/no-require-imports
9+
const http = require("http");
10+
11+
const PROXY_PORT = 4337;
12+
const ALTO_PORT = 4338;
13+
const ALTO_HOST = "localhost";
14+
15+
const server = http.createServer((req, res) => {
16+
// Set CORS headers
17+
res.setHeader("Access-Control-Allow-Origin", "*");
18+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
19+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
20+
res.setHeader("Access-Control-Max-Age", "86400"); // 24 hours
21+
22+
// Handle preflight requests
23+
if (req.method === "OPTIONS") {
24+
res.writeHead(200);
25+
res.end();
26+
return;
27+
}
28+
29+
// Collect request body
30+
const chunks = [];
31+
req.on("data", (chunk) => {
32+
chunks.push(chunk);
33+
});
34+
35+
req.on("end", () => {
36+
const body = Buffer.concat(chunks).toString();
37+
// Forward request to Alto
38+
// Clone original headers and update Content-Length
39+
const headers = { ...req.headers };
40+
headers["content-length"] = Buffer.byteLength(body);
41+
const options = {
42+
hostname: ALTO_HOST,
43+
port: ALTO_PORT,
44+
path: req.url,
45+
method: req.method,
46+
headers,
47+
};
48+
49+
const proxyReq = http.request(options, (proxyRes) => {
50+
// Forward status code
51+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
52+
53+
// Forward response body
54+
proxyRes.pipe(res);
55+
});
56+
57+
proxyReq.on("error", (error) => {
58+
console.error("Proxy error:", error);
59+
res.setHeader('Content-Type', 'application/json');
60+
res.writeHead(502);
61+
res.end(JSON.stringify({ error: "Bad Gateway", message: error.message }));
62+
});
63+
64+
// Send request body to Alto
65+
if (body) {
66+
proxyReq.write(body);
67+
}
68+
proxyReq.end();
69+
});
70+
});
71+
72+
server.listen(PROXY_PORT, () => {
73+
console.log(`CORS proxy listening on port ${PROXY_PORT}`);
74+
75+
console.log(`Forwarding requests to Alto at ${ALTO_HOST}:${ALTO_PORT}`);
76+
77+
console.log(`CORS enabled for all origins (*)`);
78+
});
79+
80+
server.on("error", (error) => {
81+
if (error.code === "EADDRINUSE") {
82+
console.error(`Port ${PROXY_PORT} is already in use. Please stop the other service first.`);
83+
} else {
84+
console.error("Server error:", error);
85+
}
86+
process.exit(1);
87+
});

js/bundler-with-proxy.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Runs Alto bundler on port 4338 and CORS proxy on port 4337
5+
* This allows browser-based applications to interact with Alto without CORS issues
6+
*/
7+
8+
// eslint-disable-next-line @typescript-eslint/no-require-imports
9+
const { spawn } = require("child_process");
10+
// eslint-disable-next-line @typescript-eslint/no-require-imports
11+
const path = require("path");
12+
// eslint-disable-next-line @typescript-eslint/no-require-imports
13+
const net = require("net");
14+
15+
const ALTO_CONFIG = path.join(__dirname, "..", "alto-with-proxy.json");
16+
const PROXY_SCRIPT = path.join(__dirname, "bundler-proxy.js");
17+
18+
// ANSI color codes
19+
const colors = {
20+
reset: "\x1b[0m",
21+
cyan: "\x1b[36m",
22+
green: "\x1b[32m",
23+
yellow: "\x1b[33m",
24+
red: "\x1b[31m",
25+
};
26+
27+
function log(prefix, message, color = colors.reset) {
28+
console.log(`${color}[${prefix}]${colors.reset} ${message}`);
29+
}
30+
31+
/**
32+
* Check if a port is listening
33+
* @param {number} port - The port to check
34+
* @param {number} maxAttempts - Maximum number of attempts
35+
* @param {number} delayMs - Delay between attempts in milliseconds
36+
* @returns {Promise<boolean>} - True if port is listening, false otherwise
37+
*/
38+
async function waitForPort(port, maxAttempts = 30, delayMs = 1000) {
39+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
40+
const isListening = await new Promise((resolve) => {
41+
const client = new net.Socket();
42+
43+
client.once("connect", () => {
44+
client.destroy();
45+
resolve(true);
46+
});
47+
48+
client.once("error", () => {
49+
client.destroy();
50+
resolve(false);
51+
});
52+
53+
client.connect(port, "localhost");
54+
});
55+
56+
if (isListening) {
57+
return true;
58+
}
59+
60+
if (attempt < maxAttempts) {
61+
await new Promise((resolve) => setTimeout(resolve, delayMs));
62+
}
63+
}
64+
65+
return false;
66+
}
67+
68+
let proxy;
69+
70+
// Start Alto bundler
71+
log("SETUP", "Starting Alto bundler on port 4338...", colors.cyan);
72+
const alto = spawn("alto", ["--config", ALTO_CONFIG], {
73+
stdio: "inherit",
74+
shell: true,
75+
});
76+
77+
alto.on("error", (error) => {
78+
log("ALTO", `Failed to start: ${error.message}`, colors.red);
79+
process.exit(1);
80+
});
81+
82+
alto.on("exit", (code) => {
83+
log("ALTO", `Exited with code ${code}`, colors.yellow);
84+
// Kill proxy if alto exits
85+
if (proxy) {
86+
proxy.kill();
87+
}
88+
process.exit(code);
89+
});
90+
91+
// Handle cleanup on exit - register handlers early to avoid race condition
92+
process.on("SIGINT", () => {
93+
log("SETUP", "Shutting down...", colors.yellow);
94+
alto.kill();
95+
if (proxy) {
96+
proxy.kill();
97+
}
98+
process.exit(0);
99+
});
100+
101+
process.on("SIGTERM", () => {
102+
log("SETUP", "Shutting down...", colors.yellow);
103+
alto.kill();
104+
if (proxy) {
105+
proxy.kill();
106+
}
107+
process.exit(0);
108+
});
109+
110+
// Wait for Alto to be ready before starting proxy
111+
(async () => {
112+
log("SETUP", "Waiting for Alto bundler to be ready on port 4338...", colors.cyan);
113+
114+
const altoReady = await waitForPort(4338);
115+
if (!altoReady) {
116+
log("ALTO", "Failed to start - port 4338 not listening after 30 seconds", colors.red);
117+
alto.kill();
118+
process.exit(1);
119+
}
120+
121+
log("ALTO", "Ready and listening on port 4338", colors.green);
122+
log("SETUP", "Starting CORS proxy on port 4337...", colors.cyan);
123+
124+
proxy = spawn("node", [PROXY_SCRIPT], {
125+
stdio: "inherit",
126+
shell: true,
127+
});
128+
129+
proxy.on("error", (error) => {
130+
log("PROXY", `Failed to start: ${error.message}`, colors.red);
131+
alto.kill();
132+
process.exit(1);
133+
});
134+
135+
proxy.on("exit", (code) => {
136+
log("PROXY", `Exited with code ${code}`, colors.yellow);
137+
// Kill alto if proxy exits
138+
alto.kill();
139+
process.exit(code);
140+
});
141+
142+
// Wait for proxy to be ready
143+
log("SETUP", "Waiting for CORS proxy to be ready on port 4337...", colors.cyan);
144+
const proxyReady = await waitForPort(4337);
145+
if (!proxyReady) {
146+
log("PROXY", "Failed to start - port 4337 not listening after 30 seconds", colors.red);
147+
alto.kill();
148+
proxy.kill();
149+
process.exit(1);
150+
}
151+
152+
log("PROXY", "Ready and listening on port 4337", colors.green);
153+
log("SETUP", "Both services started successfully!", colors.green);
154+
log("SETUP", "Alto bundler: http://localhost:4338", colors.green);
155+
log("SETUP", "CORS proxy: http://localhost:4337", colors.green);
156+
})();

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"main": "index.js",
66
"scripts": {
77
"bundler": "alto --config ./alto.json",
8+
"bundler:with-proxy": "node js/bundler-with-proxy.js",
9+
"bundler-proxy": "node js/bundler-proxy.js",
810
"test": "node --test --import tsx --test-concurrency=1 test/integration/*.test.ts",
911
"anvil": "anvil --fork-url https://reth-ethereum.ithaca.xyz/rpc --chain-id 1337",
1012
"coverage": "forge coverage --ir-minimum --no-match-coverage 'script|test'",

src/libraries/SessionLib.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,9 @@ library SessionLib {
318318
shrinkRange(timeRange, newValidAfter, newValidUntil);
319319

320320
for (uint256 i = 0; i < callPolicy.constraints.length; ++i) {
321-
(newValidAfter, newValidUntil) = callPolicy.constraints[i]
322-
.checkAndUpdate(state.params[target][selector][i], callData, periodIds[2 + i]);
321+
(newValidAfter, newValidUntil) = callPolicy.constraints[i].checkAndUpdate(
322+
state.params[target][selector][i], callData, periodIds[2 + i]
323+
);
323324
shrinkRange(timeRange, newValidAfter, newValidUntil);
324325
}
325326
} else {

0 commit comments

Comments
 (0)