UNPKG

@wandelbots/nova-js

Version:

Official JS client for the Wandelbots API

250 lines (249 loc) 9.1 kB
import { u as tryStringifyJson } from "./converters-DnG1fX23.mjs"; import ReconnectingWebSocket from "reconnecting-websocket"; import { isAxiosError } from "axios"; //#region src/lib/AutoReconnectingWebsocket.ts var AutoReconnectingWebsocket = class extends ReconnectingWebSocket { receivedFirstMessage; targetUrl; disposed = false; opts; constructor(targetUrl, opts = {}) { console.log("Opening websocket to", targetUrl); super(() => this.targetUrl || targetUrl, void 0, { startClosed: true }); Object.defineProperty(this, "url", { get() { return this.targetUrl; } }); this.targetUrl = targetUrl; this.opts = opts; this.addEventListener("open", () => { console.log(`Websocket to ${this.url} opened`); }); this.addEventListener("message", (ev) => { if (!this.receivedFirstMessage) this.receivedFirstMessage = ev; }); this.addEventListener("close", () => { console.log(`Websocket to ${this.url} closed`); }); const origReconnect = this.reconnect; this.reconnect = () => { if (this.opts.mock) this.opts.mock.handleWebsocketConnection(this); else origReconnect.apply(this); }; this.reconnect(); } changeUrl(targetUrl) { this.receivedFirstMessage = void 0; this.targetUrl = targetUrl; this.reconnect(); } sendJson(data) { if (this.opts.mock) this.opts.mock.handleWebsocketMessage(this, JSON.stringify(data)); else this.send(JSON.stringify(data)); } /** * Permanently close this websocket and indicate that * this object should not be used again. **/ dispose() { this.close(); this.disposed = true; if (this.opts.onDispose) this.opts.onDispose(); } /** * Returns a promise that resolves once the websocket * is in the OPEN state. */ async opened() { return new Promise((resolve, reject) => { if (this.readyState === WebSocket.OPEN) resolve(); else { this.addEventListener("open", () => resolve()); this.addEventListener("error", reject); } }); } /** * Returns a promise that resolves once the websocket * is in the CLOSED state. */ async closed() { return new Promise((resolve, reject) => { if (this.readyState === WebSocket.CLOSED) resolve(); else { this.addEventListener("close", () => resolve()); this.addEventListener("error", reject); } }); } /** * Returns a promise that resolves when the first message * is received from the websocket. Resolves immediately if * the first message has already been received. */ async firstMessage() { if (this.receivedFirstMessage) return this.receivedFirstMessage; return new Promise((resolve, reject) => { const onMessage = (ev) => { this.receivedFirstMessage = ev; this.removeEventListener("message", onMessage); this.removeEventListener("error", onError); resolve(ev); }; const onError = (ev) => { this.removeEventListener("message", onMessage); this.removeEventListener("error", onError); reject(ev); }; this.addEventListener("message", onMessage); this.addEventListener("error", onError); }); } /** * Returns a promise that resolves when the next message * is received from the websocket. */ async nextMessage() { return new Promise((resolve, reject) => { const onMessage = (ev) => { this.removeEventListener("message", onMessage); this.removeEventListener("error", onError); resolve(ev); }; const onError = (ev) => { this.removeEventListener("message", onMessage); this.removeEventListener("error", onError); reject(ev); }; this.addEventListener("message", onMessage); this.addEventListener("error", onError); }); } }; //#endregion //#region src/lib/availableStorage.ts /** * Safety wrapper around browser localStorage providing context availability * checks and JSON parsing */ var AvailableStorage = class { available = typeof window !== "undefined" && !!window.localStorage; getJSON(key) { if (!this.available) return null; const result = window.localStorage.getItem(key); if (result === null) return null; try { return JSON.parse(result); } catch (err) { return null; } } setJSON(key, obj) { if (!this.available) return null; window.localStorage.setItem(key, JSON.stringify(obj)); } delete(key) { if (!this.available) return null; window.localStorage.removeItem(key); } setString(key, value) { if (!this.available) return null; window.localStorage.setItem(key, value); } getString(key) { if (!this.available) return null; return window.localStorage.getItem(key); } }; const availableStorage = new AvailableStorage(); //#endregion //#region src/lib/errorHandling.ts function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Attempts to make a helpful error message from an unknown thrown error * or promise rejection. * * This function is mainly to aid debugging and good bug reports. For * expected errors encountered by end users, it's more ideal to catch * the specific error code and provide a localized app-specific error message. */ function makeErrorMessage(err) { if (isAxiosError(err)) { if (err.response) return `${err.response?.status} ${err.response?.statusText} from ${err.response?.config.method?.toUpperCase() || "accessing"} ${err.response?.config.url}: ${JSON.stringify(err.response?.data)}`; else if (err.config) if (err.code === "ERR_NETWORK") return `${err.message} from ${err.config.method?.toUpperCase() || "accessing"} ${err.config.url}. This error can happen because of either connection issues or server CORS policy.`; else return `${err.message} from ${err.config.method?.toUpperCase() || "accessing"} ${err.config.url}`; } else if (err instanceof Error) return err.message; else if (typeof err === "string") return err; else if (typeof err === "object") return tryStringifyJson(err) || `Unserializable object ${err}`; return `${err}`; } /** * Reloads the page with protection against loops. * Allows at most one reload per 10 seconds; otherwise throws * an error. */ function guardedPageReload(key) { const RELOAD_KEY = `novajs_reload_guard:${key}`; const RELOAD_COOLDOWN_MS = 1e4; const lastReloadAt = Number(window.sessionStorage.getItem(RELOAD_KEY) ?? "0"); if (Date.now() - lastReloadAt < RELOAD_COOLDOWN_MS) throw new Error(`Unhandled error caused a reload (${key}), but a reload was already attempted recently. Aborting to prevent reload loop.`); window.sessionStorage.setItem(RELOAD_KEY, String(Date.now())); window.location.reload(); return new Promise(() => {}); } //#endregion //#region src/lib/LoginWithAuth0.ts /** * Mapping of stages to Auth0 configurations. * The client ids are public identifiers for a specific auth0 application * and are safe to include in client-side code. * https://auth0.com/docs/get-started/applications/application-settings */ const auth0ConfigMap = { dev: { domain: `https://auth.portal.dev.wandelbots.io`, clientId: "fLbJD0RLp5r2Dpucm5j8BjwMR6Hunfha" }, stg: { domain: `https://auth.portal.stg.wandelbots.io`, clientId: "joVDeD9e786WzFNSGCqoVq7HNkWt5j6s" }, prod: { domain: `https://auth.portal.wandelbots.io`, clientId: "J7WJUi38xVQdJAEBNRT9Xw1b0fXDb4J2" } }; /** Determine which Auth0 configuration to use based on instance URL */ const getAuth0Config = (instanceUrl) => { if (instanceUrl.host.endsWith(".dev.wandelbots.io")) return auth0ConfigMap.dev; if (instanceUrl.host.endsWith(".stg.wandelbots.io")) return auth0ConfigMap.stg; if (instanceUrl.host.endsWith(".wandelbots.io")) return auth0ConfigMap.prod; throw new Error(`Unable to authenticate with NOVA instance "${instanceUrl}". Auth0 login is only supported for cloud instances with hosts of the form "**.wandelbots.io".`); }; /** * Initializes Auth0 login process using redirect if necessary and retrieves an access token. * Returns null when an access token should not be needed to authenticate (i.e. cookie auth * when deployed on the instance domain) */ const loginWithAuth0 = async (instanceUrl) => { if (typeof window === "undefined") throw new Error(`Access token must be set to use Nova when not in a browser environment.`); if (instanceUrl.origin === window.location.origin) return guardedPageReload("cloud_instance_auth"); const { Auth0Client } = await import("@auth0/auth0-spa-js"); const auth0Config = getAuth0Config(instanceUrl); const auth0Client = new Auth0Client({ domain: auth0Config.domain, clientId: auth0Config.clientId ?? "", useRefreshTokens: false, authorizationParams: { audience: "nova-api", redirect_uri: window.location.origin } }); if (window.location.search.includes("code=") && window.location.search.includes("state=")) { const { appState } = await auth0Client.handleRedirectCallback(); window.history.replaceState({}, document.title, appState?.returnTo || window.location.pathname); } else await auth0Client.loginWithRedirect(); return await auth0Client.getTokenSilently(); }; //#endregion export { makeErrorMessage as a, guardedPageReload as i, loginWithAuth0 as n, availableStorage as o, delay as r, AutoReconnectingWebsocket as s, getAuth0Config as t }; //# sourceMappingURL=LoginWithAuth0-D92HnzHq.mjs.map