mirror of
https://github.com/tiennm99/rplace.git
synced 2026-10-11 03:13:48 +00:00
fix: binary-safe canvas read via Upstash base64 encoding
Use path-based REST API with Upstash-Encoding: base64 header for GETRANGE to prevent binary data corruption through JSON text encoding. Verified all 32 colors round-trip correctly.
This commit is contained in:
1 parent
e3eb34c6de
commit
b35769cc73
2 files changed
+31
-21
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { getRedis, redisRaw } from './redis-client.js';
|
||||
import { redisRaw, redisRawBinary } from './redis-client.js';
|
||||
import { CANVAS_WIDTH, TOTAL_PIXELS, BITS_PER_PIXEL, REDIS_CANVAS_KEY } from './constants.js';
|
||||
|
||||
/** Total bytes needed for the canvas bitfield */
|
||||
@@ -6,55 +6,42 @@ const CANVAS_BYTES = Math.ceil((TOTAL_PIXELS * BITS_PER_PIXEL) / 8);
|
||||
|
||||
/**
|
||||
* Get the full canvas as a Uint8Array of raw bytes.
|
||||
* Uses GETRANGE to fetch raw binary reliably (avoids encoding issues with GET).
|
||||
* Uses raw REST API to avoid SDK binary encoding corruption.
|
||||
* Returns a zero-filled buffer if canvas doesn't exist yet.
|
||||
* @param {object} env
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
export async function getFullCanvas(env) {
|
||||
const redis = getRedis(env);
|
||||
// Use base64 encoding for binary-safe transport of BITFIELD data
|
||||
const base64 = await redisRawBinary(env, ['GETRANGE', REDIS_CANVAS_KEY, '0', String(CANVAS_BYTES - 1)]);
|
||||
|
||||
// Use GETRANGE to fetch the full string as raw bytes
|
||||
// This is more reliable than GET for binary data written by BITFIELD
|
||||
const data = await redis.getrange(REDIS_CANVAS_KEY, 0, CANVAS_BYTES - 1);
|
||||
if (!data || data.length === 0) {
|
||||
if (!base64 || base64.length === 0) {
|
||||
return new Uint8Array(CANVAS_BYTES);
|
||||
}
|
||||
|
||||
// Upstash REST may return base64 or raw string for binary data
|
||||
if (typeof data === 'string') {
|
||||
let raw;
|
||||
try {
|
||||
raw = atob(data);
|
||||
} catch {
|
||||
raw = data; // Already a raw string
|
||||
}
|
||||
const raw = atob(base64);
|
||||
const bytes = new Uint8Array(raw.length);
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
bytes[i] = raw.charCodeAt(i);
|
||||
}
|
||||
// Pad to full canvas size if shorter
|
||||
|
||||
if (bytes.length < CANVAS_BYTES) {
|
||||
const padded = new Uint8Array(CANVAS_BYTES);
|
||||
padded.set(bytes);
|
||||
return padded;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
return new Uint8Array(CANVAS_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple pixels in a single atomic BITFIELD command.
|
||||
* Uses Upstash's builder pattern: redis.bitfield(key).set().set().exec()
|
||||
* Uses raw REST API — SDK bitfield builder is broken in @upstash/redis 1.x.
|
||||
* @param {object} env
|
||||
* @param {Array<{x: number, y: number, color: number}>} pixels
|
||||
*/
|
||||
export async function setPixels(env, pixels) {
|
||||
if (!pixels.length) return;
|
||||
|
||||
// Use raw REST API for BITFIELD — SDK builder API is broken in @upstash/redis 1.x
|
||||
const command = ['BITFIELD', REDIS_CANVAS_KEY];
|
||||
for (const { x, y, color } of pixels) {
|
||||
const offset = y * CANVAS_WIDTH + x;
|
||||
|
||||
@@ -33,3 +33,26 @@ export async function redisRaw(env, command) {
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Redis command via Upstash path-based REST API with base64 response.
|
||||
* Uses Upstash-Encoding: base64 for binary-safe response transport.
|
||||
* @param {object} env
|
||||
* @param {string[]} command - Redis command as array, e.g. ['GETRANGE', 'key', '0', '100']
|
||||
* @returns {Promise<string|null>} base64-encoded result string
|
||||
*/
|
||||
export async function redisRawBinary(env, command) {
|
||||
const path = command.map((arg) => encodeURIComponent(String(arg))).join('/');
|
||||
const res = await fetch(`${env.UPSTASH_REDIS_REST_URL}/${path}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.UPSTASH_REDIS_REST_TOKEN}`,
|
||||
'Upstash-Encoding': 'base64',
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Redis command failed: ${res.status} ${text}`);
|
||||
}
|
||||
const { result } = await res.json();
|
||||
return result; // base64-encoded string
|
||||
}
|
||||
Reference in new issue
Block a user