2023-07-17 00:32:32 +02:00
|
|
|
import { EventEmitter } from "events";
|
2024-03-28 17:26:52 +01:00
|
|
|
import type Entity from "./entity";
|
2023-07-06 00:36:26 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parser
|
|
|
|
* Parse response data in streaming.
|
|
|
|
**/
|
|
|
|
export class Parser extends EventEmitter {
|
2023-07-17 00:32:32 +02:00
|
|
|
private message: string;
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.message = "";
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
public parse(chunk: string) {
|
|
|
|
// skip heartbeats
|
|
|
|
if (chunk === ":thump\n") {
|
|
|
|
this.emit("heartbeat", {});
|
|
|
|
return;
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
this.message += chunk;
|
|
|
|
chunk = this.message;
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
const size: number = chunk.length;
|
|
|
|
let start = 0;
|
|
|
|
let offset = 0;
|
|
|
|
let curr: string | undefined;
|
|
|
|
let next: string | undefined;
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
while (offset < size) {
|
|
|
|
curr = chunk[offset];
|
|
|
|
next = chunk[offset + 1];
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
if (curr === "\n" && next === "\n") {
|
|
|
|
const piece: string = chunk.slice(start, offset);
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
offset += 2;
|
|
|
|
start = offset;
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
if (!piece.length) continue; // empty object
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
const root: Array<string> = piece.split("\n");
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
// should never happen, as long as mastodon doesn't change API messages
|
|
|
|
if (root.length !== 2) continue;
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
// remove event and data markers
|
2024-04-07 12:10:26 +02:00
|
|
|
const event: string = root[0].substring(7);
|
|
|
|
const data: string = root[1].substring(6);
|
2023-07-06 00:36:26 +02:00
|
|
|
|
2023-07-17 00:32:32 +02:00
|
|
|
let jsonObj = {};
|
|
|
|
try {
|
|
|
|
jsonObj = JSON.parse(data);
|
|
|
|
} catch (err) {
|
|
|
|
// delete event does not have json object
|
|
|
|
if (event !== "delete") {
|
|
|
|
this.emit(
|
|
|
|
"error",
|
|
|
|
new Error(
|
|
|
|
`Error parsing API reply: '${piece}', error message: '${err}'`,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch (event) {
|
|
|
|
case "update":
|
|
|
|
this.emit("update", jsonObj as Entity.Status);
|
|
|
|
break;
|
|
|
|
case "notification":
|
|
|
|
this.emit("notification", jsonObj as Entity.Notification);
|
|
|
|
break;
|
|
|
|
case "conversation":
|
|
|
|
this.emit("conversation", jsonObj as Entity.Conversation);
|
|
|
|
break;
|
|
|
|
case "delete":
|
|
|
|
// When delete, data is an ID of the deleted status
|
|
|
|
this.emit("delete", data);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this.emit(
|
|
|
|
"error",
|
|
|
|
new Error(`Unknown event has received: ${event}`),
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
this.message = chunk.slice(start, size);
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|