jinaga
Version:
Data management for web and mobile applications.
67 lines (59 loc) • 2.27 kB
text/typescript
import { ControlKeyword, ControlFrame, ProtocolMessageRouterCallbacks, AuthorizationContext } from "./types";
import { ControlFrameHandler } from "./control-frame-handler";
const CONTROL_KEYWORDS: ReadonlySet<string> = new Set(["BOOK", "ERR", "SUB", "UNSUB", "ACK"]);
export class WebSocketMessageRouter {
private buffer: string = "";
private authorizationContext: AuthorizationContext | undefined;
constructor(
private readonly callbacks: ProtocolMessageRouterCallbacks,
private readonly controlHandler: ControlFrameHandler,
authorizationContext?: AuthorizationContext
) {
this.authorizationContext = authorizationContext;
}
setAuthorizationContext(ctx: AuthorizationContext | undefined) {
this.authorizationContext = ctx;
}
pushChunk(chunk: string): void {
this.buffer += typeof chunk === "string" ? chunk : String(chunk);
this.flush();
}
private flush(): void {
const parts = this.buffer.split(/\r?\n/);
this.buffer = parts.pop() ?? "";
for (let i = 0; i < parts.length; i++) {
const line = parts[i];
if (CONTROL_KEYWORDS.has(line)) {
const keyword = line as ControlKeyword;
const payload: string[] = [];
// Collect until blank terminator; payload lines are JSON-encoded strings
i++;
while (i < parts.length) {
const next = parts[i];
if (next === "") {
break;
}
payload.push(next);
i++;
}
// If we ran out without a blank line terminator, stash partial back into buffer
if (i >= parts.length || parts[i] !== "") {
// Reconstruct the partial frame back into buffer including keyword and payload
const remainder = [keyword, ...payload].join("\n");
this.buffer = remainder + (this.buffer ? "\n" + this.buffer : "");
return;
}
// Process complete control frame
const frame: ControlFrame = { keyword, payload };
try {
this.controlHandler.handle(frame);
} catch (err) {
// Swallow handler errors; protocol continues
}
continue;
}
// Not a control keyword; forward to graph line consumer
this.callbacks.onGraphLine(line);
}
}
}