Skip to content

Commit c895c97

Browse files
committed
fix(tlon): parse split SSE delimiters at limit
1 parent e2f0dcc commit c895c97

2 files changed

Lines changed: 18 additions & 10 deletions

File tree

extensions/tlon/src/urbit/sse-client.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,14 +301,15 @@ describe("UrbitSSEClient", () => {
301301
expect(handler).toHaveBeenCalledWith({ text: "😀" });
302302
});
303303

304-
it("counts a boundary-sized surrogate pair split across string chunks once", async () => {
304+
it("accepts a boundary-sized event with split surrogate pair and delimiter", async () => {
305305
const cap = 16 * 1024 * 1024;
306306
const prefix = 'id: 1\ndata: {"json":{"text":"';
307307
const suffix = '"}}';
308308
const padLen = cap - Buffer.byteLength(prefix + "😀" + suffix, "utf8");
309309
const stream = Readable.from([
310310
prefix + "a".repeat(padLen) + "\uD83D",
311-
`\uDE00${suffix}\n\n`,
311+
`\uDE00${suffix}\n`,
312+
"\n",
312313
]);
313314
const client = new UrbitSSEClient("https://example.com", "urbauth-~zod=123", {
314315
autoReconnect: false,

extensions/tlon/src/urbit/sse-client.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export class UrbitSSEClient {
240240
const decoder = new TextDecoder();
241241
let buffer = "";
242242
let bufferBytes = 0;
243+
let pendingDelimiterNewline = false;
243244

244245
const appendPending = (text: string) => {
245246
const previousCodeUnit = buffer.charCodeAt(buffer.length - 1);
@@ -261,19 +262,25 @@ export class UrbitSSEClient {
261262
};
262263
const consumeText = (text: string) => {
263264
let offset = 0;
264-
while (offset < text.length) {
265-
// The blank-line delimiter can straddle chunks. Drain it before
266-
// counting later events against the retained pending-event budget.
267-
if (buffer.endsWith("\n") && text[offset] === "\n") {
268-
this.processEvent(buffer.slice(0, -1));
265+
if (pendingDelimiterNewline && text.length > 0) {
266+
pendingDelimiterNewline = false;
267+
if (text.startsWith("\n")) {
268+
this.processEvent(buffer);
269269
buffer = "";
270270
bufferBytes = 0;
271-
offset += 1;
272-
continue;
271+
offset = 1;
272+
} else {
273+
// A trailing newline stays outside the budget until the next byte
274+
// distinguishes an event delimiter from retained event data.
275+
appendPending("\n");
273276
}
277+
}
278+
while (offset < text.length) {
274279
const eventEnd = text.indexOf("\n\n", offset);
275280
if (eventEnd === -1) {
276-
appendPending(text.slice(offset));
281+
const endsWithNewline = text.endsWith("\n");
282+
appendPending(text.slice(offset, endsWithNewline ? -1 : undefined));
283+
pendingDelimiterNewline = endsWithNewline;
277284
return;
278285
}
279286
appendPending(text.slice(offset, eventEnd));

0 commit comments

Comments
 (0)