tencentcloud-sdk-nodejs-intl-en
Version:
168 lines (150 loc) • 4.62 kB
JavaScript
const { EventEmitter } = require('events')
const { createInterface } = require('readline')
/**
* @interface EventSourceMessage
* @description Represents a type EventSourceMessage.
* @property {string} id - The event ID to set the EventSource object's last event ID value.
* @property {string} event - A string identifying the type of event described.
* @property {string} data - The event data.
* @property {string} retry - The reconnection interval (in milliseconds) to wait before retrying the connection (optional).
*/
class SSEEventEmitter extends EventEmitter {}
class SSEResponseModel {
/**
* Initialize the SSE response model.
* @param {NodeJS.ReadableStream} stream
*/
constructor(stream) {
this.stream = stream
this.readline = createInterface({
input: stream,
crlfDelay: Infinity
})
this.eventSource = new SSEEventEmitter()
this.init()
}
/**
* @inner
*/
init() {
const { stream, readline, eventSource } = this
let lines = []
readline.on("line", (line) => {
if (line) {
lines.push(line)
return
}
eventSource.emit("message", this.parseSSEMessage(lines.splice(0)));
})
readline.on("close", () => {
if (lines.length > 0) {
eventSource.emit("message", this.parseSSEMessage(lines.splice(0)));
}
})
stream.on("close", () => {
eventSource.emit("close")
})
stream.on("error", (err) => {
eventSource.emit("error", err)
})
}
/**
* @inner
* @param {string[]} lines
*/
parseSSEMessage(lines) {
/**
* @type {EventSourceMessage}
*/
const message = {
data: "",
event: "",
id: "",
retry: undefined,
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
// line is of format "<field>:<value>" or "<field>: <value>"
const colonIndex = line.indexOf(":")
if (colonIndex <= 0) continue // exclude comments and lines with no values
const field = line.slice(0, colonIndex)
const value = line.slice(colonIndex + (line[colonIndex + 1] === " " ? 2 : 1))
switch (field) {
case "data":
message.data = message.data ? message.data + "\n" + value : value
break
case "event":
message.event = value
break
case "id":
message.id = value
break
case "retry":
const retry = parseInt(value, 10)
if (!isNaN(retry)) {
// per spec, ignore non-integers
message.retry = retry
}
break
}
}
return message
}
/**
* add event listener.
* @param {"message"} event - The event type.
* @param {(message: EventSourceMessage) => void} listener - The event listener for "message" event.
* @returns {this}
* @param {"close"} event - The event type.
* @param {() => void} listener - The event listener for "close" event.
* @returns {this}
* @param {"error"} event - The event type.
* @param {(err: Error) => void} listener - The event listener for "error" event.
* @returns {this}
* @param {string} event - The event type.
* @param {any} listener - The event listener for other events.
* @returns {this}
*/
on(event, listener) {
this.eventSource.on(event, listener)
return this
}
/**
* @param {"message"} event - The event type.
* @param {(message: EventSourceMessage) => void} listener - The event listener for "message" event.
* @returns {this}
* @param {"close"} event - The event type.
* @param {() => void} listener - The event listener for "close" event.
* @returns {this}
* @param {"error"} event - The event type.
* @param {(err: Error) => void} listener - The event listener for "error" event.
* @returns {this}
* @param {string} event - The event type.
* @param {any} listener - The event listener for other events.
* @returns {this}
*/
removeListener(event, listener) {
this.eventSource.removeListener(event, listener)
return this
}
/**
* @function
* @name Symbol.asyncIterator
* @description Async iterator function for Symbol.asyncIterator.
* @returns {AsyncIterableIterator<EventSourceMessage>}
*/
async *[Symbol.asyncIterator]() {
let lines = []
for await (const line of this.readline) {
if (line) {
lines.push(line)
continue
}
yield this.parseSSEMessage(lines.splice(0))
}
if (lines.length > 0) {
yield this.parseSSEMessage(lines.splice(0))
}
}
}
module.exports = SSEResponseModel;