@wandelbots/nova-js
Version:
Official JS client for the Wandelbots API
1 lines • 15.9 kB
Source Map (JSON)
{"version":3,"file":"LoginWithAuth0-D92HnzHq.mjs","names":[],"sources":["../src/lib/AutoReconnectingWebsocket.ts","../src/lib/availableStorage.ts","../src/lib/errorHandling.ts","../src/lib/LoginWithAuth0.ts"],"sourcesContent":["import ReconnectingWebSocket, { type ErrorEvent } from \"reconnecting-websocket\"\nimport type { MockNovaInstance } from \"./mock/MockNovaInstance.ts\"\n\nexport class AutoReconnectingWebsocket extends ReconnectingWebSocket {\n receivedFirstMessage?: MessageEvent\n targetUrl: string\n disposed = false\n readonly opts: {\n mock?: MockNovaInstance\n onDispose?: () => void\n }\n\n constructor(\n targetUrl: string,\n opts: {\n mock?: MockNovaInstance\n onDispose?: () => void\n } = {},\n ) {\n console.log(\"Opening websocket to\", targetUrl)\n\n super(() => this.targetUrl || targetUrl, undefined, {\n startClosed: true,\n })\n\n // Reconnecting websocket doesn't set this properly with startClosed\n Object.defineProperty(this, \"url\", {\n get() {\n return this.targetUrl\n },\n })\n\n this.targetUrl = targetUrl\n this.opts = opts\n\n this.addEventListener(\"open\", () => {\n console.log(`Websocket to ${this.url} opened`)\n })\n\n this.addEventListener(\"message\", (ev) => {\n if (!this.receivedFirstMessage) {\n this.receivedFirstMessage = ev\n }\n })\n\n this.addEventListener(\"close\", () => {\n console.log(`Websocket to ${this.url} closed`)\n })\n\n const origReconnect = this.reconnect\n this.reconnect = () => {\n if (this.opts.mock) {\n this.opts.mock.handleWebsocketConnection(this)\n } else {\n origReconnect.apply(this)\n }\n }\n\n this.reconnect()\n }\n\n changeUrl(targetUrl: string) {\n this.receivedFirstMessage = undefined\n this.targetUrl = targetUrl\n this.reconnect()\n }\n\n sendJson(data: unknown) {\n if (this.opts.mock) {\n this.opts.mock.handleWebsocketMessage(this, JSON.stringify(data))\n } else {\n this.send(JSON.stringify(data))\n }\n }\n\n /**\n * Permanently close this websocket and indicate that\n * this object should not be used again.\n **/\n dispose() {\n this.close()\n this.disposed = true\n if (this.opts.onDispose) {\n this.opts.onDispose()\n }\n }\n\n /**\n * Returns a promise that resolves once the websocket\n * is in the OPEN state. */\n async opened() {\n return new Promise<void>((resolve, reject) => {\n if (this.readyState === WebSocket.OPEN) {\n resolve()\n } else {\n this.addEventListener(\"open\", () => resolve())\n this.addEventListener(\"error\", reject)\n }\n })\n }\n\n /**\n * Returns a promise that resolves once the websocket\n * is in the CLOSED state. */\n async closed() {\n return new Promise<void>((resolve, reject) => {\n if (this.readyState === WebSocket.CLOSED) {\n resolve()\n } else {\n this.addEventListener(\"close\", () => resolve())\n this.addEventListener(\"error\", reject)\n }\n })\n }\n\n /**\n * Returns a promise that resolves when the first message\n * is received from the websocket. Resolves immediately if\n * the first message has already been received.\n */\n async firstMessage() {\n if (this.receivedFirstMessage) {\n return this.receivedFirstMessage\n }\n\n return new Promise<MessageEvent>((resolve, reject) => {\n const onMessage = (ev: MessageEvent) => {\n this.receivedFirstMessage = ev\n this.removeEventListener(\"message\", onMessage)\n this.removeEventListener(\"error\", onError)\n resolve(ev)\n }\n\n const onError = (ev: ErrorEvent) => {\n this.removeEventListener(\"message\", onMessage)\n this.removeEventListener(\"error\", onError)\n reject(ev)\n }\n\n this.addEventListener(\"message\", onMessage)\n this.addEventListener(\"error\", onError)\n })\n }\n\n /**\n * Returns a promise that resolves when the next message\n * is received from the websocket.\n */\n async nextMessage() {\n return new Promise<MessageEvent>((resolve, reject) => {\n const onMessage = (ev: MessageEvent) => {\n this.removeEventListener(\"message\", onMessage)\n this.removeEventListener(\"error\", onError)\n resolve(ev)\n }\n\n const onError = (ev: ErrorEvent) => {\n this.removeEventListener(\"message\", onMessage)\n this.removeEventListener(\"error\", onError)\n reject(ev)\n }\n\n this.addEventListener(\"message\", onMessage)\n this.addEventListener(\"error\", onError)\n })\n }\n}\n","/**\n * Safety wrapper around browser localStorage providing context availability\n * checks and JSON parsing\n */\nclass AvailableStorage {\n available = typeof window !== \"undefined\" && !!window.localStorage\n\n getJSON<T>(key: string): Partial<T> | null {\n if (!this.available) return null\n\n const result = window.localStorage.getItem(key)\n if (result === null) return null\n\n try {\n return JSON.parse(result)\n } catch (err) {\n return null\n }\n }\n\n setJSON(key: string, obj: unknown) {\n if (!this.available) return null\n\n window.localStorage.setItem(key, JSON.stringify(obj))\n }\n\n delete(key: string) {\n if (!this.available) return null\n\n window.localStorage.removeItem(key)\n }\n\n setString(key: string, value: string) {\n if (!this.available) return null\n\n window.localStorage.setItem(key, value)\n }\n\n getString(key: string): string | null {\n if (!this.available) return null\n\n return window.localStorage.getItem(key)\n }\n}\n\nexport const availableStorage = new AvailableStorage()\n","import { isAxiosError } from \"axios\"\nimport { tryStringifyJson } from \"./converters.ts\"\n\nexport function delay(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Attempts to make a helpful error message from an unknown thrown error\n * or promise rejection.\n *\n * This function is mainly to aid debugging and good bug reports. For\n * expected errors encountered by end users, it's more ideal to catch\n * the specific error code and provide a localized app-specific error message.\n */\nexport function makeErrorMessage(err: unknown): string {\n if (isAxiosError(err)) {\n if (err.response) {\n return `${err.response?.status} ${err.response?.statusText} from ${err.response?.config.method?.toUpperCase() || \"accessing\"} ${err.response?.config.url}: ${JSON.stringify(err.response?.data)}`\n } else if (err.config) {\n if (err.code === \"ERR_NETWORK\") {\n 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.`\n } else {\n return `${err.message} from ${err.config.method?.toUpperCase() || \"accessing\"} ${err.config.url}`\n }\n }\n } else if (err instanceof Error) {\n return err.message\n } else if (typeof err === \"string\") {\n return err\n } else if (typeof err === \"object\") {\n return tryStringifyJson(err) || `Unserializable object ${err}`\n }\n\n return `${err}`\n}\n\n/**\n * Reloads the page with protection against loops.\n * Allows at most one reload per 10 seconds; otherwise throws\n * an error.\n */\nexport function guardedPageReload(key: string): Promise<never> {\n const RELOAD_KEY = `novajs_reload_guard:${key}`\n const RELOAD_COOLDOWN_MS = 10_000\n const lastReloadAt = Number(window.sessionStorage.getItem(RELOAD_KEY) ?? \"0\")\n\n if (Date.now() - lastReloadAt < RELOAD_COOLDOWN_MS) {\n throw new Error(\n `Unhandled error caused a reload (${key}), but a reload was already attempted recently. Aborting to prevent reload loop.`,\n )\n }\n\n window.sessionStorage.setItem(RELOAD_KEY, String(Date.now()))\n window.location.reload()\n\n return new Promise(() => {}) // never settles, the page should be reloading\n}\n","import { guardedPageReload } from \"./errorHandling.ts\"\n\n/**\n * Mapping of stages to Auth0 configurations.\n * The client ids are public identifiers for a specific auth0 application\n * and are safe to include in client-side code.\n * https://auth0.com/docs/get-started/applications/application-settings\n */\nconst auth0ConfigMap = {\n dev: {\n domain: `https://auth.portal.dev.wandelbots.io`,\n clientId: \"fLbJD0RLp5r2Dpucm5j8BjwMR6Hunfha\",\n },\n stg: {\n domain: `https://auth.portal.stg.wandelbots.io`,\n clientId: \"joVDeD9e786WzFNSGCqoVq7HNkWt5j6s\",\n },\n prod: {\n domain: `https://auth.portal.wandelbots.io`,\n clientId: \"J7WJUi38xVQdJAEBNRT9Xw1b0fXDb4J2\",\n },\n}\n\n/** Determine which Auth0 configuration to use based on instance URL */\nexport const getAuth0Config = (instanceUrl: URL) => {\n if (instanceUrl.host.endsWith(\".dev.wandelbots.io\")) return auth0ConfigMap.dev\n if (instanceUrl.host.endsWith(\".stg.wandelbots.io\")) return auth0ConfigMap.stg\n if (instanceUrl.host.endsWith(\".wandelbots.io\")) return auth0ConfigMap.prod\n throw new Error(\n `Unable to authenticate with NOVA instance \"${instanceUrl}\". Auth0 login is only supported for cloud instances with hosts of the form \"**.wandelbots.io\".`,\n )\n}\n\n/**\n * Initializes Auth0 login process using redirect if necessary and retrieves an access token.\n * Returns null when an access token should not be needed to authenticate (i.e. cookie auth\n * when deployed on the instance domain)\n */\nexport const loginWithAuth0 = async (\n instanceUrl: URL,\n): Promise<string | null> => {\n if (typeof window === \"undefined\") {\n throw new Error(\n `Access token must be set to use Nova when not in a browser environment.`,\n )\n }\n\n if (instanceUrl.origin === window.location.origin) {\n // When deployed on the instance itself, our auth is handled by cookies\n // and no access token is needed-- just need to reload the page and it'll\n // login again / set cookie as needed\n return guardedPageReload(\"cloud_instance_auth\")\n }\n\n // If we're on localhost or another domain, we need to do the full oauth flow\n // Note this will ONLY work for origins which are whitelisted as a redirect_uri\n // in the auth0 config, currently\n const { Auth0Client } = await import(\"@auth0/auth0-spa-js\")\n\n const auth0Config = getAuth0Config(instanceUrl)\n\n const auth0Client = new Auth0Client({\n domain: auth0Config.domain,\n clientId: auth0Config.clientId ?? \"\",\n useRefreshTokens: false,\n authorizationParams: {\n audience: \"nova-api\",\n redirect_uri: window.location.origin,\n },\n })\n\n // If the URL includes a redirect result, handle it\n if (\n window.location.search.includes(\"code=\") &&\n window.location.search.includes(\"state=\")\n ) {\n const { appState } = await auth0Client.handleRedirectCallback()\n // Return to the URL the user was originally on before the redirect\n window.history.replaceState(\n {},\n document.title,\n appState?.returnTo || window.location.pathname,\n )\n } else {\n // Initiate login with redirect\n await auth0Client.loginWithRedirect()\n }\n\n // Once logged in, retrieve the access token silently\n const accessToken = await auth0Client.getTokenSilently()\n return accessToken\n}\n"],"mappings":"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"}