@wandelbots/nova-js 3.3.2 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{LoginWithAuth0-wQB-Sol1.mjs → LoginWithAuth0-DBe9CXOr.mjs} +97 -13
  2. package/dist/LoginWithAuth0-DBe9CXOr.mjs.map +1 -0
  3. package/dist/{LoginWithAuth0-CBD9BXXz.cjs → LoginWithAuth0-iXpPiCcz.cjs} +162 -12
  4. package/dist/LoginWithAuth0-iXpPiCcz.cjs.map +1 -0
  5. package/dist/{NovaClient-qJnHcx2s.d.mts → NovaClient-C0GXOu4w.d.mts} +2 -1
  6. package/dist/{NovaClient-CV7ooIkD.d.cts.map → NovaClient-C0GXOu4w.d.mts.map} +1 -1
  7. package/dist/{NovaClient-B8XM3OPO.mjs → NovaClient-C27dk3Ql.mjs} +12 -64
  8. package/dist/NovaClient-C27dk3Ql.mjs.map +1 -0
  9. package/dist/{NovaClient-CV7ooIkD.d.cts → NovaClient-PNinV5c4.d.cts} +2 -1
  10. package/dist/{NovaClient-qJnHcx2s.d.mts.map → NovaClient-PNinV5c4.d.cts.map} +1 -1
  11. package/dist/{NovaClient-D2EItmiH.cjs → NovaClient-j40sHBBq.cjs} +20 -114
  12. package/dist/NovaClient-j40sHBBq.cjs.map +1 -0
  13. package/dist/index.cjs +14 -10
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +34 -3
  16. package/dist/index.d.cts.map +1 -1
  17. package/dist/index.d.mts +34 -3
  18. package/dist/index.d.mts.map +1 -1
  19. package/dist/index.mjs +3 -3
  20. package/dist/lib/v1/index.cjs +3 -3
  21. package/dist/lib/v1/index.cjs.map +1 -1
  22. package/dist/lib/v1/index.d.cts +1 -1
  23. package/dist/lib/v1/index.d.mts +1 -1
  24. package/dist/lib/v1/index.mjs +2 -2
  25. package/dist/lib/v2/index.cjs +11 -10
  26. package/dist/lib/v2/index.cjs.map +1 -1
  27. package/dist/lib/v2/index.d.cts +1 -0
  28. package/dist/lib/v2/index.d.cts.map +1 -1
  29. package/dist/lib/v2/index.d.mts +1 -0
  30. package/dist/lib/v2/index.d.mts.map +1 -1
  31. package/dist/lib/v2/index.mjs +11 -10
  32. package/dist/lib/v2/index.mjs.map +1 -1
  33. package/package.json +2 -2
  34. package/src/LoginWithAuth0.ts +13 -15
  35. package/src/lib/converters.ts +61 -0
  36. package/src/lib/v1/NovaClient.ts +14 -15
  37. package/src/lib/v2/NovaClient.ts +15 -16
  38. package/dist/LoginWithAuth0-CBD9BXXz.cjs.map +0 -1
  39. package/dist/LoginWithAuth0-wQB-Sol1.mjs.map +0 -1
  40. package/dist/NovaClient-B8XM3OPO.mjs.map +0 -1
  41. package/dist/NovaClient-D2EItmiH.cjs.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wandelbots/nova-js",
3
3
  "type": "module",
4
- "version": "3.3.2",
4
+ "version": "3.3.3",
5
5
  "description": "Official JS client for the Wandelbots API",
6
6
  "sideEffects": false,
7
7
  "packageManager": "pnpm@10.19.0",
@@ -55,7 +55,7 @@
55
55
  "semantic-release": "^24.2.7",
56
56
  "tsdown": "^0.16.0",
57
57
  "typescript": "^5.9.3",
58
- "vitest": "^4.0.6",
58
+ "vitest": "^4.0.8",
59
59
  "ws": "^8.18.1"
60
60
  },
61
61
  "dependencies": {
@@ -1,5 +1,3 @@
1
- const DOMAIN_SUFFIX = "wandelbots.io"
2
-
3
1
  /**
4
2
  * Mapping of stages to Auth0 configurations.
5
3
  * The client ids are public identifiers for a specific auth0 application
@@ -8,26 +6,26 @@ const DOMAIN_SUFFIX = "wandelbots.io"
8
6
  */
9
7
  const auth0ConfigMap = {
10
8
  dev: {
11
- domain: `https://auth.portal.dev.${DOMAIN_SUFFIX}`,
9
+ domain: `https://auth.portal.dev.wandelbots.io`,
12
10
  clientId: "fLbJD0RLp5r2Dpucm5j8BjwMR6Hunfha",
13
11
  },
14
12
  stg: {
15
- domain: `https://auth.portal.stg.${DOMAIN_SUFFIX}`,
13
+ domain: `https://auth.portal.stg.wandelbots.io`,
16
14
  clientId: "joVDeD9e786WzFNSGCqoVq7HNkWt5j6s",
17
15
  },
18
16
  prod: {
19
- domain: `https://auth.portal.${DOMAIN_SUFFIX}`,
17
+ domain: `https://auth.portal.wandelbots.io`,
20
18
  clientId: "J7WJUi38xVQdJAEBNRT9Xw1b0fXDb4J2",
21
19
  },
22
20
  }
23
21
 
24
- /** Determine which Auth0 configuration to use based on instance URL */
25
- const getAuth0Config = (instanceUrl: string) => {
26
- if (instanceUrl.endsWith(`dev.${DOMAIN_SUFFIX}`)) return auth0ConfigMap.dev
27
- if (instanceUrl.endsWith(`stg.${DOMAIN_SUFFIX}`)) return auth0ConfigMap.stg
28
- if (instanceUrl.endsWith(DOMAIN_SUFFIX)) return auth0ConfigMap.prod
22
+ /** Determine which Auth0 configuration to use based on instance URL */
23
+ export const getAuth0Config = (instanceUrl: URL) => {
24
+ if (instanceUrl.host.endsWith(".dev.wandelbots.io")) return auth0ConfigMap.dev
25
+ if (instanceUrl.host.endsWith(".stg.wandelbots.io")) return auth0ConfigMap.stg
26
+ if (instanceUrl.host.endsWith(".wandelbots.io")) return auth0ConfigMap.prod
29
27
  throw new Error(
30
- "Unsupported instance URL. Cannot determine Auth0 configuration.",
28
+ `Unable to authenticate with NOVA instance "${instanceUrl}". Auth0 login is only supported for cloud instances with hosts of the form "**.wandelbots.io".`,
31
29
  )
32
30
  }
33
31
 
@@ -37,7 +35,7 @@ const getAuth0Config = (instanceUrl: string) => {
37
35
  * when deployed on the instance domain)
38
36
  */
39
37
  export const loginWithAuth0 = async (
40
- instanceUrl: string,
38
+ instanceUrl: URL,
41
39
  ): Promise<string | null> => {
42
40
  if (typeof window === "undefined") {
43
41
  throw new Error(
@@ -45,9 +43,7 @@ export const loginWithAuth0 = async (
45
43
  )
46
44
  }
47
45
 
48
- const auth0Config = getAuth0Config(instanceUrl)
49
-
50
- if (new URL(instanceUrl).origin === window.location.origin) {
46
+ if (instanceUrl.origin === window.location.origin) {
51
47
  // When deployed on the instance itself, our auth is handled by cookies
52
48
  // and no access token is needed-- just need to reload the page and it'll
53
49
  // login again / set cookie as needed
@@ -62,6 +58,8 @@ export const loginWithAuth0 = async (
62
58
  // in the auth0 config, currently
63
59
  const { Auth0Client } = await import("@auth0/auth0-spa-js")
64
60
 
61
+ const auth0Config = getAuth0Config(instanceUrl)
62
+
65
63
  const auth0Client = new Auth0Client({
66
64
  domain: auth0Config.domain,
67
65
  clientId: auth0Config.clientId ?? "",
@@ -1,3 +1,64 @@
1
+ export type URLParseOptions = {
2
+ /**
3
+ * Ignore any scheme in the input string and force this scheme instead.
4
+ */
5
+ scheme?: "http" | "https"
6
+ /**
7
+ * If the input string does not include a scheme, use this as the default
8
+ * scheme.
9
+ */
10
+ defaultScheme?: "http" | "https"
11
+ }
12
+
13
+ /**
14
+ * Parse a string as a URL, with options to enforce or default the scheme.
15
+ */
16
+ export function parseUrl(url: string, options: URLParseOptions = {}): URL {
17
+ const { scheme, defaultScheme } = options
18
+
19
+ const schemeRegex = /^[a-zA-Z]+:\/\//
20
+
21
+ if (scheme) {
22
+ // Force the scheme by removing any existing scheme and prepending the desired one
23
+ url = url.replace(schemeRegex, "")
24
+ url = `${scheme}://${url}`
25
+ } else if (defaultScheme && !schemeRegex.test(url)) {
26
+ // No scheme is present, add the default one
27
+ url = `${defaultScheme}://${url}`
28
+ }
29
+
30
+ return new URL(url)
31
+ }
32
+
33
+ /**
34
+ * Attempt to parse a string as a URL; return undefined if we can't
35
+ */
36
+ export function tryParseUrl(
37
+ url: string,
38
+ options: URLParseOptions = {},
39
+ ): URL | undefined {
40
+ try {
41
+ return parseUrl(url, options)
42
+ } catch {
43
+ return undefined
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Permissively parse a NOVA instance URL from a config variable.
49
+ * If scheme is not specified, defaults to https for *.wandelbots.io hosts,
50
+ * and http otherwise.
51
+ * Throws an error if a valid URL could not be determined.
52
+ */
53
+ export function parseNovaInstanceUrl(url: string): URL {
54
+ const testUrl = tryParseUrl(url, { defaultScheme: "http" })
55
+ if (testUrl?.host.endsWith(".wandelbots.io")) {
56
+ return parseUrl(url, { defaultScheme: "https" })
57
+ } else {
58
+ return parseUrl(url, { defaultScheme: "http" })
59
+ }
60
+ }
61
+
1
62
  /** Try to parse something as JSON; return undefined if we can't */
2
63
  // biome-ignore lint/suspicious/noExplicitAny: it's json
3
64
  export function tryParseJson(json: unknown): any {
@@ -10,6 +10,7 @@ import urlJoin from "url-join"
10
10
  import { loginWithAuth0 } from "../../LoginWithAuth0.js"
11
11
  import { AutoReconnectingWebsocket } from "../AutoReconnectingWebsocket.js"
12
12
  import { availableStorage } from "../availableStorage.js"
13
+ import { parseNovaInstanceUrl } from "../converters.js"
13
14
  import { ConnectedMotionGroup } from "./ConnectedMotionGroup.js"
14
15
  import { JoggerConnection } from "./JoggerConnection.js"
15
16
  import { MotionStreamConnection } from "./MotionStreamConnection.js"
@@ -49,14 +50,6 @@ export type NovaClientConfig = {
49
50
 
50
51
  type NovaClientConfigWithDefaults = NovaClientConfig & { cellId: string }
51
52
 
52
- function permissiveInstanceUrlParse(url: string): string {
53
- if (!url.startsWith("http")) {
54
- url = `http://${url}`
55
- }
56
-
57
- return new URL(url).toString()
58
- }
59
-
60
53
  /**
61
54
  * Client for connecting to a Nova instance and controlling robots.
62
55
  * @deprecated The nova v1 client is deprecated. Please use the v2 client from `@wandelbots/nova-js/v2` instead.
@@ -65,6 +58,7 @@ export class NovaClient {
65
58
  readonly api: NovaCellAPIClient
66
59
  readonly config: NovaClientConfigWithDefaults
67
60
  readonly mock?: MockNovaInstance
61
+ readonly instanceUrl: URL
68
62
  authPromise: Promise<string | null> | null = null
69
63
  accessToken: string | null = null
70
64
 
@@ -81,11 +75,8 @@ export class NovaClient {
81
75
 
82
76
  if (this.config.instanceUrl === "https://mock.example.com") {
83
77
  this.mock = new MockNovaInstance()
84
- } else {
85
- this.config.instanceUrl = permissiveInstanceUrlParse(
86
- this.config.instanceUrl,
87
- )
88
78
  }
79
+ this.instanceUrl = parseNovaInstanceUrl(this.config.instanceUrl)
89
80
 
90
81
  // Set up Axios instance with interceptor for token fetching
91
82
  const axiosInstance = axios.create({
@@ -151,7 +142,7 @@ export class NovaClient {
151
142
 
152
143
  this.api = new NovaCellAPIClient(cellId, {
153
144
  ...config,
154
- basePath: urlJoin(this.config.instanceUrl, "/api/v1"),
145
+ basePath: urlJoin(this.instanceUrl.href, "/api/v1"),
155
146
  isJsonMime: (mime: string) => {
156
147
  return mime === "application/json"
157
148
  },
@@ -176,7 +167,15 @@ export class NovaClient {
176
167
  return
177
168
  }
178
169
 
179
- this.authPromise = loginWithAuth0(this.config.instanceUrl)
170
+ const storedToken = availableStorage.getString("wbjs.access_token")
171
+ if (storedToken && this.accessToken !== storedToken) {
172
+ // Might be newer than the one we have
173
+ this.accessToken = storedToken
174
+ return
175
+ }
176
+
177
+ // Otherwise, perform login flow
178
+ this.authPromise = loginWithAuth0(this.instanceUrl)
180
179
  try {
181
180
  this.accessToken = await this.authPromise
182
181
  if (this.accessToken) {
@@ -193,7 +192,7 @@ export class NovaClient {
193
192
  makeWebsocketURL(path: string): string {
194
193
  const url = new URL(
195
194
  urlJoin(
196
- this.config.instanceUrl,
195
+ this.instanceUrl.href,
197
196
  `/api/v1/cells/${this.config.cellId}`,
198
197
  path,
199
198
  ),
@@ -6,6 +6,7 @@ import urlJoin from "url-join"
6
6
  import { loginWithAuth0 } from "../../LoginWithAuth0"
7
7
  import { AutoReconnectingWebsocket } from "../AutoReconnectingWebsocket"
8
8
  import { availableStorage } from "../availableStorage"
9
+ import { parseNovaInstanceUrl } from "../converters"
9
10
  import { MockNovaInstance } from "./mock/MockNovaInstance"
10
11
  import { NovaCellAPIClient } from "./NovaCellAPIClient"
11
12
 
@@ -42,14 +43,6 @@ export type NovaClientConfig = {
42
43
 
43
44
  type NovaClientConfigWithDefaults = NovaClientConfig & { cellId: string }
44
45
 
45
- function permissiveInstanceUrlParse(url: string): string {
46
- if (!url.startsWith("http")) {
47
- url = `http://${url}`
48
- }
49
-
50
- return new URL(url).toString()
51
- }
52
-
53
46
  /**
54
47
  *
55
48
  * Client for connecting to a Nova instance and controlling robots.
@@ -58,6 +51,7 @@ export class NovaClient {
58
51
  readonly api: NovaCellAPIClient
59
52
  readonly config: NovaClientConfigWithDefaults
60
53
  readonly mock?: MockNovaInstance
54
+ readonly instanceUrl: URL
61
55
  authPromise: Promise<string | null> | null = null
62
56
  accessToken: string | null = null
63
57
 
@@ -74,15 +68,12 @@ export class NovaClient {
74
68
 
75
69
  if (this.config.instanceUrl === "https://mock.example.com") {
76
70
  this.mock = new MockNovaInstance()
77
- } else {
78
- this.config.instanceUrl = permissiveInstanceUrlParse(
79
- this.config.instanceUrl,
80
- )
81
71
  }
72
+ this.instanceUrl = parseNovaInstanceUrl(this.config.instanceUrl)
82
73
 
83
74
  // Set up Axios instance with interceptor for token fetching
84
75
  const axiosInstance = axios.create({
85
- baseURL: urlJoin(this.config.instanceUrl, "/api/v2"),
76
+ baseURL: urlJoin(this.instanceUrl.href, "/api/v2"),
86
77
  // TODO - backend needs to set proper CORS headers for this
87
78
  headers:
88
79
  typeof window !== "undefined" &&
@@ -144,7 +135,7 @@ export class NovaClient {
144
135
 
145
136
  this.api = new NovaCellAPIClient(cellId, {
146
137
  ...config,
147
- basePath: urlJoin(this.config.instanceUrl, "/api/v2"),
138
+ basePath: urlJoin(this.instanceUrl.href, "/api/v2"),
148
139
  isJsonMime: (mime: string) => {
149
140
  return mime === "application/json"
150
141
  },
@@ -168,7 +159,15 @@ export class NovaClient {
168
159
  return
169
160
  }
170
161
 
171
- this.authPromise = loginWithAuth0(this.config.instanceUrl)
162
+ const storedToken = availableStorage.getString("wbjs.access_token")
163
+ if (storedToken && this.accessToken !== storedToken) {
164
+ // Might be newer than the one we have
165
+ this.accessToken = storedToken
166
+ return
167
+ }
168
+
169
+ // Otherwise, perform login flow
170
+ this.authPromise = loginWithAuth0(this.instanceUrl)
172
171
  try {
173
172
  this.accessToken = await this.authPromise
174
173
  if (this.accessToken) {
@@ -185,7 +184,7 @@ export class NovaClient {
185
184
  makeWebsocketURL(path: string): string {
186
185
  const url = new URL(
187
186
  urlJoin(
188
- this.config.instanceUrl,
187
+ this.instanceUrl.href,
189
188
  `/api/v2/cells/${this.config.cellId}`,
190
189
  path,
191
190
  ),
@@ -1 +0,0 @@
1
- {"version":3,"file":"LoginWithAuth0-CBD9BXXz.cjs","names":["ReconnectingWebSocket","opts: {\n mock?: v1.MockNovaInstance | v2.MockNovaInstance\n onDispose?: () => void\n }"],"sources":["../src/lib/AutoReconnectingWebsocket.ts","../src/lib/availableStorage.ts","../src/LoginWithAuth0.ts"],"sourcesContent":["import ReconnectingWebSocket, { type ErrorEvent } from \"reconnecting-websocket\"\nimport type * as v1 from \"./v1/mock/MockNovaInstance\"\nimport type * as v2 from \"./v2/mock/MockNovaInstance\"\n\nexport class AutoReconnectingWebsocket extends ReconnectingWebSocket {\n receivedFirstMessage?: MessageEvent\n targetUrl: string\n disposed = false\n\n constructor(\n targetUrl: string,\n readonly opts: {\n mock?: v1.MockNovaInstance | v2.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\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","const DOMAIN_SUFFIX = \"wandelbots.io\"\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.${DOMAIN_SUFFIX}`,\n clientId: \"fLbJD0RLp5r2Dpucm5j8BjwMR6Hunfha\",\n },\n stg: {\n domain: `https://auth.portal.stg.${DOMAIN_SUFFIX}`,\n clientId: \"joVDeD9e786WzFNSGCqoVq7HNkWt5j6s\",\n },\n prod: {\n domain: `https://auth.portal.${DOMAIN_SUFFIX}`,\n clientId: \"J7WJUi38xVQdJAEBNRT9Xw1b0fXDb4J2\",\n },\n}\n\n/** Determine which Auth0 configuration to use based on instance URL */\nconst getAuth0Config = (instanceUrl: string) => {\n if (instanceUrl.endsWith(`dev.${DOMAIN_SUFFIX}`)) return auth0ConfigMap.dev\n if (instanceUrl.endsWith(`stg.${DOMAIN_SUFFIX}`)) return auth0ConfigMap.stg\n if (instanceUrl.endsWith(DOMAIN_SUFFIX)) return auth0ConfigMap.prod\n throw new Error(\n \"Unsupported instance URL. Cannot determine Auth0 configuration.\",\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: string,\n): Promise<string | null> => {\n if (typeof window === \"undefined\") {\n throw new Error(\n `Access token must be set to use NovaClient when not in a browser environment.`,\n )\n }\n\n const auth0Config = getAuth0Config(instanceUrl)\n\n if (new URL(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 window.location.reload()\n throw new Error(\n \"Failed to reload page to get auth details, please refresh manually\",\n )\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 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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"LoginWithAuth0-wQB-Sol1.mjs","names":["opts: {\n mock?: v1.MockNovaInstance | v2.MockNovaInstance\n onDispose?: () => void\n }"],"sources":["../src/lib/AutoReconnectingWebsocket.ts","../src/lib/availableStorage.ts","../src/LoginWithAuth0.ts"],"sourcesContent":["import ReconnectingWebSocket, { type ErrorEvent } from \"reconnecting-websocket\"\nimport type * as v1 from \"./v1/mock/MockNovaInstance\"\nimport type * as v2 from \"./v2/mock/MockNovaInstance\"\n\nexport class AutoReconnectingWebsocket extends ReconnectingWebSocket {\n receivedFirstMessage?: MessageEvent\n targetUrl: string\n disposed = false\n\n constructor(\n targetUrl: string,\n readonly opts: {\n mock?: v1.MockNovaInstance | v2.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\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","const DOMAIN_SUFFIX = \"wandelbots.io\"\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.${DOMAIN_SUFFIX}`,\n clientId: \"fLbJD0RLp5r2Dpucm5j8BjwMR6Hunfha\",\n },\n stg: {\n domain: `https://auth.portal.stg.${DOMAIN_SUFFIX}`,\n clientId: \"joVDeD9e786WzFNSGCqoVq7HNkWt5j6s\",\n },\n prod: {\n domain: `https://auth.portal.${DOMAIN_SUFFIX}`,\n clientId: \"J7WJUi38xVQdJAEBNRT9Xw1b0fXDb4J2\",\n },\n}\n\n/** Determine which Auth0 configuration to use based on instance URL */\nconst getAuth0Config = (instanceUrl: string) => {\n if (instanceUrl.endsWith(`dev.${DOMAIN_SUFFIX}`)) return auth0ConfigMap.dev\n if (instanceUrl.endsWith(`stg.${DOMAIN_SUFFIX}`)) return auth0ConfigMap.stg\n if (instanceUrl.endsWith(DOMAIN_SUFFIX)) return auth0ConfigMap.prod\n throw new Error(\n \"Unsupported instance URL. Cannot determine Auth0 configuration.\",\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: string,\n): Promise<string | null> => {\n if (typeof window === \"undefined\") {\n throw new Error(\n `Access token must be set to use NovaClient when not in a browser environment.`,\n )\n }\n\n const auth0Config = getAuth0Config(instanceUrl)\n\n if (new URL(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 window.location.reload()\n throw new Error(\n \"Failed to reload page to get auth details, please refresh manually\",\n )\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 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":";;;AAIA,IAAa,4BAAb,cAA+C,sBAAsB;CAKnE,YACE,WACA,AAASA,OAGL,EAAE,EACN;AACA,UAAQ,IAAI,wBAAwB,UAAU;AAE9C,cAAY,KAAK,aAAa,WAAW,QAAW,EAClD,aAAa,MACd,CAAC;EATO;kBAJA;AAgBT,SAAO,eAAe,MAAM,OAAO,EACjC,MAAM;AACJ,UAAO,KAAK;KAEf,CAAC;AAEF,OAAK,YAAY;AAEjB,OAAK,iBAAiB,cAAc;AAClC,WAAQ,IAAI,gBAAgB,KAAK,IAAI,SAAS;IAC9C;AAEF,OAAK,iBAAiB,YAAY,OAAO;AACvC,OAAI,CAAC,KAAK,qBACR,MAAK,uBAAuB;IAE9B;AAEF,OAAK,iBAAiB,eAAe;AACnC,WAAQ,IAAI,gBAAgB,KAAK,IAAI,SAAS;IAC9C;EAEF,MAAM,gBAAgB,KAAK;AAC3B,OAAK,kBAAkB;AACrB,OAAI,KAAK,KAAK,KACZ,MAAK,KAAK,KAAK,0BAA0B,KAAK;OAE9C,eAAc,MAAM,KAAK;;AAI7B,OAAK,WAAW;;CAGlB,UAAU,WAAmB;AAC3B,OAAK,uBAAuB;AAC5B,OAAK,YAAY;AACjB,OAAK,WAAW;;CAGlB,SAAS,MAAe;AACtB,MAAI,KAAK,KAAK,KACZ,MAAK,KAAK,KAAK,uBAAuB,MAAM,KAAK,UAAU,KAAK,CAAC;MAEjE,MAAK,KAAK,KAAK,UAAU,KAAK,CAAC;;;;;;CAQnC,UAAU;AACR,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,MAAI,KAAK,KAAK,UACZ,MAAK,KAAK,WAAW;;;;;CAOzB,MAAM,SAAS;AACb,SAAO,IAAI,SAAe,SAAS,WAAW;AAC5C,OAAI,KAAK,eAAe,UAAU,KAChC,UAAS;QACJ;AACL,SAAK,iBAAiB,cAAc,SAAS,CAAC;AAC9C,SAAK,iBAAiB,SAAS,OAAO;;IAExC;;;;;CAMJ,MAAM,SAAS;AACb,SAAO,IAAI,SAAe,SAAS,WAAW;AAC5C,OAAI,KAAK,eAAe,UAAU,OAChC,UAAS;QACJ;AACL,SAAK,iBAAiB,eAAe,SAAS,CAAC;AAC/C,SAAK,iBAAiB,SAAS,OAAO;;IAExC;;;;;;;CAQJ,MAAM,eAAe;AACnB,MAAI,KAAK,qBACP,QAAO,KAAK;AAGd,SAAO,IAAI,SAAuB,SAAS,WAAW;GACpD,MAAM,aAAa,OAAqB;AACtC,SAAK,uBAAuB;AAC5B,SAAK,oBAAoB,WAAW,UAAU;AAC9C,SAAK,oBAAoB,SAAS,QAAQ;AAC1C,YAAQ,GAAG;;GAGb,MAAM,WAAW,OAAmB;AAClC,SAAK,oBAAoB,WAAW,UAAU;AAC9C,SAAK,oBAAoB,SAAS,QAAQ;AAC1C,WAAO,GAAG;;AAGZ,QAAK,iBAAiB,WAAW,UAAU;AAC3C,QAAK,iBAAiB,SAAS,QAAQ;IACvC;;;;;;CAOJ,MAAM,cAAc;AAClB,SAAO,IAAI,SAAuB,SAAS,WAAW;GACpD,MAAM,aAAa,OAAqB;AACtC,SAAK,oBAAoB,WAAW,UAAU;AAC9C,SAAK,oBAAoB,SAAS,QAAQ;AAC1C,YAAQ,GAAG;;GAGb,MAAM,WAAW,OAAmB;AAClC,SAAK,oBAAoB,WAAW,UAAU;AAC9C,SAAK,oBAAoB,SAAS,QAAQ;AAC1C,WAAO,GAAG;;AAGZ,QAAK,iBAAiB,WAAW,UAAU;AAC3C,QAAK,iBAAiB,SAAS,QAAQ;IACvC;;;;;;;;;;AC5JN,IAAM,mBAAN,MAAuB;;mBACT,OAAO,WAAW,eAAe,CAAC,CAAC,OAAO;;CAEtD,QAAW,KAAgC;AACzC,MAAI,CAAC,KAAK,UAAW,QAAO;EAE5B,MAAM,SAAS,OAAO,aAAa,QAAQ,IAAI;AAC/C,MAAI,WAAW,KAAM,QAAO;AAE5B,MAAI;AACF,UAAO,KAAK,MAAM,OAAO;WAClB,KAAK;AACZ,UAAO;;;CAIX,QAAQ,KAAa,KAAc;AACjC,MAAI,CAAC,KAAK,UAAW,QAAO;AAE5B,SAAO,aAAa,QAAQ,KAAK,KAAK,UAAU,IAAI,CAAC;;CAGvD,OAAO,KAAa;AAClB,MAAI,CAAC,KAAK,UAAW,QAAO;AAE5B,SAAO,aAAa,WAAW,IAAI;;CAGrC,UAAU,KAAa,OAAe;AACpC,MAAI,CAAC,KAAK,UAAW,QAAO;AAE5B,SAAO,aAAa,QAAQ,KAAK,MAAM;;CAGzC,UAAU,KAA4B;AACpC,MAAI,CAAC,KAAK,UAAW,QAAO;AAE5B,SAAO,OAAO,aAAa,QAAQ,IAAI;;;AAI3C,MAAa,mBAAmB,IAAI,kBAAkB;;;;AC7CtD,MAAM,gBAAgB;;;;;;;AAQtB,MAAM,iBAAiB;CACrB,KAAK;EACH,QAAQ,2BAA2B;EACnC,UAAU;EACX;CACD,KAAK;EACH,QAAQ,2BAA2B;EACnC,UAAU;EACX;CACD,MAAM;EACJ,QAAQ,uBAAuB;EAC/B,UAAU;EACX;CACF;;AAGD,MAAM,kBAAkB,gBAAwB;AAC9C,KAAI,YAAY,SAAS,OAAO,gBAAgB,CAAE,QAAO,eAAe;AACxE,KAAI,YAAY,SAAS,OAAO,gBAAgB,CAAE,QAAO,eAAe;AACxE,KAAI,YAAY,SAAS,cAAc,CAAE,QAAO,eAAe;AAC/D,OAAM,IAAI,MACR,kEACD;;;;;;;AAQH,MAAa,iBAAiB,OAC5B,gBAC2B;AAC3B,KAAI,OAAO,WAAW,YACpB,OAAM,IAAI,MACR,gFACD;CAGH,MAAM,cAAc,eAAe,YAAY;AAE/C,KAAI,IAAI,IAAI,YAAY,CAAC,WAAW,OAAO,SAAS,QAAQ;AAI1D,SAAO,SAAS,QAAQ;AACxB,QAAM,IAAI,MACR,qEACD;;CAMH,MAAM,EAAE,gBAAgB,MAAM,OAAO;CAErC,MAAM,cAAc,IAAI,YAAY;EAClC,QAAQ,YAAY;EACpB,UAAU,YAAY,YAAY;EAClC,kBAAkB;EAClB,qBAAqB;GACnB,UAAU;GACV,cAAc,OAAO,SAAS;GAC/B;EACF,CAAC;AAGF,KACE,OAAO,SAAS,OAAO,SAAS,QAAQ,IACxC,OAAO,SAAS,OAAO,SAAS,SAAS,EACzC;EACA,MAAM,EAAE,aAAa,MAAM,YAAY,wBAAwB;AAE/D,SAAO,QAAQ,aACb,EAAE,EACF,SAAS,OACT,UAAU,YAAY,OAAO,SAAS,SACvC;OAGD,OAAM,YAAY,mBAAmB;AAKvC,QADoB,MAAM,YAAY,kBAAkB"}