UNPKG

@cutls/megalodon

Version:

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

400 lines (399 loc) 15.1 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (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 || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; var isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); var events_1 = require("events"); var api_client_js_1 = __importDefault(require("./api_client.js")); var default_js_1 = require("../default.js"); var WebSocket = (function (_super) { __extends(WebSocket, _super); function WebSocket(url, channel, accessToken, listId, userAgent) { var _this = _super.call(this) || this; _this.listId = null; _this.channelSubscriptions = []; _this._client = null; _this.url = url; _this.parser = new Parser(); _this.channel = channel; _this.headers = { 'User-Agent': userAgent }; if (listId === undefined) { _this.listId = null; } else { _this.listId = listId; } _this._accessToken = accessToken; _this._reconnectInterval = 10000; _this._reconnectMaxAttempts = Infinity; _this._reconnectCurrentAttempts = 0; _this._connectionClosed = false; _this._channelID = 'user'; return _this; } WebSocket.prototype.start = function () { this._connectionClosed = false; this._resetRetryParams(); this._startWebSocketConnection(); }; WebSocket.prototype.baseUrlToHost = function (baseUrl) { var m = baseUrl.match(/(https|wss):\/\/([^/]+)/); if (!m) return ''; return m[2] || ''; }; WebSocket.prototype._startWebSocketConnection = function () { this._resetConnection(); this._setupParser(); this._client = this._connect(); this._bindSocket(this._client); }; WebSocket.prototype.stop = function () { this._connectionClosed = true; this._resetConnection(); this._resetRetryParams(); }; WebSocket.prototype.subscribe = function (channelID, stream, add) { var _a; (_a = this._client) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ type: 'connect', body: { channel: stream, id: channelID, params: add } })); this.channelSubscriptions.push(__assign({ type: 'subscribe', stream: channelID, channel: stream }, add)); }; WebSocket.prototype.unsubscribe = function (channelID) { var _a; (_a = this._client) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ type: 'disconnect', body: { id: channelID } })); this.channelSubscriptions = this.channelSubscriptions.filter(function (ch) { return !(ch.type === 'subscribe' && ch.stream === channelID); }); }; WebSocket.prototype._resetConnection = function () { if (this._client) { this._client.close(1000); this._clearBinding(); this._client = null; this.channelSubscriptions = []; } if (this.parser) { this.parser.removeAllListeners(); } }; WebSocket.prototype._resetRetryParams = function () { this._reconnectCurrentAttempts = 0; }; WebSocket.prototype._connect = function () { var requestURL = "".concat(this.url, "?i=").concat(this._accessToken); if ((0, default_js_1.isBrowser)()) { var cli = new isomorphic_ws_1.default(requestURL); return cli; } else { var options = { headers: this.headers }; var cli = new isomorphic_ws_1.default(requestURL, options); return cli; } }; WebSocket.prototype._channel = function () { if (!this._client) { return; } switch (this.channel) { case 'conversation': this._client.send(JSON.stringify({ type: 'connect', body: { channel: 'main', id: this._channelID } })); break; case 'user': this._client.send(JSON.stringify({ type: 'connect', body: { channel: 'main', id: this._channelID } })); this._client.send(JSON.stringify({ type: 'connect', body: { channel: 'homeTimeline', id: this._channelID } })); break; case 'list': this._client.send(JSON.stringify({ type: 'connect', body: { channel: 'userList', id: this._channelID, params: { listId: this.listId } } })); break; default: this._client.send(JSON.stringify({ type: 'connect', body: { channel: this.channel, id: this._channelID } })); break; } }; WebSocket.prototype._reconnect = function () { var _this = this; setTimeout(function () { if (_this._client && _this._client.readyState === isomorphic_ws_1.default.CONNECTING) { return; } if (_this._reconnectCurrentAttempts < _this._reconnectMaxAttempts) { _this._reconnectCurrentAttempts++; _this._clearBinding(); if (_this._client) { if ((0, default_js_1.isBrowser)()) { _this._client.close(); } else { _this._client.terminate(); } } console.log('Reconnecting'); _this._client = _this._connect(); _this._bindSocket(_this._client); } }, this._reconnectInterval); }; WebSocket.prototype.reconnect = function () { this._reconnect(); }; WebSocket.prototype._clearBinding = function () { if (this._client && !(0, default_js_1.isBrowser)()) { this._client.removeAllListeners('close'); this._client.removeAllListeners('pong'); this._client.removeAllListeners('open'); this._client.removeAllListeners('message'); this._client.removeAllListeners('error'); } }; WebSocket.prototype._bindSocket = function (client) { var _this = this; client.onclose = function (_a) { var code = _a.code; if (code === 1000) { _this.emit('close', {}); } else { console.log("Closed connection with ".concat(code)); if (!_this._connectionClosed) { _this._reconnect(); } } }; client.onopen = function (_event) { var e_1, _a; var _b; var chsRaw = structuredClone(_this.channelSubscriptions); var chs = __spreadArray([], __read(new Set(chsRaw.map(function (e) { return JSON.stringify(e); }))), false).map(function (e) { return JSON.parse(e); }); _this.channelSubscriptions = []; if (!chs.length) _this.emit('connect', {}); _this._channel(); if (!(0, default_js_1.isBrowser)()) { setTimeout(function () { client.ping(''); }, 10000); } try { for (var _c = __values(_this.channelSubscriptions), _d = _c.next(); !_d.done; _d = _c.next()) { var ch = _d.value; var add = __assign({}, ch); delete add.type; delete add.stream; delete add.channel; (_b = _this._client) === null || _b === void 0 ? void 0 : _b.send(JSON.stringify({ type: 'connect', body: { channel: ch.channel, id: ch.stream, params: add } })); _this.channelSubscriptions.push(ch); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_1) throw e_1.error; } } if (chs.length) { _this.emit('connect', {}); } }; client.onmessage = function (event) { _this.parser.parse(event.data, false, _this._channelID); }; client.onerror = function (event) { _this.emit('error', event.error); }; }; WebSocket.prototype._setupParser = function () { var _this = this; this.parser.on('update', function (note, ch) { var _a; if (note.id) (_a = _this._client) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ type: 's', body: { id: note.id } })); _this.emit('update', api_client_js_1.default.Converter.note(note, _this.baseUrlToHost(_this.url)), ch); }); this.parser.on('notification', function (notification, ch) { _this.emit('notification', api_client_js_1.default.Converter.notification(notification, _this.baseUrlToHost(_this.url)), ch); }); this.parser.on('conversation', function (note, ch) { _this.emit('conversation', api_client_js_1.default.Converter.noteToConversation(note, _this.baseUrlToHost(_this.url)), ch); }); this.parser.on('delete', function (noteId, ch) { _this.emit('delete', noteId, ch); }); this.parser.on('error', function (err) { _this.emit('parser-error', err); }); }; return WebSocket; }(events_1.EventEmitter)); exports.default = WebSocket; var Parser = (function (_super) { __extends(Parser, _super); function Parser() { return _super !== null && _super.apply(this, arguments) || this; } Parser.prototype.parse = function (data, isBinary, _channelID) { var message = isBinary ? data : data.toString(); if (typeof message !== 'string') { this.emit('heartbeat', {}); return; } if (message === '') { this.emit('heartbeat', {}); return; } var obj; var body; try { obj = JSON.parse(message); if (obj.type === 'noteUpdated') { if (obj.body.type !== 'deleted') return; var noteId = obj.body.id; this.emit('delete', noteId); } if (obj.type !== 'channel') { return; } if (!obj.body) { return; } body = obj.body; } catch (err) { this.emit('error', new Error("Error parsing websocket reply: ".concat(message, ", error message: ").concat(err))); return; } switch (body.type) { case 'note': this.emit('update', body.body, [body.id]); break; case 'notification': this.emit('notification', body.body, [body.id]); break; case 'mention': { var note = body.body; if (note.visibility === 'specified') { this.emit('conversation', note, [body.id]); } break; } case 'renote': case 'followed': case 'follow': case 'unfollow': case 'receiveFollowRequest': case 'meUpdated': case 'readAllNotifications': case 'readAllUnreadSpecifiedNotes': case 'readAllAntennas': case 'readAllUnreadMentions': case 'unreadNotification': break; default: this.emit('error', new Error("Unknown event has received: ".concat(JSON.stringify(body)))); break; } }; return Parser; }(events_1.EventEmitter)); exports.Parser = Parser;