UNPKG

@cutls/megalodon

Version:

Mastodon, Pleroma, Misskey API client for node.js and browser

290 lines (289 loc) 8.12 kB
var __extends = (this && this.__extends) || (() => { var extendStatics = (d, b) => { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && ((d, b) => { d.__proto__ = b })) || ((d, b) => { for (var p in b) if (Object.hasOwn(b, p)) d[p] = b[p] }) return extendStatics(d, b) } return (d, b) => { if (typeof b !== 'function' && b !== null) throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null') extendStatics(d, b) function __() { this.constructor = d } d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __()) } })() var __assign = (this && this.__assign) || function () { __assign = Object.assign || ((t) => { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i] for (var p in s) if (Object.hasOwn(s, p)) t[p] = s[p] } return t }) return __assign.apply(this, arguments) } import WS from 'isomorphic-ws' import dayjs from 'dayjs' import { EventEmitter } from 'events' import MastodonAPI from './api_client' import { UnknownNotificationTypeError } from '../notification' import { isBrowser } from '../default' var Streaming = ((_super) => { __extends(Streaming, _super) function Streaming(url, stream, params, accessToken, userAgent) { var _this = _super.call(this) || this _this._heartbeatInterval = 60000 _this._pongWaiting = false _this.url = url _this.stream = stream if (params === undefined) { _this.params = null } else { _this.params = params } _this.parser = new Parser() _this.headers = { 'User-Agent': userAgent } _this._accessToken = accessToken _this._reconnectInterval = 10000 _this._reconnectMaxAttempts = Infinity _this._reconnectCurrentAttempts = 0 _this._connectionClosed = false _this._client = null _this._pongReceivedTimestamp = dayjs() return _this } Streaming.prototype.start = function () { this._connectionClosed = false this._resetRetryParams() this._startWebSocketConnection() } Streaming.prototype._startWebSocketConnection = function () { this._resetConnection() this._setupParser() this._client = this._connect(this.url, this.stream, this.params, this._accessToken, this.headers) this._bindSocket(this._client) } Streaming.prototype.stop = function () { this._connectionClosed = true this._resetConnection() this._resetRetryParams() } Streaming.prototype.subscribe = function (name, _stream, add) { var _a ;(_a = this._client) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(__assign({ type: 'subscribe', stream: name }, add))) } Streaming.prototype._resetConnection = function () { if (this._client) { this._client.close(1000) this._clearBinding() this._client = null } if (this.parser) { this.parser.removeAllListeners() } } Streaming.prototype._resetRetryParams = function () { this._reconnectCurrentAttempts = 0 } Streaming.prototype._reconnect = function () { setTimeout(() => { if (this._client && this._client.readyState === WS.CONNECTING) { return } if (this._reconnectCurrentAttempts < this._reconnectMaxAttempts) { this._reconnectCurrentAttempts++ this._clearBinding() if (this._client) { if (isBrowser()) { this._client.close() } else { this._client.terminate() } } console.log('Reconnecting') this._client = this._connect(this.url, this.stream, this.params, this._accessToken, this.headers) this._bindSocket(this._client) } }, this._reconnectInterval) } Streaming.prototype._connect = (url, stream, params, accessToken, headers) => { var parameter = stream ? ['stream='.concat(stream)] : [] if (params) { parameter.push(params) } if (accessToken !== null) { parameter.push('access_token='.concat(accessToken)) headers.Authorization = 'Bearer '.concat(accessToken) } var requestURL = ''.concat(url, '?').concat(parameter.join('&')) if (isBrowser()) { var cli = new WS(requestURL) return cli } else { var options = { headers: headers } var cli = new WS(requestURL, options) return cli } } Streaming.prototype._clearBinding = function () { if (this._client && !isBrowser()) { this._client.removeAllListeners('close') this._client.removeAllListeners('pong') this._client.removeAllListeners('open') this._client.removeAllListeners('message') this._client.removeAllListeners('error') } } Streaming.prototype._bindSocket = function (client) { client.onclose = (event) => { if (event.code === 1000) { this.emit('close', {}) } else { console.log('Closed connection with '.concat(event.code)) if (!this._connectionClosed) { this._reconnect() } } } client.onopen = (_event) => { this.emit('connect', {}) if (!isBrowser()) { setTimeout(() => { client.ping('') }, 10000) } } client.onmessage = (event) => { this.parser.parse(event) } client.onerror = (event) => { this.emit('error', event.error) } if (!isBrowser()) { client.on('pong', () => { this._pongWaiting = false this.emit('pong', {}) this._pongReceivedTimestamp = dayjs() setTimeout(() => this._checkAlive(this._pongReceivedTimestamp), this._heartbeatInterval) }) } } Streaming.prototype._setupParser = function () { this.parser.on('update', (status, ch) => { this.emit('update', MastodonAPI.Converter.status(status), ch) }) this.parser.on('notification', (notification, ch) => { var n = MastodonAPI.Converter.notification(notification) if (n instanceof UnknownNotificationTypeError) { console.warn('Unknown notification event has received: '.concat(notification)) } else { this.emit('notification', n, ch) } }) this.parser.on('delete', (id, ch) => { this.emit('delete', id, ch) }) this.parser.on('conversation', (conversation, ch) => { this.emit('conversation', MastodonAPI.Converter.conversation(conversation), ch) }) this.parser.on('status_update', (status, ch) => { this.emit('status_update', MastodonAPI.Converter.status(status), ch) }) this.parser.on('error', (err) => { this.emit('parser-error', err) }) this.parser.on('heartbeat', (_) => { this.emit('heartbeat', 'heartbeat') }) } Streaming.prototype._checkAlive = function (timestamp) { var now = dayjs() if (now.diff(timestamp) > this._heartbeatInterval - 1000 && !this._connectionClosed) { if (this._client && this._client.readyState !== WS.CONNECTING) { this._pongWaiting = true this._client.ping('') setTimeout(() => { if (this._pongWaiting) { this._pongWaiting = false this._reconnect() } }, 10000) } } } return Streaming })(EventEmitter) export default Streaming var Parser = ((_super) => { __extends(Parser, _super) function Parser() { return (_super !== null && _super.apply(this, arguments)) || this } Parser.prototype.parse = function (ev) { var data = ev.data var message = data.toString() if (typeof message !== 'string') { this.emit('heartbeat', {}) return } if (message === '') { this.emit('heartbeat', {}) return } var ch = [] var event = '' var payload = '' var mes = {} try { var obj = JSON.parse(message) event = obj.event payload = obj.payload ch = obj.stream || [] mes = JSON.parse(payload) } catch (err) { if (event !== 'delete') { this.emit('error', new Error('Error parsing websocket reply: '.concat(message, ', error message: ').concat(err))) return } } switch (event) { case 'update': this.emit('update', mes, ch) break case 'notification': this.emit('notification', mes, ch) break case 'conversation': this.emit('conversation', mes, ch) break case 'delete': this.emit('delete', payload) break case 'status.update': this.emit('status_update', mes, ch) break default: this.emit('error', new Error('Unknown event has received: '.concat(message))) } } return Parser })(EventEmitter) export { Parser }