UNPKG

@ztimson/momentum

Version:

Client library for momentum

1,407 lines (1,406 loc) 105 kB
(function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.utils = {})); })(this, function(exports2) { "use strict";var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __defProp2 = Object.defineProperty; var __defNormalProp2 = (obj, key, value2) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2; var __publicField2 = (obj, key, value2) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value2); function clean(obj, undefinedOnly = false) { if (obj == null) throw new Error("Cannot clean a NULL value"); if (Array.isArray(obj)) { obj = obj.filter((o) => o != null); } else { Object.entries(obj).forEach(([key, value2]) => { if (undefinedOnly && value2 === void 0 || !undefinedOnly && value2 == null) delete obj[key]; }); } return obj; } function deepCopy(value2) { try { return structuredClone(value2); } catch { return JSON.parse(JSONSanitize(value2)); } } function isEqual(a2, b2) { const ta = typeof a2, tb = typeof b2; if (ta != "object" || a2 == null || (tb != "object" || b2 == null)) return ta == "function" && tb == "function" ? a2.toString() == b2.toString() : a2 === b2; const keys = Object.keys(a2); if (keys.length != Object.keys(b2).length) return false; return Object.keys(a2).every((key) => isEqual(a2[key], b2[key])); } function JSONAttemptParse(json) { try { return JSON.parse(json); } catch { return json; } } function JSONSanitize(obj, space) { return JSON.stringify(obj, (key, value2) => { return value2; }, space); } function makeArray(value2) { return Array.isArray(value2) ? value2 : [value2]; } class ASet extends Array { /** Number of elements in set */ get size() { return this.length; } /** * Array to create set from, duplicate values will be removed * @param {T[]} elements Elements which will be added to set */ constructor(elements = []) { super(); if (!!(elements == null ? void 0 : elements["forEach"])) elements.forEach((el) => this.add(el)); } /** * Add elements to set if unique * @param items */ add(...items) { items.filter((el) => !this.has(el)).forEach((el) => this.push(el)); return this; } /** * Delete elements from set * @param items Elements that will be deleted */ delete(...items) { items.forEach((el) => { const index = this.indexOf(el); if (index != -1) this.splice(index, 1); }); return this; } /** * Create list of elements this set has which the comparison set does not * @param {ASet<T>} set Set to compare against * @return {ASet<T>} Different elements */ difference(set) { return new ASet(this.filter((el) => !set.has(el))); } /** * Check if set includes element * @param {T} el Element to look for * @return {boolean} True if element was found, false otherwise */ has(el) { return this.indexOf(el) != -1; } /** * Find index number of element, or -1 if it doesn't exist. Matches by equality not reference * * @param {T} search Element to find * @param {number} fromIndex Starting index position * @return {number} Element index number or -1 if missing */ indexOf(search2, fromIndex) { return super.findIndex((el) => isEqual(el, search2), fromIndex); } /** * Create list of elements this set has in common with the comparison set * @param {ASet<T>} set Set to compare against * @return {boolean} Set of common elements */ intersection(set) { return new ASet(this.filter((el) => set.has(el))); } /** * Check if this set has no elements in common with the comparison set * @param {ASet<T>} set Set to compare against * @return {boolean} True if nothing in common, false otherwise */ isDisjointFrom(set) { return this.intersection(set).size == 0; } /** * Check if all elements in this set are included in the comparison set * @param {ASet<T>} set Set to compare against * @return {boolean} True if all elements are included, false otherwise */ isSubsetOf(set) { return this.findIndex((el) => !set.has(el)) == -1; } /** * Check if all elements from comparison set are included in this set * @param {ASet<T>} set Set to compare against * @return {boolean} True if all elements are included, false otherwise */ isSuperset(set) { return set.findIndex((el) => !this.has(el)) == -1; } /** * Create list of elements that are only in one set but not both (XOR) * @param {ASet<T>} set Set to compare against * @return {ASet<T>} New set of unique elements */ symmetricDifference(set) { return new ASet([...this.difference(set), ...set.difference(this)]); } /** * Create joined list of elements included in this & the comparison set * @param {ASet<T>} set Set join * @return {ASet<T>} New set of both previous sets combined */ union(set) { return new ASet([...this, ...set]); } } class Cache { /** * Create new cache * * @param {keyof T} key Default property to use as primary key * @param options */ constructor(key, options = {}) { __publicField2(this, "store", {}); __publicField2(this, "complete", false); __publicField2(this, "values", this.all()); this.key = key; this.options = options; if (options.storageKey && !options.storage && typeof Storage !== "undefined") options.storage = localStorage; if (options.storageKey && options.storage) { const stored = options.storage.getItem(options.storageKey); if (stored) { try { Object.assign(this.store, JSON.parse(stored)); } catch { } } } return new Proxy(this, { get: (target, prop2) => { if (prop2 in target) return target[prop2]; return deepCopy(target.store[prop2]); }, set: (target, prop2, value2) => { if (prop2 in target) target[prop2] = value2; else this.set(prop2, value2); return true; } }); } getKey(value2) { if (!this.key) throw new Error("No key defined"); return value2[this.key]; } /** * Get all cached items * * @return {T[]} Array of items */ all() { return deepCopy(Object.values(this.store)); } /** * Add a new item to the cache. Like set, but finds key automatically * * @param {T} value Item to add to cache * @param {number | undefined} ttl Override default expiry * @return {this} */ add(value2, ttl = this.ttl) { const key = this.getKey(value2); this.set(key, value2, ttl); return this; } /** * Add several rows to the cache * * @param {T[]} rows Several items that will be cached using the default key * @param complete Mark cache as complete & reliable, defaults to true * @return {this} */ addAll(rows2, complete = true) { rows2.forEach((r2) => this.add(r2)); this.complete = complete; return this; } /** * Remove all keys from cache */ clear() { this.store = {}; } /** * Delete an item from the cache * * @param {K} key Item's primary key */ delete(key) { delete this.store[key]; if (this.options.storageKey && this.options.storage) this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store)); } /** * Return cache as an array of key-value pairs * @return {[K, T][]} Key-value pairs array */ entries() { return Object.entries(this.store); } /** * Get item from the cache * @param {K} key Key to lookup * @return {T} Cached item */ get(key) { return deepCopy(this.store[key]); } /** * Get a list of cached keys * * @return {K[]} Array of keys */ keys() { return Object.keys(this.store); } /** * Get map of cached items * * @return {Record<K, T>} */ map() { return deepCopy(this.store); } /** * Add an item to the cache manually specifying the key * * @param {K} key Key item will be cached under * @param {T} value Item to cache * @param {number | undefined} ttl Override default expiry in seconds * @return {this} */ set(key, value2, ttl = this.options.ttl) { this.store[key] = value2; if (this.options.storageKey && this.options.storage) this.options.storage.setItem(this.options.storageKey, JSON.stringify(this.store)); if (ttl) setTimeout(() => { this.complete = false; this.delete(key); }, ttl * 1e3); return this; } } function blackOrWhite(background) { const exploded = background == null ? void 0 : background.match(background.length >= 6 ? /\w\w/g : /\w/g); if (!exploded) return "black"; const [r2, g, b2] = exploded.map((hex) => parseInt(hex, 16)); const luminance = (0.299 * r2 + 0.587 * g + 0.114 * b2) / 255; return luminance > 0.5 ? "black" : "white"; } class PromiseProgress extends Promise { constructor(executor) { super((resolve, reject) => executor( (value2) => resolve(value2), (reason) => reject(reason), (progress) => this.progress = progress )); __publicField2(this, "listeners", []); __publicField2(this, "_progress", 0); } get progress() { return this._progress; } set progress(p2) { if (p2 == this._progress) return; this._progress = p2; this.listeners.forEach((l) => l(p2)); } static from(promise) { if (promise instanceof PromiseProgress) return promise; return new PromiseProgress((res, rej) => promise.then((...args) => res(...args)).catch((...args) => rej(...args))); } from(promise) { const newPromise = PromiseProgress.from(promise); this.onProgress((p2) => newPromise.progress = p2); return newPromise; } onProgress(callback) { this.listeners.push(callback); return this; } then(res, rej) { const resp = super.then(res, rej); return this.from(resp); } catch(rej) { return this.from(super.catch(rej)); } finally(res) { return this.from(super.finally(res)); } } function formatDate(format = "YYYY-MM-DD H:mm", date = /* @__PURE__ */ new Date(), tz) { const timezones = [ ["IDLW", -12], ["SST", -11], ["HST", -10], ["AKST", -9], ["PST", -8], ["MST", -7], ["CST", -6], ["EST", -5], ["AST", -4], ["BRT", -3], ["MAT", -2], ["AZOT", -1], ["UTC", 0], ["CET", 1], ["EET", 2], ["MSK", 3], ["AST", 4], ["PKT", 5], ["IST", 5.5], ["BST", 6], ["ICT", 7], ["CST", 8], ["JST", 9], ["AEST", 10], ["SBT", 11], ["FJT", 12], ["TOT", 13], ["LINT", 14] ]; function adjustTz(date2, gmt) { const currentOffset = date2.getTimezoneOffset(); const adjustedOffset = gmt * 60; return new Date(date2.getTime() + (adjustedOffset + currentOffset) * 6e4); } function day(num) { return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][num] || "Unknown"; } function doy(date2) { const start = /* @__PURE__ */ new Date(`${date2.getFullYear()}-01-01 0:00:00`); return Math.ceil((date2.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24)); } function month(num) { return ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][num] || "Unknown"; } function suffix(num) { if (num % 100 >= 11 && num % 100 <= 13) return `${num}th`; switch (num % 10) { case 1: return `${num}st`; case 2: return `${num}nd`; case 3: return `${num}rd`; default: return `${num}th`; } } function tzOffset(offset) { const hours = ~~(offset / 60); const minutes = offset % 60; return (offset > 0 ? "-" : "") + `${hours}:${minutes.toString().padStart(2, "0")}`; } if (typeof date == "number" || typeof date == "string") date = new Date(date); let t; if (tz == null) tz = -(date.getTimezoneOffset() / 60); t = timezones.find((t2) => isNaN(tz) ? t2[0] == tz : t2[1] == tz); if (!t) throw new Error(`Unknown timezone: ${tz}`); date = adjustTz(date, t[1]); const tokens = { "YYYY": date.getFullYear().toString(), "YY": date.getFullYear().toString().slice(2), "MMMM": month(date.getMonth()), "MMM": month(date.getMonth()).slice(0, 3), "MM": (date.getMonth() + 1).toString().padStart(2, "0"), "M": (date.getMonth() + 1).toString(), "DDD": doy(date).toString(), "DD": date.getDate().toString().padStart(2, "0"), "Do": suffix(date.getDate()), "D": date.getDate().toString(), "dddd": day(date.getDay()), "ddd": day(date.getDay()).slice(0, 3), "HH": date.getHours().toString().padStart(2, "0"), "H": date.getHours().toString(), "hh": (date.getHours() % 12 || 12).toString().padStart(2, "0"), "h": (date.getHours() % 12 || 12).toString(), "mm": date.getMinutes().toString().padStart(2, "0"), "m": date.getMinutes().toString(), "ss": date.getSeconds().toString().padStart(2, "0"), "s": date.getSeconds().toString(), "SSS": date.getMilliseconds().toString().padStart(3, "0"), "A": date.getHours() >= 12 ? "PM" : "AM", "a": date.getHours() >= 12 ? "pm" : "am", "ZZ": tzOffset(t[1] * 60).replace(":", ""), "Z": tzOffset(t[1] * 60), "z": typeof tz == "string" ? tz : t[0] }; return format.replace(/YYYY|YY|MMMM|MMM|MM|M|DDD|DD|Do|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|SSS|A|a|ZZ|Z|z/g, (token) => tokens[token]); } function downloadFile(blob, name) { if (!(blob instanceof Blob)) blob = new Blob(makeArray(blob)); const url = URL.createObjectURL(blob); downloadUrl(url, name); URL.revokeObjectURL(url); } function downloadUrl(href, name) { const a2 = document.createElement("a"); a2.href = href; a2.download = name || href.split("/").pop(); document.body.appendChild(a2); a2.click(); document.body.removeChild(a2); } function fileBrowser(options = {}) { return new Promise((res) => { const input = document.createElement("input"); input.type = "file"; input.accept = options.accept || "*"; input.style.display = "none"; input.multiple = !!options.multiple; input.onblur = input.onchange = async () => { res(Array.from(input.files)); input.remove(); }; document.body.appendChild(input); input.click(); }); } function timestampFilename(name, date = /* @__PURE__ */ new Date()) { if (typeof date == "number" || typeof date == "string") date = new Date(date); const timestamp = formatDate("YYYY-MM-DD_HH:mm:ss", date); return timestamp; } function uploadWithProgress(options) { return new PromiseProgress((res, rej, prog) => { const xhr = new XMLHttpRequest(); const formData2 = new FormData(); options.files.forEach((f) => formData2.append("file", f)); xhr.withCredentials = !!options.withCredentials; xhr.upload.addEventListener("progress", (event) => event.lengthComputable ? prog(event.loaded / event.total) : null); xhr.addEventListener("loadend", () => res(JSONAttemptParse(xhr.responseText))); xhr.addEventListener("error", () => rej(JSONAttemptParse(xhr.responseText))); xhr.addEventListener("timeout", () => rej({ error: "Request timed out" })); xhr.open("POST", options.url); Object.entries(options.headers || {}).forEach(([key, value2]) => xhr.setRequestHeader(key, value2)); xhr.send(formData2); }); } class TypedEmitter { constructor() { __publicField2(this, "listeners", {}); } static emit(event, ...args) { (this.listeners["*"] || []).forEach((l) => l(event, ...args)); (this.listeners[event.toString()] || []).forEach((l) => l(...args)); } static off(event, listener) { const e = event.toString(); this.listeners[e] = (this.listeners[e] || []).filter((l) => l === listener); } static on(event, listener) { var _a; const e = event.toString(); if (!this.listeners[e]) this.listeners[e] = []; (_a = this.listeners[e]) == null ? void 0 : _a.push(listener); return () => this.off(event, listener); } static once(event, listener) { return new Promise((res) => { const unsubscribe = this.on(event, (...args) => { res(args.length == 1 ? args[0] : args); if (listener) listener(...args); unsubscribe(); }); }); } emit(event, ...args) { (this.listeners["*"] || []).forEach((l) => l(event, ...args)); (this.listeners[event] || []).forEach((l) => l(...args)); } off(event, listener) { this.listeners[event] = (this.listeners[event] || []).filter((l) => l === listener); } on(event, listener) { var _a; if (!this.listeners[event]) this.listeners[event] = []; (_a = this.listeners[event]) == null ? void 0 : _a.push(listener); return () => this.off(event, listener); } once(event, listener) { return new Promise((res) => { const unsubscribe = this.on(event, (...args) => { res(args.length == 1 ? args[0] : args); if (listener) listener(...args); unsubscribe(); }); }); } } __publicField2(TypedEmitter, "listeners", {}); class CustomError extends Error { constructor(message, code) { super(message); __publicField2(this, "_code"); if (code != null) this._code = code; } get code() { return this._code || this.constructor.code; } set code(c) { this._code = c; } static from(err) { const code = Number(err.statusCode) ?? Number(err.code); const newErr = new this(err.message || err.toString()); return Object.assign(newErr, { stack: err.stack, ...err, code: code ?? void 0 }); } static instanceof(err) { return err.constructor.code != void 0; } toString() { return this.message || super.toString(); } } __publicField2(CustomError, "code", 500); class BadRequestError extends CustomError { constructor(message = "Bad Request") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(BadRequestError, "code", 400); class UnauthorizedError extends CustomError { constructor(message = "Unauthorized") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(UnauthorizedError, "code", 401); class PaymentRequiredError extends CustomError { constructor(message = "Payment Required") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(PaymentRequiredError, "code", 402); class ForbiddenError extends CustomError { constructor(message = "Forbidden") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(ForbiddenError, "code", 403); class NotFoundError extends CustomError { constructor(message = "Not Found") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(NotFoundError, "code", 404); class MethodNotAllowedError extends CustomError { constructor(message = "Method Not Allowed") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(MethodNotAllowedError, "code", 405); class NotAcceptableError extends CustomError { constructor(message = "Not Acceptable") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(NotAcceptableError, "code", 406); class InternalServerError extends CustomError { constructor(message = "Internal Server Error") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(InternalServerError, "code", 500); class NotImplementedError extends CustomError { constructor(message = "Not Implemented") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(NotImplementedError, "code", 501); class BadGatewayError extends CustomError { constructor(message = "Bad Gateway") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(BadGatewayError, "code", 502); class ServiceUnavailableError extends CustomError { constructor(message = "Service Unavailable") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(ServiceUnavailableError, "code", 503); class GatewayTimeoutError extends CustomError { constructor(message = "Gateway Timeout") { super(message); } static instanceof(err) { return err.constructor.code == this.code; } } __publicField2(GatewayTimeoutError, "code", 504); class HttpResponse extends Response { constructor(resp, stream) { super(stream, { headers: resp.headers, status: resp.status, statusText: resp.statusText }); __publicField2(this, "data"); __publicField2(this, "ok"); __publicField2(this, "redirected"); __publicField2(this, "type"); __publicField2(this, "url"); this.ok = resp.ok; this.redirected = resp.redirected; this.type = resp.type; this.url = resp.url; } } const _Http = class _Http2 { constructor(defaults = {}) { __publicField2(this, "interceptors", {}); __publicField2(this, "headers", {}); __publicField2(this, "url"); this.url = defaults.url ?? null; this.headers = defaults.headers || {}; if (defaults.interceptors) { defaults.interceptors.forEach((i) => _Http2.addInterceptor(i)); } } static addInterceptor(fn) { const key = Object.keys(_Http2.interceptors).length.toString(); _Http2.interceptors[key] = fn; return () => { _Http2.interceptors[key] = null; }; } addInterceptor(fn) { const key = Object.keys(this.interceptors).length.toString(); this.interceptors[key] = fn; return () => { this.interceptors[key] = null; }; } request(opts = {}) { var _a; if (!this.url && !opts.url) throw new Error("URL needs to be set"); let url = (((_a = opts.url) == null ? void 0 : _a.startsWith("http")) ? opts.url : (this.url || "") + (opts.url || "")).replace(/([^:]\/)\/+/g, "$1"); if (opts.fragment) url.includes("#") ? url.replace(/#.*(\?|\n)/g, (match, arg1) => `#${opts.fragment}${arg1}`) : url += "#" + opts.fragment; if (opts.query) { const q = Array.isArray(opts.query) ? opts.query : Object.keys(opts.query).map((k) => ({ key: k, value: opts.query[k] })); url += (url.includes("?") ? "&" : "?") + q.map((q2) => `${q2.key}=${q2.value}`).join("&"); } const headers = clean({ "Content-Type": !opts.body ? void 0 : opts.body instanceof FormData ? "multipart/form-data" : "application/json", ..._Http2.headers, ...this.headers, ...opts.headers }); if (typeof opts.body == "object" && opts.body != null && headers["Content-Type"] == "application/json") opts.body = JSON.stringify(opts.body); return new PromiseProgress((res, rej, prog) => { try { fetch(url, { headers, method: opts.method || (opts.body ? "POST" : "GET"), body: opts.body }).then(async (resp) => { var _a2, _b; for (let fn of [...Object.values(_Http2.interceptors), ...Object.values(this.interceptors)]) { await new Promise((res2) => fn(resp, () => res2())); } const contentLength = resp.headers.get("Content-Length"); const total = contentLength ? parseInt(contentLength, 10) : 0; let loaded = 0; const reader = (_a2 = resp.body) == null ? void 0 : _a2.getReader(); const stream = new ReadableStream({ start(controller) { function push() { reader == null ? void 0 : reader.read().then((event) => { if (event.done) return controller.close(); loaded += event.value.byteLength; prog(loaded / total); controller.enqueue(event.value); push(); }).catch((error) => controller.error(error)); } push(); } }); resp = new HttpResponse(resp, stream); if (opts.decode !== false) { const content = (_b = resp.headers.get("Content-Type")) == null ? void 0 : _b.toLowerCase(); if (content == null ? void 0 : content.includes("form")) resp.data = await resp.formData(); else if (content == null ? void 0 : content.includes("json")) resp.data = await resp.json(); else if (content == null ? void 0 : content.includes("text")) resp.data = await resp.text(); else if (content == null ? void 0 : content.includes("application")) resp.data = await resp.blob(); } if (resp.ok) res(resp); else rej(resp); }).catch((err) => rej(err)); } catch (err) { rej(err); } }); } }; __publicField2(_Http, "interceptors", {}); __publicField2(_Http, "headers", {}); let Http = _Http; const CliEffects = { CLEAR: "\x1B[0m" }; const CliForeground = { RED: "\x1B[31m", YELLOW: "\x1B[33m", BLUE: "\x1B[34m", LIGHT_GREY: "\x1B[37m" }; var LOG_LEVEL = /* @__PURE__ */ ((LOG_LEVEL2) => { LOG_LEVEL2[LOG_LEVEL2["ERROR"] = 0] = "ERROR"; LOG_LEVEL2[LOG_LEVEL2["WARN"] = 1] = "WARN"; LOG_LEVEL2[LOG_LEVEL2["INFO"] = 2] = "INFO"; LOG_LEVEL2[LOG_LEVEL2["LOG"] = 3] = "LOG"; LOG_LEVEL2[LOG_LEVEL2["DEBUG"] = 4] = "DEBUG"; return LOG_LEVEL2; })(LOG_LEVEL || {}); const _Logger = class _Logger2 extends TypedEmitter { constructor(namespace) { super(); this.namespace = namespace; } format(...text) { const now = /* @__PURE__ */ new Date(); const timestamp = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()} ${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}.${now.getMilliseconds().toString().padEnd(3, "0")}`; return `${timestamp}${this.namespace ? ` [${this.namespace}]` : ""} ${text.map((t) => typeof t == "string" ? t : JSONSanitize(t, 2)).join(" ")}`; } debug(...args) { if (_Logger2.LOG_LEVEL < 4) return; const str = this.format(...args); _Logger2.emit(4, str); console.debug(CliForeground.LIGHT_GREY + str + CliEffects.CLEAR); } log(...args) { if (_Logger2.LOG_LEVEL < 3) return; const str = this.format(...args); _Logger2.emit(3, str); console.log(CliEffects.CLEAR + str); } info(...args) { if (_Logger2.LOG_LEVEL < 2) return; const str = this.format(...args); _Logger2.emit(2, str); console.info(CliForeground.BLUE + str + CliEffects.CLEAR); } warn(...args) { if (_Logger2.LOG_LEVEL < 1) return; const str = this.format(...args); _Logger2.emit(1, str); console.warn(CliForeground.YELLOW + str + CliEffects.CLEAR); } error(...args) { if (_Logger2.LOG_LEVEL < 0) return; const str = this.format(...args); _Logger2.emit(0, str); console.error(CliForeground.RED + str + CliEffects.CLEAR); } }; __publicField2(_Logger, "LOG_LEVEL", 4); function PE(str, ...args) { const combined = []; for (let i = 0; i < str.length || i < args.length; i++) { if (str[i]) combined.push(str[i]); if (args[i]) combined.push(args[i]); } return new PathEvent(combined.join("")); } function PES(str, ...args) { let combined = []; for (let i = 0; i < str.length || i < args.length; i++) { if (str[i]) combined.push(str[i]); if (args[i]) combined.push(args[i]); } const [paths, methods] = combined.join("").split(":"); return PathEvent.toString(paths, methods == null ? void 0 : methods.split("")); } class PathError extends Error { } class PathEvent { constructor(Event) { __publicField2(this, "module"); __publicField2(this, "fullPath"); __publicField2(this, "path"); __publicField2(this, "name"); __publicField2(this, "methods"); var _a; if (typeof Event == "object") return Object.assign(this, Event); let [p2, scope, method] = Event.replaceAll(/\/{2,}/g, "/").split(":"); if (!method) method = scope || "*"; if (p2 == "*" || !p2 && method == "*") { p2 = ""; method = "*"; } let temp = p2.split("/").filter((p22) => !!p22); this.module = ((_a = temp.splice(0, 1)[0]) == null ? void 0 : _a.toLowerCase()) || ""; this.fullPath = p2; this.path = temp.join("/"); this.name = temp.pop() || ""; this.methods = new ASet(method.split("")); } /** All/Wildcard specified */ get all() { return this.methods.has("*"); } set all(v2) { v2 ? new ASet(["*"]) : this.methods.delete("*"); } /** None specified */ get none() { return this.methods.has("n"); } set none(v2) { v2 ? this.methods = new ASet(["n"]) : this.methods.delete("n"); } /** Create method specified */ get create() { return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("c")); } set create(v2) { v2 ? this.methods.delete("n").add("c") : this.methods.delete("c"); } /** Read method specified */ get read() { return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("r")); } set read(v2) { v2 ? this.methods.delete("n").add("r") : this.methods.delete("r"); } /** Update method specified */ get update() { return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("u")); } set update(v2) { v2 ? this.methods.delete("n").add("u") : this.methods.delete("u"); } /** Delete method specified */ get delete() { return !this.methods.has("n") && (this.methods.has("*") || this.methods.has("d")); } set delete(v2) { v2 ? this.methods.delete("n").add("d") : this.methods.delete("d"); } /** * Combine multiple events into one parsed object. Longest path takes precedent, but all subsequent methods are * combined until a "none" is reached * * @param {string | PathEvent} paths Events as strings or pre-parsed * @return {PathEvent} Final combined permission */ static combine(...paths) { let hitNone = false; const combined = paths.map((p2) => new PathEvent(p2)).toSorted((p1, p2) => { const l1 = p1.fullPath.length, l2 = p2.fullPath.length; return l1 < l2 ? 1 : l1 > l2 ? -1 : 0; }).reduce((acc, p2) => { if (p2.none) hitNone = true; if (!acc) return p2; if (hitNone) return acc; acc.methods = [...acc.methods, ...p2.methods]; return acc; }, null); combined.methods = new ASet(combined.methods); return combined; } /** * Filter a set of paths based on the target * * @param {string | PathEvent | (string | PathEvent)[]} target Array of events that will filtered * @param filter {...PathEvent} Must container one of * @return {boolean} Whether there is any overlap */ static filter(target, ...filter) { const parsedTarget = makeArray(target).map((pe) => new PathEvent(pe)); const parsedFind = makeArray(filter).map((pe) => new PathEvent(pe)); return parsedTarget.filter((t) => { if (!t.fullPath && t.all) return true; return !!parsedFind.find((f) => (t.fullPath.startsWith(f.fullPath) || f.fullPath.startsWith(t.fullPath)) && (f.all || t.all || t.methods.intersection(f.methods).length)); }); } /** * Squash 2 sets of paths & return true if any overlap is found * * @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed * @param has Target must have at least one of these path * @return {boolean} Whether there is any overlap */ static has(target, ...has) { const parsedRequired = makeArray(has).map((pe) => new PathEvent(pe)); const parsedTarget = makeArray(target).map((pe) => new PathEvent(pe)); return !!parsedRequired.find((r2) => { if (!r2.fullPath && r2.all) return true; const filtered = parsedTarget.filter((p2) => r2.fullPath.startsWith(p2.fullPath)); if (!filtered.length) return false; const combined = PathEvent.combine(...filtered); return !combined.none && (combined.all || r2.all) || combined.methods.intersection(r2.methods).length; }); } /** * Squash 2 sets of paths & return true if the target has all paths * * @param {string | PathEvent | (string | PathEvent)[]} target Array of Events as strings or pre-parsed * @param has Target must have all these paths * @return {boolean} Whether there is any overlap */ static hasAll(target, ...has) { return has.filter((h) => PathEvent.has(target, h)).length == has.length; } /** * Same as `has` but raises an error if there is no overlap * * @param {string | string[]} target Array of Events as strings or pre-parsed * @param has Target must have at least one of these path */ static hasFatal(target, ...has) { if (!PathEvent.has(target, ...has)) throw new PathError(`Requires one of: ${makeArray(has).join(", ")}`); } /** * Same as `hasAll` but raises an error if the target is missing any paths * * @param {string | string[]} target Array of Events as strings or pre-parsed * @param has Target must have all these paths */ static hasAllFatal(target, ...has) { if (!PathEvent.hasAll(target, ...has)) throw new PathError(`Requires all: ${makeArray(has).join(", ")}`); } /** * Create event string from its components * * @param {string | string[]} path Event path * @param {Method} methods Event method * @return {string} String representation of Event */ static toString(path, methods) { let p2 = makeArray(path).filter((p22) => p22 != null).join("/"); p2 = p2 == null ? void 0 : p2.trim().replaceAll(/\/{2,}/g, "/").replaceAll(/(^\/|\/$)/g, ""); if (methods == null ? void 0 : methods.length) p2 += `:${makeArray(methods).map((m) => m.toLowerCase()).join("")}`; return p2; } /** * Filter a set of paths based on this event * * @param {string | PathEvent | (string | PathEvent)[]} target Array of events that will filtered * @return {boolean} Whether there is any overlap */ filter(target) { return PathEvent.filter(target, this); } /** * Create event string from its components * * @return {string} String representation of Event */ toString() { return PathEvent.toString(this.fullPath, this.methods); } } class PathEventEmitter { constructor() { __publicField2(this, "listeners", []); } emit(event, ...args) { const parsed = new PathEvent(event); this.listeners.filter((l) => PathEvent.has(l[0], event)).forEach(async (l) => l[1](parsed, ...args)); } off(listener) { this.listeners = this.listeners.filter((l) => l[1] != listener); } on(event, listener) { makeArray(event).forEach((e) => this.listeners.push([new PathEvent(e), listener])); return () => this.off(listener); } once(event, listener) { return new Promise((res) => { const unsubscribe = this.on(event, (event2, ...args) => { res(args.length < 2 ? args[0] : args); if (listener) listener(event2, ...args); unsubscribe(); }); }); } relayEvents(emitter) { emitter.on("*", (event, ...args) => this.emit(event, ...args)); } } class Api extends Http { constructor(url = location.origin, opts = {}) { super({ ...(opts == null ? void 0 : opts.http) || {}, url }); __publicField(this, "emitter", new PathEventEmitter()); __publicField(this, "pending", {}); __publicField(this, "storageKey"); __publicField(this, "host"); __publicField(this, "_token", null); __publicField(this, "emit", this.emitter.emit.bind(this.emitter)); __publicField(this, "off", this.emitter.off.bind(this.emitter)); __publicField(this, "on", this.emitter.on.bind(this.emitter)); __publicField(this, "once", this.emitter.once.bind(this.emitter)); __publicField(this, "relayEvents", this.emitter.relayEvents.bind(this.emitter)); this.url = url; this.opts = opts; this.host = new URL(url).host; this.storageKey = `momentum:token:${this.host}`; if (opts.persist && localStorage) { const token = localStorage.getItem(this.storageKey); if (token) this.token = token; } } get sameOrigin() { if (typeof window == "undefined" || !(window == null ? void 0 : window.location)) return false; return window.location.host == this.host; } /** Current API token */ get token() { return this._token; } set token(token) { if (token == this._token) return; this._token = token; this.headers["Authorization"] = token ? `Bearer ${token}` : null; if (this.opts.persist && localStorage) { if (token) localStorage.setItem(this.storageKey, token); else localStorage.removeItem(this.storageKey); } this.emit(PES`api/token:${token ? "u" : "d"}`, token); } /** * Fetch current Momentum status * @return {Promise<Health>} */ healthcheck() { return this.request({ url: "/api/healthcheck" }).then((resp) => { this.emit(PES`api/healthcheck:r`, resp); return resp; }); } /** * Create API request * @param {HttpRequestOptions} options Request options * @return {Promise} Response */ request(options) { const key = JSONSanitize(options); const method = options.method == "GET" ? "r" : options.method == "POST" ? "c" : options.method == "DELETE" ? "d" : "u"; if (this.pending[key] != null) return this.pending[key]; this.pending[key] = super.request({ ...options, credentials: "include" }).then((response) => { this.emit(PES`api/response:${method}`, { request: options, response }); return options.decode === false ? response : response.data; }).catch((err) => { const e = (err == null ? void 0 : err.data) || err; this.emit(PES`api/error:${method}`, { request: options, error: e }); throw e; }).finally(() => delete this.pending[key]); this.emit(PES`api/request:${method}`, { request: options, response: this.pending[key] }); return this.pending[key]; } } var ActionType = /* @__PURE__ */ ((ActionType2) => { ActionType2[ActionType2["CRON"] = 0] = "CRON"; ActionType2[ActionType2["EVENT"] = 1] = "EVENT"; ActionType2[ActionType2["DELETE"] = 2] = "DELETE"; ActionType2[ActionType2["GET"] = 3] = "GET"; ActionType2[ActionType2["PATCH"] = 4] = "PATCH"; ActionType2[ActionType2["POST"] = 5] = "POST"; ActionType2[ActionType2["PUT"] = 6] = "PUT"; return ActionType2; })(ActionType || {}); class Actions extends PathEventEmitter { constructor(api) { super(); __publicField(this, "api"); __publicField(this, "cache", new Cache("_id")); this.api = typeof api == "string" ? new Api(api) : api; } /** * All saved actions * @param {boolean} reload Will use cached response if false * @return {Promise<Action[]>} List of saved actions */ async all(reload) { if (!reload && this.cache.complete) return this.cache.all(); return this.api.request({ url: `/api/` + PES`actions` }).then((resp) => { this.cache.addAll(resp); this.emit(PES`actions:r`, resp || []); return resp; }); } /** * Manually trigger an actions execution * @param {string} id Action ID * @param {HttpRequestOptions} opts Additional arguments * @return {Promise<ActionResult>} All action output including console logs & return */ debug(id, opts = {}) { return this.api.request({ url: "/api/" + PES`actions/debug/${id}`, method: "POST", ...opts }); } /** * Delete an existing action * @param {string} id Action ID * @return {Promise<void>} Delete complete */ delete(id) { if (!id) throw new Error("Cannot delete action, missing ID"); return this.api.request({ url: `/api/` + PES`actions/${id}`, method: "DELETE" }).then(() => { this.cache.delete(id); this.emit(PES`actions/${id}:d`, id); }); } /** * Fetch action info * @param {string} id Action ID * @param {boolean} reload Will use cached response if false * @return {Promise<Action | null>} Requested action */ read(id, reload) { if (!id) throw new Error("Cannot read action, missing ID"); const cached = this.cache.get(id); if (!reload && cached) return Promise.resolve(cached); return this.api.request({ url: `/api/` + PES`actions/${id}` }).then((action) => { if (action) this.cache.add(action); this.emit(PES`actions/${id}:r`, action); return action; }); } /** * Run an HTTP action * @param {string} path HTTP path excluding `/api/actions/run` * @param {HttpRequestOptions} opts HTTP options * @return {Promise<T>} HTTP response */ run(path, opts = {}) { return this.api.request({ ...opts, url: "/api/" + PES`actions/run/${path}` }); } /** * Update an action * @param {Action} action The new action * @return {Promise<Action>} Saved action */ update(action) { return this.api.request({ url: `/api/` + PES`actions/${action._id}`, method: "POST", body: action }).then((action2) => { if (action2) this.cache.add(action2); this.emit(PES`actions/${action2._id}:u`, action2); return action2; }); } } class Ai extends PathEventEmitter { constructor(api) { super(); __publicField(this, "api"); this.api = typeof api == "string" ? new Api(api) : api; } /** * Ask the AI assistant a question * @param {string} question Users question * @param {any} context Additional data to aid response * @return {Promise<string>} AI's response */ ask(question, context) { if (!question) throw new Error("Cannot ask AI, missing question"); return this.api.request({ url: `/api/` + PES`ai`, method: "POST", body: { question, context } }).then((response) => { this.emit(PES`ai:c`, { question, context, response }); return response; }); } /** * Clear AI assistant memory & context * @return {Promise<void>} Resolves once complete */ clear() { return this.api.request({ url: "/api/" + PES`ai`, method: "DELETE" }).then(() => this.emit(PES`ai:d`, this.api.token)); } /** * Current chat history * @return {Promise<{role: string, content: string}[]>} */ history() { return this.api.request({ url: "/api/" + PES`ai` }).then((resp) => { this.emit(PES`ai:r`, resp); return resp; }); } /** * Get model info * @return {Promise<{host: string, model: string}>} Model Info */ info() { return this.api.request({ url: "/api/ai/info" }); } } class Analytics extends PathEventEmitter { constructor(api) { super(); __publicField(this, "api"); this.api = typeof api == "string" ? new Api(api) : api; } /** * Perform IP trace * @param {string} ip IP address to trace * @return {Promise<IpTrace>} Trace results */ ipTrace(ip) { if (!ip) throw new Error("Cannot trace, missing IP"); return this.api.request({ url: `/api/` + PES`analytics/trace/${ip}` }).then((resp) => { this.emit(PES`analytics/trace/${ip}:r`, resp); return resp; }); } } class Token extends PathEventEmitter { constructor(api) { super(); this.api = api; } /** * Fetch all tokens for user * @param {string} username User to search * @return {Promise<UserToken[]>} List of tokens */ all(username) { return this.api.request({ url: "/" + PES`api/auth/tokens/${username}` }).then((resp) => { this.emit(PES`token/${username}:r`, resp); return resp; }); } /** * Create a new user token * @param {{name: string, owner: string, expire: null | Date}} token Token settings * @return {Promise<UserToken>} Crated token */ create(token) { return this.api.request({ url: "/" + PES`api/auth/tokens/${token.owner}`, body: token }).then((resp) => { this.emit(PES`token/${token.owner}:c`, token); return resp; }); } /** * Delete an existing user token * @param {string} id Token ID * @return {Promise<void>} Resolves once complete */ delete(id) { return this.api.request({ url: "/" + PES`api/auth/tokens/${id}`, method: "DELETE" }).then(() => this.emit(PES`token/${id}:d`, id)); } } class Totp { constructor(api) { /** * Enable 2FA for user * @param {string} username User to reset * @return {Promise<void>} Resolves once complete */ __publicField(this, "enable", this.reset); this.api = api; } /** * Disable 2FA for user * @param {string} username User to disable 2FA for * @return {Promise<void>} Resolves once complete */ disable(username) { return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, method: "DELETE" }); } /** * Reset users 2FA * @param {string} username User to reset * @return {Promise<void>} Resolves once complete */ reset(username) { return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, method: "POST" }); } /** * Setup 2FA authentication method * @param {string} username User to setup * @param {string} method Authenticator type * @param {string | null} totp null to being process, 2FA code to validate method * @return {Promise<void>} Resolves once complete */ setup(username, method = "app", totp) { return this.api.request({ url: "/" + PES`api/auth/totp/${username}`, body: clean({ method, totp }) }); } } class Auth extends PathEventEmitter { constructor(api, opts = {}) { super(); __publicField(this, "api"); /** Manage user tokens */ __publicField(this, "token"); /** Manage user 2FA */ __publicField(this, "totp"); __publicField(this, "_permissions", []); __publicField(this, "_user"); // Permission helpers __publicField(this, "filter", (...events) => PathEvent.filter(this.permissions, ...events)); __publicField(this, "has", (...events) => PathEvent.has(this.permissions, ...events)); __publicField(this, "hasAll", (...events) => PathEvent.hasAll(this.permissions, ...events)); __publicField(this, "hasFatal", (...events) => PathEvent.hasFatal(this.permissions, ...events)); __publicField(this, "hasAllFatal", (...events) => PathEvent.hasAllFatal(this.permissions, ...events)); this.opts = opts; this.api = typeof api == "string" ? new Api(api) : api; this.token = new Token(this.api); this.totp = new Totp(this.api); this.relayEvents(this.token); this.opts = { loginUrl: this.api.url + "/ui/login", ...this.opts }; this.api.addInterceptor((resp, next) => { const blacklist = [ "/api/auth/