mirror of
https://github.com/tiennm99/rplace.git
synced 2026-10-11 03:13:48 +00:00
fix: address code review findings
- Fix WebSocket reconnection logic in App.svelte (was a no-op) - Remove spoofable x-forwarded-for fallback, use cf-connecting-ip only - Wrap Durable Object broadcast in try/catch to prevent place failures - Cache OffscreenCanvas to avoid re-allocation every render frame
This commit is contained in:
1 parent
2ccf1c9779
commit
b3b4916263
5 files changed
+32
-18
No files matched your search
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"frontend-design@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
/** @type {CanvasRenderer} */
|
||||
let canvasRenderer;
|
||||
|
||||
// WebSocket connection for real-time updates
|
||||
$effect(() => {
|
||||
// WebSocket connection with auto-reconnect
|
||||
function connectWebSocket() {
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const ws = new WebSocket(`${protocol}//${location.host}/api/ws`);
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
} catch { /* ignore parse errors */ }
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
// Reconnect after 1s
|
||||
setTimeout(() => { /* effect re-runs on reactive dep change */ }, 1000);
|
||||
};
|
||||
ws.onclose = () => setTimeout(connectWebSocket, 1000);
|
||||
ws.onerror = () => ws.close();
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const ws = connectWebSocket();
|
||||
return () => ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
let lastMouse = { x: 0, y: 0 };
|
||||
let loading = $state(true);
|
||||
|
||||
// Cached offscreen canvas — avoids re-allocation every render
|
||||
const offscreen = new OffscreenCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
const offCtx = offscreen.getContext('2d');
|
||||
|
||||
/** Render ImageData onto visible canvas with current zoom/pan */
|
||||
function render() {
|
||||
if (!canvasEl || !imageData) return;
|
||||
@@ -21,8 +25,7 @@
|
||||
ctx.fillStyle = '#1a1a1a';
|
||||
ctx.fillRect(0, 0, canvasEl.width, canvasEl.height);
|
||||
|
||||
const offscreen = new OffscreenCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
offscreen.getContext('2d').putImageData(imageData, 0, 0);
|
||||
offCtx.putImageData(imageData, 0, 0);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(pan.x, pan.y);
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
* @returns {string} user id prefixed with "anon:"
|
||||
*/
|
||||
export function getUserId(request) {
|
||||
const ip = request.headers.get('cf-connecting-ip')
|
||||
|| request.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
|
||||
|| '127.0.0.1';
|
||||
// CF-Connecting-IP is set by Cloudflare and cannot be spoofed
|
||||
const ip = request.headers.get('cf-connecting-ip') || '127.0.0.1';
|
||||
|
||||
// Simple hash for privacy
|
||||
let hash = 0;
|
||||
|
||||
+11
-7
@@ -63,13 +63,17 @@ app.post('/api/place', async (c) => {
|
||||
// Write pixels to canvas
|
||||
await setPixels(c.env, pixels);
|
||||
|
||||
// Broadcast to all connected WebSocket clients
|
||||
const roomId = c.env.CANVAS_ROOM.idFromName('main');
|
||||
const room = c.env.CANVAS_ROOM.get(roomId);
|
||||
await room.fetch(new Request('http://internal/broadcast', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(pixels),
|
||||
}));
|
||||
// Broadcast to all connected WebSocket clients (non-blocking)
|
||||
try {
|
||||
const roomId = c.env.CANVAS_ROOM.idFromName('main');
|
||||
const room = c.env.CANVAS_ROOM.get(roomId);
|
||||
await room.fetch(new Request('http://internal/broadcast', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(pixels),
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Broadcast failed:', err);
|
||||
}
|
||||
|
||||
return c.json({ ok: true, credits: remaining });
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user