'use strict'; var tsCustomError = require('ts-custom-error'); var WebSocket = require('isomorphic-ws'); var eventsToAsync = require('events-to-async'); var changeCase = require('change-case'); class MastoUnexpectedError extends tsCustomError.CustomError { } class MastoDeserializeError extends tsCustomError.CustomError { constructor(message, contentType, data, options) { super(message, options); this.contentType = contentType; this.data = data; } } class MastoHttpError extends tsCustomError.CustomError { constructor(props, errorOptions) { super(props.message, errorOptions); this.statusCode = props.statusCode; this.message = props.message; this.description = props.description; this.additionalProperties = props.additionalProperties; this.details = props.details; } } class MastoInvalidArgumentError extends tsCustomError.CustomError { } class MastoTimeoutError extends tsCustomError.CustomError { } class MastoWebSocketError extends tsCustomError.CustomError { constructor(message, options) { super(message, options); this.message = message; } } /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __values(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."); } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } function __asyncGenerator(thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; const sleep = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms)); const isRecord = (x) => typeof x === "object" && x !== null && x.constructor.name === "Object"; function noop() { // } class ExponentialBackoffError extends tsCustomError.CustomError { constructor(attempts, options) { super(`Maximum number of attempts reached: ${attempts}`, options); } } // https://en.wikipedia.org/wiki/Exponential_backoff class ExponentialBackoff { constructor(props = {}) { this.props = props; this.attempts = 0; } sleep() { return __awaiter(this, void 0, void 0, function* () { if (this.attempts >= this.maxAttempts) { throw new ExponentialBackoffError(this.attempts); } yield sleep(this.timeout); this.attempts++; }); } clear() { this.attempts = 0; } get factor() { var _a; return (_a = this.props.factor) !== null && _a !== void 0 ? _a : 1000; } get base() { var _a; return (_a = this.props.base) !== null && _a !== void 0 ? _a : 2; } get maxAttempts() { var _a; return (_a = this.props.maxAttempts) !== null && _a !== void 0 ? _a : Number.POSITIVE_INFINITY; } get timeout() { return this.factor * Math.pow(this.base, this.attempts); } } // https://github.com/tc39/proposal-promise-with-resolvers const createPromiseWithResolvers = () => { let resolve; let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; const parseLinkHeader = (linkHeader) => { const links = new Map(); for (const link of linkHeader.split(",")) { const match = link.match(/<([^>]+)>;\s*rel="([^"]+)"/); if (match) { links.set(match[2], match[1]); } } return links; }; class PaginatorHttp { constructor(http, nextPath, nextParams, meta, direction = "next") { this.http = http; this.nextPath = nextPath; this.nextParams = nextParams; this.meta = meta; this.direction = direction; } next() { return __awaiter(this, void 0, void 0, function* () { if (!this.nextPath) { return { done: true, value: undefined }; } const response = yield this.http.request(Object.assign({ method: "GET", path: this.nextPath, search: this.nextParams }, this.meta)); const nextUrl = this.getLink(response.headers.get("link")); this.nextPath = nextUrl === null || nextUrl === void 0 ? void 0 : nextUrl.pathname; this.nextParams = nextUrl === null || nextUrl === void 0 ? void 0 : nextUrl.search.replace(/^\?/, ""); const data = (yield response.data); return { done: false, value: data, }; }); } return(value) { return __awaiter(this, void 0, void 0, function* () { this.clear(); return { done: true, value: yield value, }; }); } throw(e) { return __awaiter(this, void 0, void 0, function* () { this.clear(); throw e; }); } then(onfulfilled = Promise.resolve.bind(Promise), onrejected = Promise.reject.bind(Promise)) { // we assume the first item won't be undefined // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.next().then((value) => onfulfilled(value.value), onrejected); } values() { return this[Symbol.asyncIterator](); } getDirection() { return this.direction; } setDirection(direction) { return new PaginatorHttp(this.http, this.nextPath, this.nextParams, this.meta, direction); } [Symbol.asyncIterator]() { // eslint-disable-next-line @typescript-eslint/no-explicit-any return this; } getLink(value) { if (!value) { return; } const parsed = parseLinkHeader(value).get(this.direction); if (!parsed) { return; } return new URL(parsed); } clear() { this.nextPath = undefined; this.nextParams = undefined; } clone() { return new PaginatorHttp(this.http, this.nextPath, this.nextParams, this.meta, this.direction); } } class HttpActionDispatcher { constructor(http, hook) { this.http = http; this.hook = hook; } dispatch(action) { if (this.hook) { action = this.hook.beforeDispatch(action); } let result = this.hook.dispatch(action); if (result !== false) { return result; } switch (action.type) { case "fetch": { result = this.http.get(action.path, action.data, action.meta); break; } case "create": { result = this.http.post(action.path, action.data, action.meta); break; } case "update": { result = this.http.put(action.path, action.data, action.meta); break; } case "remove": { result = this.http.delete(action.path, action.data, action.meta); break; } case "list": { result = new PaginatorHttp(this.http, action.path, action.data); break; } } /* eslint-disable unicorn/prefer-ternary, prettier/prettier */ if (result instanceof Promise) { return result.then((result) => { var _a; return (_a = this.hook) === null || _a === void 0 ? void 0 : _a.afterDispatch(action, result); }); } else { return this.hook.afterDispatch(action, result); } /* eslint-enable unicorn/prefer-ternary, prettier/prettier */ } } function waitForOpen(ws) { if (ws.readyState === WebSocket.OPEN) { return Promise.resolve(); } return new Promise((resolve, reject) => { const handleError = (error) => { reject(error); }; const handleClose = () => { reject(new Error("WebSocket closed")); }; const handleOpen = () => { resolve(); }; ws.addEventListener("error", handleError, { once: true }); ws.addEventListener("close", handleClose, { once: true }); ws.addEventListener("open", handleOpen, { once: true }); }); } function waitForClose(ws) { if (ws.readyState === WebSocket.CLOSED) { return Promise.resolve(); } return new Promise((resolve) => { const handleClose = () => { resolve(); }; ws.addEventListener("error", handleClose, { once: true }); ws.addEventListener("close", handleClose, { once: true }); }); } class WebSocketConnectorImpl { constructor(props, logger) { this.props = props; this.logger = logger; this.killed = false; this.queue = []; this.backoff = new ExponentialBackoff({ maxAttempts: this.props.maxAttempts, }); this.spawn(); } acquire() { if (this.killed) { throw new MastoWebSocketError("WebSocket closed"); } if (this.ws) { return Promise.resolve(this.ws); } const promiseWithResolvers = createPromiseWithResolvers(); this.queue.push(promiseWithResolvers); return promiseWithResolvers.promise; } [Symbol.asyncIterator]() { return __asyncGenerator(this, arguments, function* _a() { while (!this.killed) { yield yield __await(yield __await(this.acquire())); } }); } kill() { var _a; this.killed = true; (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close(); this.backoff.clear(); for (const { reject } of this.queue) { reject(new MastoWebSocketError("WebSocket closed")); } this.queue = []; } spawn() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; while (!this.killed) { try { yield this.backoff.sleep(); } catch (_f) { break; } try { (_a = this.logger) === null || _a === void 0 ? void 0 : _a.log("info", "Connecting to WebSocket..."); { const ctor = ((_b = this.props.implementation) !== null && _b !== void 0 ? _b : WebSocket); const ws = new ctor(...this.props.constructorParameters); yield waitForOpen(ws); this.ws = ws; } (_c = this.logger) === null || _c === void 0 ? void 0 : _c.log("info", "Connected to WebSocket"); for (const { resolve } of this.queue) { resolve(this.ws); } this.queue = []; yield waitForClose(this.ws); (_d = this.logger) === null || _d === void 0 ? void 0 : _d.log("info", "WebSocket closed"); this.backoff.clear(); } catch (error) { (_e = this.logger) === null || _e === void 0 ? void 0 : _e.log("error", "WebSocket error:", error); } this.ws = undefined; } for (const { reject } of this.queue) { reject(new MastoWebSocketError(`Failed to connect to WebSocket after ${this.props.maxAttempts} attempts`)); } this.queue = []; }); } } function toAsyncIterable(ws) { return __asyncGenerator(this, arguments, function* toAsyncIterable_1() { var _a, e_1, _b, _c; const handleClose = (e) => __awaiter(this, void 0, void 0, function* () { /* istanbul ignore next */ if (!events.return) { throw new MastoUnexpectedError("events.return is undefined"); } yield events.return(e); }); const handleError = (e) => __awaiter(this, void 0, void 0, function* () { /* istanbul ignore next */ if (!events.return) { throw new MastoUnexpectedError("events.return is undefined"); } yield events.return(e); }); const events = eventsToAsync.on((handler) => { ws.addEventListener("message", handler); ws.addEventListener("error", handleError); ws.addEventListener("close", handleClose); return () => { ws.removeEventListener("message", handler); ws.removeEventListener("error", handleError); ws.removeEventListener("close", handleClose); }; }); try { for (var _d = true, events_1 = __asyncValues(events), events_1_1; events_1_1 = yield __await(events_1.next()), _a = events_1_1.done, !_a; _d = true) { _c = events_1_1.value; _d = false; const [event] = _c; yield yield __await(event); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = events_1.return)) yield __await(_b.call(events_1)); } finally { if (e_1) throw e_1.error; } } }); } class WebSocketSubscription { constructor(connector, counter, serializer, stream, logger, params) { this.connector = connector; this.counter = counter; this.serializer = serializer; this.stream = stream; this.logger = logger; this.params = params; } values() { return __asyncGenerator(this, arguments, function* values_1() { var _a, e_1, _b, _c, _d, e_2, _e, _f; var _g, _h, _j; try { (_g = this.logger) === null || _g === void 0 ? void 0 : _g.log("info", "Subscribing to stream", this.stream); try { for (var _k = true, _l = __asyncValues(this.connector), _m; _m = yield __await(_l.next()), _a = _m.done, !_a; _k = true) { _c = _m.value; _k = false; this.connection = _c; const data = this.serializer.serialize("json", Object.assign({ type: "subscribe", stream: this.stream }, this.params)); (_h = this.logger) === null || _h === void 0 ? void 0 : _h.log("debug", "↑ WEBSOCKET", data); this.connection.send(data); this.counter.increment(this.stream, this.params); const messages = toAsyncIterable(this.connection); try { for (var _o = true, messages_1 = (e_2 = void 0, __asyncValues(messages)), messages_1_1; messages_1_1 = yield __await(messages_1.next()), _d = messages_1_1.done, !_d; _o = true) { _f = messages_1_1.value; _o = false; const message = _f; const event = this.parseMessage(message.data); if (!this.test(event)) { continue; } (_j = this.logger) === null || _j === void 0 ? void 0 : _j.log("debug", "↓ WEBSOCKET", event); yield yield __await(event); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_o && !_d && (_e = messages_1.return)) yield __await(_e.call(messages_1)); } finally { if (e_2) throw e_2.error; } } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_k && !_a && (_b = _l.return)) yield __await(_b.call(_l)); } finally { if (e_1) throw e_1.error; } } } finally { this.unsubscribe(); } }); } unsubscribe() { if (!this.connection) { return; } this.counter.decrement(this.stream, this.params); if (this.counter.count(this.stream, this.params) <= 0) { const data = this.serializer.serialize("json", Object.assign({ type: "unsubscribe", stream: this.stream }, this.params)); this.connection.send(data); } this.connection = undefined; } [Symbol.asyncIterator]() { return this.values(); } /** * @experimental This is an experimental API. */ [Symbol.dispose]() { this.unsubscribe(); } test(event) { var _a; // subscribe("hashtag", { tag: "foo" }) -> ["hashtag", "foo"] // subscribe("list", { list: "foo" }) -> ["list", "foo"] const params = (_a = this.params) !== null && _a !== void 0 ? _a : {}; const extra = Object.values(params); const stream = [this.stream, ...extra]; return stream.every((s) => event.stream.includes(s)); } parseMessage(rawEvent) { const data = this.serializer.deserialize("json", rawEvent); if ("error" in data) { throw new MastoUnexpectedError(data.error); } const payload = data.event === "delete" || data.payload == undefined ? data.payload : this.serializer.deserialize("json", data.payload); return { stream: data.stream, event: data.event, payload: payload, }; } } class WebSocketSubscriptionCounterImpl { constructor() { this.counts = new Map(); } count(stream, params) { var _a; const key = this.hash(stream, params); return (_a = this.counts.get(key)) !== null && _a !== void 0 ? _a : 0; } increment(stream, params) { const key = this.hash(stream, params); if (!this.counts.has(key)) { this.counts.set(key, 0); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.counts.set(key, this.counts.get(key) + 1); } decrement(stream, params) { const key = this.hash(stream, params); if (!this.counts.has(key)) { throw new Error("Cannot decrement non-existent count"); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.counts.set(key, this.counts.get(key) - 1); } hash(stream, params) { return JSON.stringify({ stream, params }); } } class WebSocketActionDispatcher { constructor(connector, counter, serializer, logger) { this.connector = connector; this.counter = counter; this.serializer = serializer; this.logger = logger; } dispatch(action) { var _a; if (action.type === "close") { this.connector.kill(); return {}; } if (action.type === "prepare") { return this.connector.acquire(); } if (action.type !== "subscribe") { throw new MastoUnexpectedError(`Unknown action type ${action.type}`); } const data = (_a = action.data) !== null && _a !== void 0 ? _a : {}; const stream = action.path.replace(/^\//, "").replaceAll("/", ":"); return new WebSocketSubscription(this.connector, this.counter, this.serializer, stream, this.logger, Object.assign({}, data)); } [Symbol.dispose]() { this.connector.kill(); } } const createActionProxy = (actionDispatcher, options = {}) => { const { context = [], applicable = false } = options; let target = {}; const handler = { get: get(actionDispatcher, context), }; if (applicable) { target = noop; handler.apply = apply(actionDispatcher, context); } return new Proxy(target, handler); }; const SPECIAL_PROPERTIES = new Set([ "then", "catch", "finally", "inspect", "toString", "valueOf", "toJSON", "constructor", "prototype", "length", "name", "caller", "callee", "arguments", "bind", "apply", "call", ]); const get = (actionDispatcher, context) => (_, property) => { if (typeof property === "string" && SPECIAL_PROPERTIES.has(property)) { return; } if (property === Symbol.dispose) { return actionDispatcher[Symbol.dispose]; } if (typeof property === "symbol") { return; } if (property.startsWith("$")) { return createActionProxy(actionDispatcher, { context: [...context, property], applicable: true, }); } return createActionProxy(actionDispatcher, { context: [...context, changeCase.snakeCase(property)], applicable: true, }); }; const apply = (actionDispatcher, context) => (_1, _2, args) => { const action = context.pop(); /* istanbul ignore next */ if (!action) { throw new Error("No action specified"); } if (action === "$select") { return createActionProxy(actionDispatcher, { context: [...context, ...args], applicable: true, }); } const path = "/" + context.join("/"); const [data, meta] = args; return actionDispatcher.dispatch({ type: action, path, data, meta: meta, }); }; function isHttpActionType(actionType) { return ["fetch", "create", "update", "remove", "list"].includes(actionType); } function toHttpActionType(action) { if (isHttpActionType(action)) { return action; } switch (action) { case "lookup": case "verify_credentials": { return "fetch"; } case "update_credentials": { return "update"; } default: { return "create"; } } } function inferEncoding(action, path) { if ((action === "create" && path === "/api/v1/accounts") || (action === "update" && path === "/api/v1/accounts/update_credentials") || (action === "create" && path === "/api/v1/email") || (action === "create" && path === "/api/v1/featured_tag") || (action === "create" && path === "/api/v1/media") || (action === "create" && path === "/api/v2/media")) { return "multipart-form"; } return "json"; } function waitForMediaAttachment(id, timeout, http) { return __awaiter(this, void 0, void 0, function* () { let media; const signal = AbortSignal.timeout(timeout); while (!media) { if (signal.aborted) { throw new MastoTimeoutError(`Media processing timed out of ${timeout}ms`); } try { yield sleep(1000); const processing = yield http.get(`/api/v1/media/${id}`); if (processing.url) { media = processing; } } catch (error) { if (error instanceof MastoHttpError && error.statusCode === 404) { continue; } throw error; } } return media; }); } class HttpActionDispatcherHookMastodon { constructor(http, mediaTimeout = 1000 * 60) { this.http = http; this.mediaTimeout = mediaTimeout; } beforeDispatch(action) { const type = toHttpActionType(action.type); const path = isHttpActionType(action.type) ? action.path : action.path + "/" + changeCase.snakeCase(action.type); const encoding = inferEncoding(type, path); const meta = Object.assign(Object.assign({}, action.meta), { encoding }); return { type, path, data: action.data, meta }; } dispatch(action) { if (action.type === "update" && action.path === "/api/v1/accounts/update_credentials") { return this.http.patch(action.path, action.data, action.meta); } return false; } afterDispatch(action, result) { var _a; if (action.type === "create" && action.path === "/api/v2/media") { const media = result; if (isRecord(action.data) && ((_a = action.data) === null || _a === void 0 ? void 0 : _a.skipPolling) === true) { return media; } return waitForMediaAttachment(media.id, this.mediaTimeout, this.http); } return result; } } const mergeAbortSignals = (signals) => { const abortController = new AbortController(); for (const signal of signals) { signal.addEventListener("abort", () => abortController.abort(), { once: true, }); } return abortController.signal; }; const mergeHeadersInit = ([head, ...tail]) => { const headers = new Headers(head); for (const entry of tail) { for (const [key, value] of new Headers(entry).entries()) { headers.set(key, value); } } return headers; }; class HttpConfigImpl { constructor(props, serializer) { this.props = props; this.serializer = serializer; } mergeRequestInitWithDefaults(override = {}) { const requestInit = Object.assign({}, this.props.requestInit); // Merge { const { headers, signal } = override, rest = __rest(override, ["headers", "signal"]); Object.assign(requestInit, rest); requestInit.headers = this.mergeHeadersWithDefaults(headers); requestInit.signal = this.mergeAbortSignalWithDefaults(signal); } return requestInit; } resolvePath(path, params) { const url = new URL(path, this.props.url); if (typeof params === "string") { url.search = params; } else if (params) { url.search = this.serializer.serialize("querystring", params); } return url; } mergeHeadersWithDefaults(override = {}) { var _a, _b; const headersInit = mergeHeadersInit([ (_b = (_a = this.props.requestInit) === null || _a === void 0 ? void 0 : _a.headers) !== null && _b !== void 0 ? _b : {}, override, ]); const headers = new Headers(headersInit); if (this.props.accessToken) { headers.set("Authorization", `Bearer ${this.props.accessToken}`); } return new Headers(headers); } mergeAbortSignalWithDefaults(signal) { var _a; const signals = []; if (this.props.timeout) { signals.push(AbortSignal.timeout(this.props.timeout)); } if ((_a = this.props.requestInit) === null || _a === void 0 ? void 0 : _a.signal) { signals.push(this.props.requestInit.signal); } if (signal) { signals.push(signal); } return signals.length === 1 ? signals[0] : mergeAbortSignals(signals); } } class WebSocketConfigImpl { constructor(props, serializer) { this.props = props; this.serializer = serializer; } getProtocols(protocols = []) { if (this.props.useInsecureAccessToken || this.props.accessToken == undefined) { return [...protocols]; } return [this.props.accessToken, ...protocols]; } resolvePath(path, params = {}) { const url = new URL(path, this.props.streamingApiUrl); if (this.props.useInsecureAccessToken) { params.accessToken = this.props.accessToken; } url.search = this.serializer.serialize("querystring", params); return url; } getMaxAttempts() { if (this.props.retry === true || this.props.retry == undefined) { return Number.POSITIVE_INFINITY; } if (this.props.retry === false) { return 1; } return this.props.retry; } } class BaseHttp { get(path, data, meta = {}) { return this.request(Object.assign({ method: "GET", path, search: data }, meta)).then((response) => response.data); } post(path, data, meta = {}) { return this.request(Object.assign({ method: "POST", path, body: data }, meta)).then((response) => response.data); } delete(path, data, meta = {}) { return this.request(Object.assign({ method: "DELETE", path, body: data }, meta)).then((response) => response.data); } put(path, data, meta = {}) { return this.request(Object.assign({ method: "PUT", path, body: data }, meta)).then((response) => response.data); } patch(path, data, meta = {}) { return this.request(Object.assign({ method: "PATCH", path, body: data }, meta)).then((response) => response.data); } } const getEncoding = (headers) => { var _a; const contentType = (_a = headers.get("Content-Type")) === null || _a === void 0 ? void 0 : _a.replace(/\s*;.*$/, ""); if (typeof contentType !== "string") { return; } switch (contentType) { case "application/json": { return "json"; } case "multipart/form-data": { return "multipart-form"; } default: { return; } } }; class HttpNativeImpl extends BaseHttp { constructor(serializer, config, logger) { super(); this.serializer = serializer; this.config = config; this.logger = logger; } request(params) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; const request = this.createRequest(params); try { (_a = this.logger) === null || _a === void 0 ? void 0 : _a.log("info", `↑ ${request.method} ${request.url}`); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.log("debug", "\tbody", { encoding: params.encoding, body: params.body, }); const response = yield fetch(request); if (!response.ok) { throw response; } const text = yield response.text(); const encoding = getEncoding(response.headers); if (!encoding) { throw new MastoUnexpectedError("The server returned data with an unknown encoding."); } const data = this.serializer.deserialize(encoding, text); (_c = this.logger) === null || _c === void 0 ? void 0 : _c.log("info", `↓ ${request.method} ${request.url}`); (_d = this.logger) === null || _d === void 0 ? void 0 : _d.log("debug", "\tbody", text); return { headers: response.headers, data, }; } catch (error) { (_e = this.logger) === null || _e === void 0 ? void 0 : _e.log("debug", `HTTP failed`, error); throw yield this.createError(error); } }); } createRequest(params) { const { method, path, search, encoding = "json", requestInit = {}, } = params; const url = this.config.resolvePath(path, search); const body = this.serializer.serialize(encoding, params.body); const init = this.config.mergeRequestInitWithDefaults(requestInit); const request = new Request(url, Object.assign({ method, body }, init)); if (typeof body === "string" && encoding === "json") { request.headers.set("Content-Type", "application/json"); } return request; } createError(error) { return __awaiter(this, void 0, void 0, function* () { if (error instanceof Response) { const encoding = getEncoding(error.headers); if (!encoding) { throw new MastoUnexpectedError("The server returned data with an unknown encoding. The server may be down."); } const data = this.serializer.deserialize(encoding, yield error.text()); const { error: message, errorDescription, details } = data, additionalProperties = __rest(data, ["error", "errorDescription", "details"]); return new MastoHttpError({ statusCode: error.status, message: message, description: errorDescription, details: details, additionalProperties, }, { cause: error }); } // TODO: Remove this handling when `AbortSignal.any` is shipped // eslint-disable-next-line @typescript-eslint/no-explicit-any if (error != undefined && error.name === "TimeoutError") { return new MastoTimeoutError(`Request timed out`, { cause: error }); } /* istanbul ignore next */ return error; }); } } class LoggerConsoleImpl { constructor(level) { this.level = level; } log(type, message, meta) { if (!this.level.satisfies(type)) { return; } const args = meta == undefined ? [message] : [message, meta]; switch (type) { case "debug": { console.debug(...args); return; } case "info": { console.info(...args); return; } case "warn": { console.warn(...args); return; } case "error": { console.error(...args); return; } } } } /* eslint-disable unicorn/prefer-math-trunc */ const LOG_TYPES = Object.freeze({ DEBUG: 1 << 0, INFO: 1 << 1, WARN: 1 << 2, ERROR: 1 << 3, }); class LogLevel { constructor(level) { this.level = level; } satisfies(type) { switch (type) { case "debug": { return Boolean(this.level & LOG_TYPES.DEBUG); } case "info": { return Boolean(this.level & LOG_TYPES.INFO); } case "warn": { return Boolean(this.level & LOG_TYPES.WARN); } case "error": { return Boolean(this.level & LOG_TYPES.ERROR); } } } static from(type) { switch (type) { case "debug": { return new LogLevel(LOG_TYPES.DEBUG | LOG_TYPES.INFO | LOG_TYPES.WARN | LOG_TYPES.ERROR); } case "info": { return new LogLevel(LOG_TYPES.INFO | LOG_TYPES.WARN | LOG_TYPES.ERROR); } case "warn": { return new LogLevel(LOG_TYPES.WARN | LOG_TYPES.ERROR); } case "error": { return new LogLevel(LOG_TYPES.ERROR); } } } } const createLogger = (type) => { const level = LogLevel.from(type !== null && type !== void 0 ? type : "warn"); return new LoggerConsoleImpl(level); }; const ObjectFlattener = (keyMapper) => { const flatten = (object, parent = "") => { if (Array.isArray(object)) { return object.flatMap((value, i) => flatten(value, parent == "" ? i.toString() : keyMapper.onArray(parent, i))); } if (isRecord(object)) { return Object.entries(object).flatMap(([key, value]) => flatten(value, parent === "" ? key : keyMapper.onObject(parent, key))); } return [[parent, object]]; }; return flatten; }; const flattenForFormData = (object) => { const flatten = ObjectFlattener({ onArray: (parent, index) => `${parent}[${index}]`, onObject: (parent, key) => `${parent}[${key}]`, }); return Object.fromEntries(flatten(object)); }; const flattenForRailsQueryString = (object) => { const flatten = ObjectFlattener({ onArray: (parent) => `${parent}[]`, onObject: (parent, key) => `${parent}[${key}]`, }); return flatten(object) .filter(([, v]) => !!v) .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) .join("&"); }; const _transformKeys = (data, transform) => { if (Array.isArray(data)) { return data.map((value) => _transformKeys(value, transform)); } if (isRecord(data)) { return Object.fromEntries(Object.entries(data).map(([key, value]) => [ transform(key), _transformKeys(value, transform), ])); } return data; }; const transformKeys = (data, transform) => { const f = (key) => { // `PATCH /v1/preferences` uses `:` as a delimiter if (key.includes(":")) return key; // `PATCH /v2/filters` uses _destroy as a special key if (key.startsWith("_")) return key; return transform(key); }; return _transformKeys(data, f); }; class SerializerNativeImpl { serialize(type, rawData) { const data = transformKeys(rawData, changeCase.snakeCase); switch (type) { case "json": { return JSON.stringify(data); } case "multipart-form": { const formData = new FormData(); for (const [key, value] of Object.entries(flattenForFormData(data))) { formData.append(key, value); } return formData; } case "querystring": { return flattenForRailsQueryString(data); } default: { throw new MastoUnexpectedError(`Unknown content type ${type} to serialize.`); } } } deserialize(type, data) { switch (type) { case "json": { try { return transformKeys(JSON.parse(data), changeCase.camelCase); } catch (_a) { throw new MastoDeserializeError(`Malformed JSON ${data} returned from the server.`, type, data); } } default: { throw new MastoDeserializeError(`Unknown content type ${type} returned from the server.`, type, data); } } } } const createRestAPIClient = (props) => { const serializer = new SerializerNativeImpl(); const config = new HttpConfigImpl(props, serializer); const logger = createLogger(props.log); const http = new HttpNativeImpl(serializer, config, logger); const hook = new HttpActionDispatcherHookMastodon(http); const actionDispatcher = new HttpActionDispatcher(http, hook); const actionProxy = createActionProxy(actionDispatcher, { context: ["api"], }); return actionProxy; }; const createOAuthAPIClient = (props) => { const serializer = new SerializerNativeImpl(); const config = new HttpConfigImpl(props, serializer); const logger = createLogger(props.log); const http = new HttpNativeImpl(serializer, config, logger); const hook = new HttpActionDispatcherHookMastodon(http); const actionDispatcher = new HttpActionDispatcher(http, hook); const actionProxy = createActionProxy(actionDispatcher, { context: ["oauth"], }); return actionProxy; }; function createStreamingAPIClient(props) { const serializer = new SerializerNativeImpl(); const config = new WebSocketConfigImpl(props, serializer); const logger = createLogger(props.log); const connector = new WebSocketConnectorImpl({ constructorParameters: [ config.resolvePath("/api/v1/streaming"), config.getProtocols(), ], implementation: props.implementation, maxAttempts: config.getMaxAttempts(), }, logger); const counter = new WebSocketSubscriptionCounterImpl(); const actionDispatcher = new WebSocketActionDispatcher(connector, counter, serializer, logger); const actionProxy = createActionProxy(actionDispatcher); return actionProxy; } var index$7 = /*#__PURE__*/Object.freeze({ __proto__: null }); var index$6 = /*#__PURE__*/Object.freeze({ __proto__: null, Admin: index$7 }); var index$5 = /*#__PURE__*/Object.freeze({ __proto__: null }); var index$4 = /*#__PURE__*/Object.freeze({ __proto__: null }); var index$3 = /*#__PURE__*/Object.freeze({ __proto__: null }); var index$2 = /*#__PURE__*/Object.freeze({ __proto__: null, v1: index$4, v2: index$3 }); var index$1 = /*#__PURE__*/Object.freeze({ __proto__: null }); var index = /*#__PURE__*/Object.freeze({ __proto__: null }); var mastodon = /*#__PURE__*/Object.freeze({ __proto__: null, oauth: index, rest: index$2, streaming: index$1, v1: index$6, v2: index$5 }); exports.MastoDeserializeError = MastoDeserializeError; exports.MastoHttpError = MastoHttpError; exports.MastoInvalidArgumentError = MastoInvalidArgumentError; exports.MastoTimeoutError = MastoTimeoutError; exports.MastoUnexpectedError = MastoUnexpectedError; exports.MastoWebSocketError = MastoWebSocketError; exports.createOAuthAPIClient = createOAuthAPIClient; exports.createRestAPIClient = createRestAPIClient; exports.createStreamingAPIClient = createStreamingAPIClient; exports.mastodon = mastodon;