Files
GMW/services/backend/src/ws/redis-bridge.ts
T
asepharyana 6293d588bc chore(lint): biome cleanup across services — format, sort imports, drop unused
- discord-gateway: 74 lint errors -> 0 (format, import sorting, unused
  imports/vars, dead breath var)
- backend: format + sort imports (11 warnings left: noExplicitAny)
- frontend: remove unused imports, drop dead breathing var, fix
  useExhaustiveDependencies (scroll keyed on messages), a11y biome-ignore
  for drag surface + stopPropagation container (mouse-only gestures)
- remaining warnings are false positives: index keys on static lists,
  <img> in static export (next/image unsupported), noExplicitAny

tsc --noEmit clean on all 3 services; vitest green (60+36).
2026-08-01 22:09:02 +07:00

137 lines
3.8 KiB
TypeScript

import Redis from "ioredis";
import { config } from "../shared/config/index.js";
import {
DISCORD_CHANNEL_TO_WS_EVENT,
DISCORD_VOICE_PCM,
} from "../shared/index.js";
import { createChildLogger } from "../shared/logger/index.js";
import { broadcastBinary, broadcastEvent } from "./broadcast.js";
const logger = createChildLogger("ws.redis-bridge");
/** Channels we subscribe to = all keys in DISCORD_CHANNEL_TO_WS_EVENT */
const SUBSCRIPTION_CHANNELS = Object.keys(DISCORD_CHANNEL_TO_WS_EVENT);
let subscriber: Redis | null = null;
function createSubscriber(): Redis {
return new Redis(config.REDIS_URL, { keyPrefix: "" });
}
function handleSubscriptionMessage(channel: string, message: string): void {
const eventType = DISCORD_CHANNEL_TO_WS_EVENT[channel];
if (!eventType) {
logger.warn({ channel }, "Received message for unmapped Redis channel");
return;
}
let envelope: {
type?: string;
data?: unknown;
timestamp?: number;
source?: string;
};
try {
envelope = JSON.parse(message);
} catch (err) {
logger.error({ channel, err }, "Failed to parse Redis message as JSON");
return;
}
// Unwrap DiscordGatewayEvent envelope — the gateway publishes:
// { type, data: <actual payload>, timestamp, source }
// We only want <actual payload>, not the full envelope.
const data = envelope.data !== undefined ? envelope.data : envelope;
// Voice PCM: decode base64 → binary broadcast instead of JSON
if (channel === DISCORD_VOICE_PCM) {
const pcmPayload = data as { userId?: string; pcm?: string };
if (pcmPayload?.pcm && pcmPayload?.userId) {
try {
const pcmBuffer = Buffer.from(pcmPayload.pcm, "base64");
// Prepend userId as 4-byte FNV-1a hash
const userIdHash = hashUserId(pcmPayload.userId);
const binary = Buffer.alloc(4 + pcmBuffer.length);
binary.writeUInt32LE(userIdHash, 0);
pcmBuffer.copy(binary, 4);
broadcastBinary(binary);
return;
} catch {
// fallback to JSON broadcast on error
}
}
}
logger.debug({ channel, eventType }, "Broadcasting Redis event");
broadcastEvent(eventType, data);
}
/** Simple 32-bit FNV-1a hash for userId → 4-byte identifier */
function hashUserId(userId: string): number {
let hash = 0x811c9dc5;
for (let i = 0; i < userId.length; i++) {
hash ^= userId.charCodeAt(i);
hash = Math.imul(hash, 0x01000193);
}
return hash >>> 0;
}
export async function startRedisBridge(): Promise<void> {
if (!config.REDIS_URL) {
logger.info("Redis not configured, skipping Redis bridge");
return;
}
try {
subscriber = createSubscriber();
subscriber.on("error", (err: Error) => {
logger.error({ err }, "Redis subscriber error");
});
subscriber.on("connect", () => {
logger.info("Redis subscriber connected");
});
subscriber.on("reconnecting", () => {
logger.warn("Redis subscriber reconnecting…");
});
subscriber.on("close", () => {
logger.warn("Redis subscriber connection closed");
});
subscriber.on("message", handleSubscriptionMessage);
await subscriber.ping();
logger.info("Redis ping OK");
const channels = SUBSCRIPTION_CHANNELS;
await subscriber.subscribe(...channels);
logger.info({ channels }, "Subscribed to Redis channels");
logger.info("Redis bridge started");
} catch (err) {
logger.error({ err }, "Failed to start Redis bridge");
throw err;
}
}
export async function stopRedisBridge(): Promise<void> {
if (!subscriber) {
logger.debug("Redis bridge not running, nothing to stop");
return;
}
try {
await subscriber.quit();
logger.info("Redis bridge stopped");
} catch (err) {
logger.error({ err }, "Error stopping Redis bridge");
// Force-close on error
subscriber.disconnect();
} finally {
subscriber = null;
}
}