tiennm99 33cfd3d7b3 test: add comprehensive unit tests with Vitest
65 tests covering canvas decoder, pixel buffer, user ID hashing,
canvas storage (with binary encoding regression test), Redis client
API shape, and worker endpoint validation.
2026-04-16 22:37:12 +07:00
2026-04-16 16:27:35 +07:00
2026-04-16 14:36:24 +07:00

rplace

A collaborative pixel art canvas inspired by Reddit's r/place. Place pixels, create art together in real-time.

Features

  • 2048x2048 canvas with 32-color palette (from rplace.live)
  • Real-time updates via WebSocket (Cloudflare Durable Objects)
  • Batch pixel placement up to 32 pixels per request
  • Stackable credit system — earn 1 pixel/sec, stack up to 256, spend in batches
  • Zoom/pan with mouse wheel + drag (desktop) and pinch-zoom + drag (mobile)
  • Long-press to place on touch devices
  • Credit bar with visual regeneration feedback

Tech Stack

Layer Technology
Frontend Svelte 5 (runes) + HTML5 Canvas
Backend Hono on Cloudflare Workers
Real-time WebSocket via Cloudflare Durable Objects
Storage Upstash Redis (BITFIELD for canvas, Lua for rate limiting)
Build Vite

Architecture

Browser (Svelte SPA + WebSocket)
  |  GET  /api/canvas  → full canvas binary (2.5MB raw)
  |  POST /api/place   → batch pixel placement
  |  WS   /api/ws      → Durable Object broadcast room
  v
Cloudflare Worker (Hono)
  ├── Canvas API (read/write pixels via Redis BITFIELD)
  ├── Rate Limiter (Lua script, atomic token bucket)
  └── Durable Object (WebSocket broadcast to all clients)
        ↕
Upstash Redis
  ├── BITFIELD "canvas" (5-bit per pixel, 2048x2048 = 2.62MB)
  └── HASH "credits:{userId}" (lastUpdate + credits)

Getting Started

Prerequisites

Setup

# Clone and install
git clone <repo-url>
cd rplace
npm install

# Configure environment
cp .env.example .env
# Edit .env with your Upstash Redis credentials

# For wrangler (Cloudflare Workers CLI)
npx wrangler secret put UPSTASH_REDIS_REST_URL
npx wrangler secret put UPSTASH_REDIS_REST_TOKEN

Development

# Run worker locally (serves both API and frontend)
npm run dev

# Or run frontend and worker separately
npm run dev:client   # Vite dev server on :5173 (proxies /api to :8787)
npm run dev          # Wrangler dev server on :8787

Deploy

npm run deploy   # Builds frontend + deploys worker to Cloudflare

Project Structure

src/
├── worker.js                          # Hono API entry point
├── durable-objects/
│   └── canvas-room.js                 # WebSocket broadcast room
├── lib/
│   ├── constants.js                   # Config, palette, limits (shared)
│   ├── redis-client.js                # Upstash Redis factory
│   ├── canvas-storage.js              # BITFIELD read/write
│   ├── canvas-decoder.js              # 5-bit → RGBA (client-side)
│   ├── rate-limiter.js                # Lua token bucket
│   └── get-user-id.js                 # IP-based identity
├── client/
│   ├── main.js                        # Svelte mount
│   ├── App.svelte                     # Root + WebSocket + credit timer
│   ├── app.css                        # Global styles
│   └── components/
│       ├── CanvasRenderer.svelte      # Canvas + zoom/pan + touch
│       ├── ColorPicker.svelte         # 32-color palette grid
│       ├── CanvasControls.svelte      # Zoom buttons + coordinates
│       └── UserInfo.svelte            # Credit counter + bar
└── index.html                         # Vite entry

API

GET /api/canvas

Returns the full canvas as raw binary (5-bit packed, ~2.5MB).

POST /api/place

Place pixels on the canvas.

{
  "pixels": [
    { "x": 100, "y": 200, "color": 27 }
  ]
}

Response: { "ok": true, "credits": 255 }

Errors:

  • 400 — invalid pixel data or batch > 32
  • 429 — rate limited (includes retryAfter seconds)

WS /api/ws

WebSocket for real-time pixel updates. Messages are JSON:

{ "type": "pixels", "pixels": [{ "x": 100, "y": 200, "color": 27 }] }

Configuration

Key constants in src/lib/constants.js:

Constant Default Description
CANVAS_WIDTH 2048 Canvas width in pixels
CANVAS_HEIGHT 2048 Canvas height in pixels
MAX_COLORS 32 Number of colors in palette
MAX_BATCH_SIZE 32 Max pixels per placement request
MAX_CREDITS 256 Max stackable credits
CREDIT_REGEN_RATE 1 Credits earned per second

Credits & References

License

MIT

S
Description
Collaborative pixel-art canvas (r/place clone). Svelte 5 + Hono on Cloudflare Workers + Durable Object SQLite.
Readme Apache-2.0
1 MiB
0 Stars 1 Watchers 0 Forks
Languages
JavaScript 61.1%
Svelte 38.6%
HTML 0.2%
CSS 0.1%