@wandelbots/nova-js 3.13.0-pr.307.dd54f61 → 3.13.0-pr.307.ea0533d

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 (45) hide show
  1. package/dist/{context-ffr5Q2dc.mjs → context-CmXqEEwW.mjs} +86 -3
  2. package/dist/context-CmXqEEwW.mjs.map +1 -0
  3. package/dist/{context-BZELA2eh.cjs → context-Cu5mXcFZ.cjs} +146 -3
  4. package/dist/context-Cu5mXcFZ.cjs.map +1 -0
  5. package/dist/experimental/nats/index.cjs +15 -84
  6. package/dist/experimental/nats/index.cjs.map +1 -1
  7. package/dist/experimental/nats/index.d.cts +10 -95
  8. package/dist/experimental/nats/index.d.cts.map +1 -1
  9. package/dist/experimental/nats/index.d.mts +10 -95
  10. package/dist/experimental/nats/index.d.mts.map +1 -1
  11. package/dist/experimental/nats/index.mjs +16 -84
  12. package/dist/experimental/nats/index.mjs.map +1 -1
  13. package/dist/index.cjs +12 -13
  14. package/dist/index.mjs +2 -3
  15. package/dist/v1/index.cjs +3 -3
  16. package/dist/v1/index.cjs.map +1 -1
  17. package/dist/v1/index.mjs +2 -2
  18. package/dist/v2/index.cjs +3 -4
  19. package/dist/v2/index.cjs.map +1 -1
  20. package/dist/v2/index.d.cts +83 -5
  21. package/dist/v2/index.d.cts.map +1 -1
  22. package/dist/v2/index.d.mts +82 -4
  23. package/dist/v2/index.d.mts.map +1 -1
  24. package/dist/v2/index.mjs +1 -2
  25. package/dist/v2/index.mjs.map +1 -1
  26. package/dist/{wandelscriptUtils-DcY1aLWu.cjs → wandelscriptUtils-BdqeVDCY.cjs} +12 -13
  27. package/dist/{wandelscriptUtils-DcY1aLWu.cjs.map → wandelscriptUtils-BdqeVDCY.cjs.map} +1 -1
  28. package/dist/{wandelscriptUtils-CpUXdLVc.mjs → wandelscriptUtils-CKhiZJsB.mjs} +2 -3
  29. package/dist/{wandelscriptUtils-CpUXdLVc.mjs.map → wandelscriptUtils-CKhiZJsB.mjs.map} +1 -1
  30. package/package.json +1 -1
  31. package/src/experimental/nats/index.ts +0 -3
  32. package/src/lib/experimental/nats/NovaNatsClient.ts +13 -67
  33. package/src/lib/experimental/nats/buildSubject.ts +5 -50
  34. package/src/lib/experimental/nats/generated/operations.ts +4 -64
  35. package/dist/Nova--8gozkLR.d.mts +0 -85
  36. package/dist/Nova--8gozkLR.d.mts.map +0 -1
  37. package/dist/Nova-B1o5iTQ6.d.cts +0 -85
  38. package/dist/Nova-B1o5iTQ6.d.cts.map +0 -1
  39. package/dist/context-BZELA2eh.cjs.map +0 -1
  40. package/dist/context-ffr5Q2dc.mjs.map +0 -1
  41. package/dist/converters-DnG1fX23.mjs +0 -87
  42. package/dist/converters-DnG1fX23.mjs.map +0 -1
  43. package/dist/converters-EYS27XJE.cjs +0 -146
  44. package/dist/converters-EYS27XJE.cjs.map +0 -1
  45. package/src/lib/experimental/nats/buildNatsServerUrl.ts +0 -15
@@ -1,4 +1,3 @@
1
- import { u as tryStringifyJson } from "./converters-DnG1fX23.mjs";
2
1
  import ReconnectingWebSocket from "reconnecting-websocket";
3
2
  import { isAxiosError } from "axios";
4
3
  //#region src/lib/AutoReconnectingWebsocket.ts
@@ -154,6 +153,90 @@ var AvailableStorage = class {
154
153
  };
155
154
  const availableStorage = new AvailableStorage();
156
155
  //#endregion
156
+ //#region src/lib/converters.ts
157
+ /**
158
+ * Parse a string as a URL, with options to enforce or default the scheme.
159
+ */
160
+ function parseUrl(url, options = {}) {
161
+ const { scheme, defaultScheme } = options;
162
+ const schemeRegex = /^[a-zA-Z]+:\/\//;
163
+ if (scheme) {
164
+ url = url.replace(schemeRegex, "");
165
+ url = `${scheme}://${url}`;
166
+ } else if (defaultScheme && !schemeRegex.test(url)) url = `${defaultScheme}://${url}`;
167
+ return new URL(url);
168
+ }
169
+ /**
170
+ * Attempt to parse a string as a URL; return undefined if we can't
171
+ */
172
+ function tryParseUrl(url, options = {}) {
173
+ try {
174
+ return parseUrl(url, options);
175
+ } catch {
176
+ return;
177
+ }
178
+ }
179
+ /**
180
+ * Permissively parse a NOVA instance URL from a config variable.
181
+ * If scheme is not specified, defaults to https for *.wandelbots.io hosts,
182
+ * and http otherwise.
183
+ * Throws an error if a valid URL could not be determined.
184
+ */
185
+ function parseNovaInstanceUrl(url) {
186
+ if (tryParseUrl(url, { defaultScheme: "http" })?.host.endsWith(".wandelbots.io")) return parseUrl(url, { defaultScheme: "https" });
187
+ else return parseUrl(url, { defaultScheme: "http" });
188
+ }
189
+ /** Try to parse something as JSON; return undefined if we can't */
190
+ function tryParseJson(json) {
191
+ try {
192
+ return JSON.parse(json);
193
+ } catch {
194
+ return;
195
+ }
196
+ }
197
+ /** Try to turn something into JSON; return undefined if we can't */
198
+ function tryStringifyJson(json) {
199
+ try {
200
+ return JSON.stringify(json);
201
+ } catch {
202
+ return;
203
+ }
204
+ }
205
+ /**
206
+ * Converts object parameters to query string.
207
+ * e.g. { a: "1", b: "2" } => "?a=1&b=2"
208
+ * {} => ""
209
+ */
210
+ function makeUrlQueryString(obj) {
211
+ const str = new URLSearchParams(obj).toString();
212
+ return str ? `?${str}` : "";
213
+ }
214
+ /** Convert radians to degrees */
215
+ function radiansToDegrees(radians) {
216
+ return radians * (180 / Math.PI);
217
+ }
218
+ /** Convert degrees to radians */
219
+ function degreesToRadians(degrees) {
220
+ return degrees * (Math.PI / 180);
221
+ }
222
+ /**
223
+ * Check for coordinate system id equivalence, accounting for the "world" default
224
+ * on empty/undefined values.
225
+ */
226
+ function isSameCoordinateSystem(firstCoordSystem, secondCoordSystem) {
227
+ if (!firstCoordSystem) firstCoordSystem = "world";
228
+ if (!secondCoordSystem) secondCoordSystem = "world";
229
+ return firstCoordSystem === secondCoordSystem;
230
+ }
231
+ /**
232
+ * Helpful const for converting {x, y, z} to [x, y, z] and vice versa
233
+ */
234
+ const XYZ_TO_VECTOR = {
235
+ x: 0,
236
+ y: 1,
237
+ z: 2
238
+ };
239
+ //#endregion
157
240
  //#region src/lib/errorHandling.ts
158
241
  function delay(ms) {
159
242
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -255,6 +338,6 @@ const loginWithAuth0 = async (instanceUrl) => {
255
338
  const isBrowser = typeof window !== "undefined";
256
339
  const isLocalhostDev = isBrowser && window.location.hostname === "localhost" && process.env.NODE_ENV === "development";
257
340
  //#endregion
258
- export { delay as a, makeShortErrorMessage as c, loginWithAuth0 as i, availableStorage as l, isLocalhostDev as n, guardedPageReload as o, getAuth0Config as r, makeErrorMessage as s, isBrowser as t, AutoReconnectingWebsocket as u };
341
+ export { tryParseUrl as _, delay as a, AutoReconnectingWebsocket as b, makeShortErrorMessage as c, isSameCoordinateSystem as d, makeUrlQueryString as f, tryParseJson as g, radiansToDegrees as h, loginWithAuth0 as i, XYZ_TO_VECTOR as l, parseUrl as m, isLocalhostDev as n, guardedPageReload as o, parseNovaInstanceUrl as p, getAuth0Config as r, makeErrorMessage as s, isBrowser as t, degreesToRadians as u, tryStringifyJson as v, availableStorage as y };
259
342
 
260
- //# sourceMappingURL=context-ffr5Q2dc.mjs.map
343
+ //# sourceMappingURL=context-CmXqEEwW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-CmXqEEwW.mjs","names":[],"sources":["../src/lib/AutoReconnectingWebsocket.ts","../src/lib/availableStorage.ts","../src/lib/converters.ts","../src/lib/errorHandling.ts","../src/lib/LoginWithAuth0.ts","../src/lib/context.ts"],"sourcesContent":["import ReconnectingWebSocket, { type ErrorEvent } from \"reconnecting-websocket\"\nimport type * as v1 from \"./deprecated/v1/mock/MockNovaInstance.ts\"\nimport type * as v2 from \"./mock/MockNovaInstance.ts\"\n\nexport class AutoReconnectingWebsocket extends ReconnectingWebSocket {\n receivedFirstMessage?: MessageEvent\n targetUrl: string\n disposed = false\n readonly opts: {\n mock?: v1.MockNovaInstance | v2.MockNovaInstance\n onDispose?: () => void\n }\n\n constructor(\n targetUrl: string,\n 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 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","export type URLParseOptions = {\n /**\n * Ignore any scheme in the input string and force this scheme instead.\n */\n scheme?: \"http\" | \"https\"\n /**\n * If the input string does not include a scheme, use this as the default\n * scheme.\n */\n defaultScheme?: \"http\" | \"https\"\n}\n\n/**\n * Parse a string as a URL, with options to enforce or default the scheme.\n */\nexport function parseUrl(url: string, options: URLParseOptions = {}): URL {\n const { scheme, defaultScheme } = options\n\n const schemeRegex = /^[a-zA-Z]+:\\/\\//\n\n if (scheme) {\n // Force the scheme by removing any existing scheme and prepending the desired one\n url = url.replace(schemeRegex, \"\")\n url = `${scheme}://${url}`\n } else if (defaultScheme && !schemeRegex.test(url)) {\n // No scheme is present, add the default one\n url = `${defaultScheme}://${url}`\n }\n\n return new URL(url)\n}\n\n/**\n * Attempt to parse a string as a URL; return undefined if we can't\n */\nexport function tryParseUrl(\n url: string,\n options: URLParseOptions = {},\n): URL | undefined {\n try {\n return parseUrl(url, options)\n } catch {\n return undefined\n }\n}\n\n/**\n * Permissively parse a NOVA instance URL from a config variable.\n * If scheme is not specified, defaults to https for *.wandelbots.io hosts,\n * and http otherwise.\n * Throws an error if a valid URL could not be determined.\n */\nexport function parseNovaInstanceUrl(url: string): URL {\n const testUrl = tryParseUrl(url, { defaultScheme: \"http\" })\n if (testUrl?.host.endsWith(\".wandelbots.io\")) {\n return parseUrl(url, { defaultScheme: \"https\" })\n } else {\n return parseUrl(url, { defaultScheme: \"http\" })\n }\n}\n\n/** Try to parse something as JSON; return undefined if we can't */\n// biome-ignore lint/suspicious/noExplicitAny: it's json\nexport function tryParseJson(json: unknown): any {\n try {\n return JSON.parse(json as string)\n } catch {\n return undefined\n }\n}\n\n/** Try to turn something into JSON; return undefined if we can't */\nexport function tryStringifyJson(json: unknown): string | undefined {\n try {\n return JSON.stringify(json)\n } catch {\n return undefined\n }\n}\n\n/**\n * Converts object parameters to query string.\n * e.g. { a: \"1\", b: \"2\" } => \"?a=1&b=2\"\n * {} => \"\"\n */\nexport function makeUrlQueryString(obj: Record<string, string>): string {\n const str = new URLSearchParams(obj).toString()\n return str ? `?${str}` : \"\"\n}\n\n/** Convert radians to degrees */\nexport function radiansToDegrees(radians: number): number {\n return radians * (180 / Math.PI)\n}\n\n/** Convert degrees to radians */\nexport function degreesToRadians(degrees: number): number {\n return degrees * (Math.PI / 180)\n}\n\n/**\n * Check for coordinate system id equivalence, accounting for the \"world\" default\n * on empty/undefined values.\n */\nexport function isSameCoordinateSystem(\n firstCoordSystem: string | undefined,\n secondCoordSystem: string | undefined,\n) {\n if (!firstCoordSystem) firstCoordSystem = \"world\"\n if (!secondCoordSystem) secondCoordSystem = \"world\"\n\n return firstCoordSystem === secondCoordSystem\n}\n\n/**\n * Helpful const for converting {x, y, z} to [x, y, z] and vice versa\n */\nexport const XYZ_TO_VECTOR = { x: 0, y: 1, z: 2 }\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 * @deprecated Use makeErrorMessage instead and truncate the error for display as needed, or make a situation-specific localized error message based on a response code\n */\nexport function makeShortErrorMessage(err: unknown) {\n return makeErrorMessage(err)\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 NovaClient 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","// Some general checks about the context the nova-js code is running in\n\nexport const isBrowser = typeof window !== \"undefined\"\n\nexport const isLocalhostDev =\n isBrowser &&\n window.location.hostname === \"localhost\" &&\n process.env.NODE_ENV === \"development\"\n"],"mappings":"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"}
@@ -20,7 +20,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
20
20
  enumerable: true
21
21
  }) : target, mod));
22
22
  //#endregion
23
- const require_converters = require("./converters-EYS27XJE.cjs");
24
23
  let reconnecting_websocket = require("reconnecting-websocket");
25
24
  reconnecting_websocket = __toESM(reconnecting_websocket, 1);
26
25
  let axios = require("axios");
@@ -177,6 +176,90 @@ var AvailableStorage = class {
177
176
  };
178
177
  const availableStorage = new AvailableStorage();
179
178
  //#endregion
179
+ //#region src/lib/converters.ts
180
+ /**
181
+ * Parse a string as a URL, with options to enforce or default the scheme.
182
+ */
183
+ function parseUrl(url, options = {}) {
184
+ const { scheme, defaultScheme } = options;
185
+ const schemeRegex = /^[a-zA-Z]+:\/\//;
186
+ if (scheme) {
187
+ url = url.replace(schemeRegex, "");
188
+ url = `${scheme}://${url}`;
189
+ } else if (defaultScheme && !schemeRegex.test(url)) url = `${defaultScheme}://${url}`;
190
+ return new URL(url);
191
+ }
192
+ /**
193
+ * Attempt to parse a string as a URL; return undefined if we can't
194
+ */
195
+ function tryParseUrl(url, options = {}) {
196
+ try {
197
+ return parseUrl(url, options);
198
+ } catch {
199
+ return;
200
+ }
201
+ }
202
+ /**
203
+ * Permissively parse a NOVA instance URL from a config variable.
204
+ * If scheme is not specified, defaults to https for *.wandelbots.io hosts,
205
+ * and http otherwise.
206
+ * Throws an error if a valid URL could not be determined.
207
+ */
208
+ function parseNovaInstanceUrl(url) {
209
+ if (tryParseUrl(url, { defaultScheme: "http" })?.host.endsWith(".wandelbots.io")) return parseUrl(url, { defaultScheme: "https" });
210
+ else return parseUrl(url, { defaultScheme: "http" });
211
+ }
212
+ /** Try to parse something as JSON; return undefined if we can't */
213
+ function tryParseJson(json) {
214
+ try {
215
+ return JSON.parse(json);
216
+ } catch {
217
+ return;
218
+ }
219
+ }
220
+ /** Try to turn something into JSON; return undefined if we can't */
221
+ function tryStringifyJson(json) {
222
+ try {
223
+ return JSON.stringify(json);
224
+ } catch {
225
+ return;
226
+ }
227
+ }
228
+ /**
229
+ * Converts object parameters to query string.
230
+ * e.g. { a: "1", b: "2" } => "?a=1&b=2"
231
+ * {} => ""
232
+ */
233
+ function makeUrlQueryString(obj) {
234
+ const str = new URLSearchParams(obj).toString();
235
+ return str ? `?${str}` : "";
236
+ }
237
+ /** Convert radians to degrees */
238
+ function radiansToDegrees(radians) {
239
+ return radians * (180 / Math.PI);
240
+ }
241
+ /** Convert degrees to radians */
242
+ function degreesToRadians(degrees) {
243
+ return degrees * (Math.PI / 180);
244
+ }
245
+ /**
246
+ * Check for coordinate system id equivalence, accounting for the "world" default
247
+ * on empty/undefined values.
248
+ */
249
+ function isSameCoordinateSystem(firstCoordSystem, secondCoordSystem) {
250
+ if (!firstCoordSystem) firstCoordSystem = "world";
251
+ if (!secondCoordSystem) secondCoordSystem = "world";
252
+ return firstCoordSystem === secondCoordSystem;
253
+ }
254
+ /**
255
+ * Helpful const for converting {x, y, z} to [x, y, z] and vice versa
256
+ */
257
+ const XYZ_TO_VECTOR = {
258
+ x: 0,
259
+ y: 1,
260
+ z: 2
261
+ };
262
+ //#endregion
180
263
  //#region src/lib/errorHandling.ts
181
264
  function delay(ms) {
182
265
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -202,7 +285,7 @@ function makeErrorMessage(err) {
202
285
  else return `${err.message} from ${err.config.method?.toUpperCase() || "accessing"} ${err.config.url}`;
203
286
  } else if (err instanceof Error) return err.message;
204
287
  else if (typeof err === "string") return err;
205
- else if (typeof err === "object") return require_converters.tryStringifyJson(err) || `Unserializable object ${err}`;
288
+ else if (typeof err === "object") return tryStringifyJson(err) || `Unserializable object ${err}`;
206
289
  return `${err}`;
207
290
  }
208
291
  /**
@@ -284,6 +367,12 @@ Object.defineProperty(exports, "AutoReconnectingWebsocket", {
284
367
  return AutoReconnectingWebsocket;
285
368
  }
286
369
  });
370
+ Object.defineProperty(exports, "XYZ_TO_VECTOR", {
371
+ enumerable: true,
372
+ get: function() {
373
+ return XYZ_TO_VECTOR;
374
+ }
375
+ });
287
376
  Object.defineProperty(exports, "__toESM", {
288
377
  enumerable: true,
289
378
  get: function() {
@@ -296,6 +385,12 @@ Object.defineProperty(exports, "availableStorage", {
296
385
  return availableStorage;
297
386
  }
298
387
  });
388
+ Object.defineProperty(exports, "degreesToRadians", {
389
+ enumerable: true,
390
+ get: function() {
391
+ return degreesToRadians;
392
+ }
393
+ });
299
394
  Object.defineProperty(exports, "delay", {
300
395
  enumerable: true,
301
396
  get: function() {
@@ -326,6 +421,12 @@ Object.defineProperty(exports, "isLocalhostDev", {
326
421
  return isLocalhostDev;
327
422
  }
328
423
  });
424
+ Object.defineProperty(exports, "isSameCoordinateSystem", {
425
+ enumerable: true,
426
+ get: function() {
427
+ return isSameCoordinateSystem;
428
+ }
429
+ });
329
430
  Object.defineProperty(exports, "loginWithAuth0", {
330
431
  enumerable: true,
331
432
  get: function() {
@@ -344,5 +445,47 @@ Object.defineProperty(exports, "makeShortErrorMessage", {
344
445
  return makeShortErrorMessage;
345
446
  }
346
447
  });
448
+ Object.defineProperty(exports, "makeUrlQueryString", {
449
+ enumerable: true,
450
+ get: function() {
451
+ return makeUrlQueryString;
452
+ }
453
+ });
454
+ Object.defineProperty(exports, "parseNovaInstanceUrl", {
455
+ enumerable: true,
456
+ get: function() {
457
+ return parseNovaInstanceUrl;
458
+ }
459
+ });
460
+ Object.defineProperty(exports, "parseUrl", {
461
+ enumerable: true,
462
+ get: function() {
463
+ return parseUrl;
464
+ }
465
+ });
466
+ Object.defineProperty(exports, "radiansToDegrees", {
467
+ enumerable: true,
468
+ get: function() {
469
+ return radiansToDegrees;
470
+ }
471
+ });
472
+ Object.defineProperty(exports, "tryParseJson", {
473
+ enumerable: true,
474
+ get: function() {
475
+ return tryParseJson;
476
+ }
477
+ });
478
+ Object.defineProperty(exports, "tryParseUrl", {
479
+ enumerable: true,
480
+ get: function() {
481
+ return tryParseUrl;
482
+ }
483
+ });
484
+ Object.defineProperty(exports, "tryStringifyJson", {
485
+ enumerable: true,
486
+ get: function() {
487
+ return tryStringifyJson;
488
+ }
489
+ });
347
490
 
348
- //# sourceMappingURL=context-BZELA2eh.cjs.map
491
+ //# sourceMappingURL=context-Cu5mXcFZ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-Cu5mXcFZ.cjs","names":["ReconnectingWebSocket"],"sources":["../src/lib/AutoReconnectingWebsocket.ts","../src/lib/availableStorage.ts","../src/lib/converters.ts","../src/lib/errorHandling.ts","../src/lib/LoginWithAuth0.ts","../src/lib/context.ts"],"sourcesContent":["import ReconnectingWebSocket, { type ErrorEvent } from \"reconnecting-websocket\"\nimport type * as v1 from \"./deprecated/v1/mock/MockNovaInstance.ts\"\nimport type * as v2 from \"./mock/MockNovaInstance.ts\"\n\nexport class AutoReconnectingWebsocket extends ReconnectingWebSocket {\n receivedFirstMessage?: MessageEvent\n targetUrl: string\n disposed = false\n readonly opts: {\n mock?: v1.MockNovaInstance | v2.MockNovaInstance\n onDispose?: () => void\n }\n\n constructor(\n targetUrl: string,\n 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 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","export type URLParseOptions = {\n /**\n * Ignore any scheme in the input string and force this scheme instead.\n */\n scheme?: \"http\" | \"https\"\n /**\n * If the input string does not include a scheme, use this as the default\n * scheme.\n */\n defaultScheme?: \"http\" | \"https\"\n}\n\n/**\n * Parse a string as a URL, with options to enforce or default the scheme.\n */\nexport function parseUrl(url: string, options: URLParseOptions = {}): URL {\n const { scheme, defaultScheme } = options\n\n const schemeRegex = /^[a-zA-Z]+:\\/\\//\n\n if (scheme) {\n // Force the scheme by removing any existing scheme and prepending the desired one\n url = url.replace(schemeRegex, \"\")\n url = `${scheme}://${url}`\n } else if (defaultScheme && !schemeRegex.test(url)) {\n // No scheme is present, add the default one\n url = `${defaultScheme}://${url}`\n }\n\n return new URL(url)\n}\n\n/**\n * Attempt to parse a string as a URL; return undefined if we can't\n */\nexport function tryParseUrl(\n url: string,\n options: URLParseOptions = {},\n): URL | undefined {\n try {\n return parseUrl(url, options)\n } catch {\n return undefined\n }\n}\n\n/**\n * Permissively parse a NOVA instance URL from a config variable.\n * If scheme is not specified, defaults to https for *.wandelbots.io hosts,\n * and http otherwise.\n * Throws an error if a valid URL could not be determined.\n */\nexport function parseNovaInstanceUrl(url: string): URL {\n const testUrl = tryParseUrl(url, { defaultScheme: \"http\" })\n if (testUrl?.host.endsWith(\".wandelbots.io\")) {\n return parseUrl(url, { defaultScheme: \"https\" })\n } else {\n return parseUrl(url, { defaultScheme: \"http\" })\n }\n}\n\n/** Try to parse something as JSON; return undefined if we can't */\n// biome-ignore lint/suspicious/noExplicitAny: it's json\nexport function tryParseJson(json: unknown): any {\n try {\n return JSON.parse(json as string)\n } catch {\n return undefined\n }\n}\n\n/** Try to turn something into JSON; return undefined if we can't */\nexport function tryStringifyJson(json: unknown): string | undefined {\n try {\n return JSON.stringify(json)\n } catch {\n return undefined\n }\n}\n\n/**\n * Converts object parameters to query string.\n * e.g. { a: \"1\", b: \"2\" } => \"?a=1&b=2\"\n * {} => \"\"\n */\nexport function makeUrlQueryString(obj: Record<string, string>): string {\n const str = new URLSearchParams(obj).toString()\n return str ? `?${str}` : \"\"\n}\n\n/** Convert radians to degrees */\nexport function radiansToDegrees(radians: number): number {\n return radians * (180 / Math.PI)\n}\n\n/** Convert degrees to radians */\nexport function degreesToRadians(degrees: number): number {\n return degrees * (Math.PI / 180)\n}\n\n/**\n * Check for coordinate system id equivalence, accounting for the \"world\" default\n * on empty/undefined values.\n */\nexport function isSameCoordinateSystem(\n firstCoordSystem: string | undefined,\n secondCoordSystem: string | undefined,\n) {\n if (!firstCoordSystem) firstCoordSystem = \"world\"\n if (!secondCoordSystem) secondCoordSystem = \"world\"\n\n return firstCoordSystem === secondCoordSystem\n}\n\n/**\n * Helpful const for converting {x, y, z} to [x, y, z] and vice versa\n */\nexport const XYZ_TO_VECTOR = { x: 0, y: 1, z: 2 }\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 * @deprecated Use makeErrorMessage instead and truncate the error for display as needed, or make a situation-specific localized error message based on a response code\n */\nexport function makeShortErrorMessage(err: unknown) {\n return makeErrorMessage(err)\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 NovaClient 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","// Some general checks about the context the nova-js code is running in\n\nexport const isBrowser = typeof window !== \"undefined\"\n\nexport const isLocalhostDev =\n isBrowser &&\n window.location.hostname === \"localhost\" &&\n process.env.NODE_ENV === \"development\"\n"],"mappings":"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"}
@@ -1,58 +1,18 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_converters = require("../../converters-EYS27XJE.cjs");
3
2
  let _nats_io_nats_core = require("@nats-io/nats-core");
4
- //#region src/lib/experimental/nats/buildNatsServerUrl.ts
5
- /**
6
- * Builds the WebSocket URL for a NOVA instance's NATS gateway from its
7
- * instance URL, e.g. `https://foo.instance.wandelbots.io` becomes
8
- * `wss://foo.instance.wandelbots.io/api/nats`.
9
- *
10
- * Pass the result as `servers` in the `NovaNatsClientConfig` passed to
11
- * `NovaNatsClient`.
12
- */
13
- function buildNatsServerUrl(instanceUrl) {
14
- const url = require_converters.parseNovaInstanceUrl(instanceUrl);
15
- return `${url.protocol === "https:" ? "wss:" : "ws:"}//${url.host}/api/nats`;
16
- }
17
- //#endregion
18
3
  //#region src/lib/experimental/nats/buildSubject.ts
19
4
  /**
20
5
  * Builds a NATS subject from an AsyncAPI-style channel address template
21
6
  * (e.g. `"{instance}.v2.cells.{cell}"`) by substituting each `{param}`
22
7
  * placeholder with the corresponding value from `params`.
23
8
  */
24
- function isValidSubjectChar(char) {
25
- const code = char.charCodeAt(0);
26
- return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "-" || char === "_" || char === "*";
27
- }
28
- function isValidSubjectValue(value) {
29
- if (value.length === 0) return false;
30
- for (const char of value) if (!isValidSubjectChar(char)) return false;
31
- return true;
32
- }
33
9
  function buildSubject(template, params) {
34
- let result = "";
35
- let cursor = 0;
36
- while (cursor < template.length) {
37
- const openIndex = template.indexOf("{", cursor);
38
- if (openIndex === -1) {
39
- result += template.slice(cursor);
40
- break;
41
- }
42
- const closeIndex = template.indexOf("}", openIndex + 1);
43
- if (closeIndex === -1) {
44
- result += template.slice(cursor);
45
- break;
46
- }
47
- result += template.slice(cursor, openIndex);
48
- const paramName = template.slice(openIndex + 1, closeIndex);
10
+ return template.replace(/\{([^}]+)\}/g, (match, paramName) => {
49
11
  const value = params[paramName];
50
12
  if (value === void 0) throw new Error(`Missing value for subject parameter "${paramName}" in template "${template}"`);
51
- if (!isValidSubjectValue(value)) throw new Error(`Invalid value for subject parameter "${paramName}": "${value}" (must be non-empty and contain only letters, digits, "-", and "_")`);
52
- result += value;
53
- cursor = closeIndex + 1;
54
- }
55
- return result;
13
+ if (value === "" || /[\s.>*]/.test(value)) throw new Error(`Invalid value for subject parameter "${paramName}": "${value}" (must be non-empty and must not contain whitespace or the NATS special characters ".", ">", "*")`);
14
+ return value;
15
+ });
56
16
  }
57
17
  //#endregion
58
18
  //#region src/lib/experimental/nats/NovaNatsClient.ts
@@ -64,33 +24,19 @@ function buildSubject(template, params) {
64
24
  */
65
25
  var NovaNatsClient = class {
66
26
  config;
67
- connectionPromise = null;
68
- constructor(nova, config = {}) {
69
- this.config = {
70
- servers: buildNatsServerUrl(nova.instanceUrl.href),
71
- ...config
72
- };
27
+ connection = null;
28
+ constructor(config) {
29
+ this.config = config;
73
30
  }
74
- /**
75
- * Connects to NATS if not already connected or connecting, and returns the
76
- * connection. Safe to call concurrently: all callers share the same
77
- * in-flight connection attempt instead of each starting their own.
78
- */
79
- connect() {
80
- if (!this.connectionPromise) this.connectionPromise = (0, _nats_io_nats_core.wsconnect)(this.config).catch((err) => {
81
- this.connectionPromise = null;
82
- throw err;
83
- });
84
- return this.connectionPromise;
31
+ /** Connects to NATS if not already connected, and returns the connection. */
32
+ async connect() {
33
+ if (!this.connection) this.connection = await (0, _nats_io_nats_core.wsconnect)(this.config);
34
+ return this.connection;
85
35
  }
86
- /** Closes the underlying NATS connection, if open or connecting. */
36
+ /** Closes the underlying NATS connection, if open. */
87
37
  async close() {
88
- const connectionPromise = this.connectionPromise;
89
- this.connectionPromise = null;
90
- if (!connectionPromise) return;
91
- try {
92
- await (await connectionPromise).close();
93
- } catch {}
38
+ await this.connection?.close();
39
+ this.connection = null;
94
40
  }
95
41
  /**
96
42
  * Subscribes to a NATS subject published by the server, invoking `handler`
@@ -102,8 +48,7 @@ var NovaNatsClient = class {
102
48
  *
103
49
  * Returns a function that unsubscribes when called.
104
50
  */
105
- async subscribe(subject, ...args) {
106
- const [params, handler] = args.length === 1 ? [{}, args[0]] : [args[0], args[1]];
51
+ async subscribe(subject, params, handler) {
107
52
  const nc = await this.connect();
108
53
  const resolvedSubject = buildSubject(subject, params);
109
54
  const sub = nc.subscribe(resolvedSubject);
@@ -127,22 +72,8 @@ var NovaNatsClient = class {
127
72
  const resolvedSubject = buildSubject(subject, params);
128
73
  return (await nc.request(resolvedSubject, JSON.stringify(payload), { timeout: opts.timeout ?? 5e3 })).json();
129
74
  }
130
- /**
131
- * Publishes a JSON payload to any NATS subject defined in the spec,
132
- * without waiting for a reply.
133
- *
134
- * `subject` is the subject template as it appears on the wire, e.g.
135
- * `"nova.v2.cells.{cell}.bus-ios.ios.set"`, with `{param}` placeholders
136
- * filled in from `params`.
137
- */
138
- async publish(subject, params, payload) {
139
- const nc = await this.connect();
140
- const resolvedSubject = buildSubject(subject, params);
141
- nc.publish(resolvedSubject, JSON.stringify(payload));
142
- }
143
75
  };
144
76
  //#endregion
145
77
  exports.NovaNatsClient = NovaNatsClient;
146
- exports.buildNatsServerUrl = buildNatsServerUrl;
147
78
 
148
79
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["parseNovaInstanceUrl"],"sources":["../../../src/lib/experimental/nats/buildNatsServerUrl.ts","../../../src/lib/experimental/nats/buildSubject.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"sourcesContent":["import { parseNovaInstanceUrl } from \"../../converters.ts\"\n\n/**\n * Builds the WebSocket URL for a NOVA instance's NATS gateway from its\n * instance URL, e.g. `https://foo.instance.wandelbots.io` becomes\n * `wss://foo.instance.wandelbots.io/api/nats`.\n *\n * Pass the result as `servers` in the `NovaNatsClientConfig` passed to\n * `NovaNatsClient`.\n */\nexport function buildNatsServerUrl(instanceUrl: string): string {\n const url = parseNovaInstanceUrl(instanceUrl)\n const protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\"\n return `${protocol}//${url.host}/api/nats`\n}\n","/**\n * Builds a NATS subject from an AsyncAPI-style channel address template\n * (e.g. `\"{instance}.v2.cells.{cell}\"`) by substituting each `{param}`\n * placeholder with the corresponding value from `params`.\n */\n\nfunction isValidSubjectChar(char: string): boolean {\n const code = char.charCodeAt(0)\n return (\n (code >= 48 && code <= 57) || // 0-9\n (code >= 65 && code <= 90) || // A-Z\n (code >= 97 && code <= 122) || // a-z\n char === \"-\" ||\n char === \"_\" ||\n char === \"*\"\n )\n}\n\nfunction isValidSubjectValue(value: string): boolean {\n if (value.length === 0) return false\n for (const char of value) {\n if (!isValidSubjectChar(char)) return false\n }\n return true\n}\n\nexport function buildSubject(\n template: string,\n params: Record<string, string>,\n): string {\n // Scanned manually (rather than with a regex like /\\{([^}]+)\\}/g) to avoid\n // a polynomial-time backtracking blowup on pathological input, e.g. a\n // template consisting of many \"{\" characters with no closing \"}\".\n let result = \"\"\n let cursor = 0\n\n while (cursor < template.length) {\n const openIndex = template.indexOf(\"{\", cursor)\n if (openIndex === -1) {\n result += template.slice(cursor)\n break\n }\n\n const closeIndex = template.indexOf(\"}\", openIndex + 1)\n if (closeIndex === -1) {\n result += template.slice(cursor)\n break\n }\n\n result += template.slice(cursor, openIndex)\n const paramName = template.slice(openIndex + 1, closeIndex)\n const value = params[paramName]\n if (value === undefined) {\n throw new Error(\n `Missing value for subject parameter \"${paramName}\" in template \"${template}\"`,\n )\n }\n if (!isValidSubjectValue(value)) {\n throw new Error(\n `Invalid value for subject parameter \"${paramName}\": \"${value}\" (must be non-empty and contain only letters, digits, \"-\", and \"_\")`,\n )\n }\n result += value\n\n cursor = closeIndex + 1\n }\n\n return result\n}\n","import {\n type ConnectionOptions,\n type Msg,\n type NatsConnection,\n wsconnect,\n} from \"@nats-io/nats-core\"\nimport type { Nova } from \"../../Nova.ts\"\nimport { buildNatsServerUrl } from \"./buildNatsServerUrl.ts\"\nimport { buildSubject } from \"./buildSubject.ts\"\nimport type {\n NatsOperationParams,\n NatsPublishPayloads,\n NatsPublishSubject,\n NatsReplyPayloads,\n NatsRequestPayloads,\n NatsRequestSubject,\n NatsSubscribePayloads,\n NatsSubscribeSubject,\n} from \"./generated/operations.ts\"\n\nexport type NovaNatsClientConfig = ConnectionOptions\n\ntype NatsMessageHandler<K extends NatsSubscribeSubject> = (\n payload: NatsSubscribePayloads[K],\n msg: Msg,\n) => void\n\ntype SubscribeArgs<K extends NatsSubscribeSubject> =\n keyof NatsOperationParams[K] extends never\n ? [handler: NatsMessageHandler<K>]\n : [params: NatsOperationParams[K], handler: NatsMessageHandler<K>]\n\n/**\n * Typed NATS client for the Wandelbots NOVA messaging API, generated from\n * src/asyncapi.yaml (see scripts/generate-nats-client.ts).\n *\n * Connects over WebSocket via `@nats-io/nats-core`'s `wsconnect`.\n */\nexport class NovaNatsClient {\n readonly config: NovaNatsClientConfig\n private connectionPromise: Promise<NatsConnection> | null = null\n\n constructor(nova: Nova, config: NovaNatsClientConfig = {}) {\n this.config = {\n servers: buildNatsServerUrl(nova.instanceUrl.href),\n ...config,\n }\n }\n\n /**\n * Connects to NATS if not already connected or connecting, and returns the\n * connection. Safe to call concurrently: all callers share the same\n * in-flight connection attempt instead of each starting their own.\n */\n connect(): Promise<NatsConnection> {\n if (!this.connectionPromise) {\n this.connectionPromise = wsconnect(this.config).catch((err: unknown) => {\n // Allow a subsequent connect() call to retry after a failed attempt.\n this.connectionPromise = null\n throw err\n })\n }\n return this.connectionPromise\n }\n\n /** Closes the underlying NATS connection, if open or connecting. */\n async close(): Promise<void> {\n const connectionPromise = this.connectionPromise\n this.connectionPromise = null\n if (!connectionPromise) return\n try {\n const nc = await connectionPromise\n await nc.close()\n } catch {\n // Connection never succeeded; nothing to close.\n }\n }\n\n /**\n * Subscribes to a NATS subject published by the server, invoking `handler`\n * with the JSON-decoded payload of every message received.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}\"`, with `{param}` placeholders filled in from\n * `params`.\n *\n * Returns a function that unsubscribes when called.\n */\n async subscribe<K extends NatsSubscribeSubject>(\n subject: K,\n ...args: SubscribeArgs<K>\n ): Promise<() => void> {\n const [params, handler] =\n args.length === 1\n ? ([{}, args[0]] as const)\n : ([args[0], args[1]] as const)\n\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const sub = nc.subscribe(resolvedSubject)\n\n ;(async () => {\n for await (const msg of sub) {\n handler(msg.json<NatsSubscribePayloads[K]>(), msg)\n }\n })().catch((err: unknown) => {\n console.error(\n `Error handling NATS subscription for \"${resolvedSubject}\"`,\n err,\n )\n })\n\n return () => sub.unsubscribe()\n }\n\n /**\n * Sends a request payload for a NATS subject the server receives, and\n * waits for the JSON-decoded reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async request<K extends NatsRequestSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsRequestPayloads[K],\n opts: { timeout?: number } = {},\n ): Promise<NatsReplyPayloads[K]> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const msg = await nc.request(resolvedSubject, JSON.stringify(payload), {\n timeout: opts.timeout ?? 5000,\n })\n return msg.json<NatsReplyPayloads[K]>()\n }\n\n /**\n * Publishes a JSON payload to any NATS subject defined in the spec,\n * without waiting for a reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async publish<K extends NatsPublishSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsPublishPayloads[K],\n ): Promise<void> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n nc.publish(resolvedSubject, JSON.stringify(payload))\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/lib/experimental/nats/buildSubject.ts","../../../src/lib/experimental/nats/NovaNatsClient.ts"],"sourcesContent":["/**\n * Builds a NATS subject from an AsyncAPI-style channel address template\n * (e.g. `\"{instance}.v2.cells.{cell}\"`) by substituting each `{param}`\n * placeholder with the corresponding value from `params`.\n */\nexport function buildSubject(\n template: string,\n params: Record<string, string>,\n): string {\n return template.replace(/\\{([^}]+)\\}/g, (match, paramName: string) => {\n const value = params[paramName]\n if (value === undefined) {\n throw new Error(\n `Missing value for subject parameter \"${paramName}\" in template \"${template}\"`,\n )\n }\n if (value === \"\" || /[\\s.>*]/.test(value)) {\n throw new Error(\n `Invalid value for subject parameter \"${paramName}\": \"${value}\" (must be non-empty and must not contain whitespace or the NATS special characters \".\", \">\", \"*\")`,\n )\n }\n return value\n })\n}\n","import {\n type ConnectionOptions,\n type Msg,\n type NatsConnection,\n wsconnect,\n} from \"@nats-io/nats-core\"\nimport { buildSubject } from \"./buildSubject.ts\"\nimport type {\n NatsOperationParams,\n NatsReplyPayloads,\n NatsRequestPayloads,\n NatsRequestSubject,\n NatsSubscribePayloads,\n NatsSubscribeSubject,\n} from \"./generated/operations.ts\"\n\nexport type NovaNatsClientConfig = ConnectionOptions\n\n/**\n * Typed NATS client for the Wandelbots NOVA messaging API, generated from\n * src/asyncapi.yaml (see scripts/generate-nats-client.ts).\n *\n * Connects over WebSocket via `@nats-io/nats-core`'s `wsconnect`.\n */\nexport class NovaNatsClient {\n readonly config: NovaNatsClientConfig\n connection: NatsConnection | null = null\n\n constructor(config: NovaNatsClientConfig) {\n this.config = config\n }\n\n /** Connects to NATS if not already connected, and returns the connection. */\n async connect(): Promise<NatsConnection> {\n if (!this.connection) {\n this.connection = await wsconnect(this.config)\n }\n return this.connection\n }\n\n /** Closes the underlying NATS connection, if open. */\n async close(): Promise<void> {\n await this.connection?.close()\n this.connection = null\n }\n\n /**\n * Subscribes to a NATS subject published by the server, invoking `handler`\n * with the JSON-decoded payload of every message received.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}\"`, with `{param}` placeholders filled in from\n * `params`.\n *\n * Returns a function that unsubscribes when called.\n */\n async subscribe<K extends NatsSubscribeSubject>(\n subject: K,\n params: NatsOperationParams[K],\n handler: (payload: NatsSubscribePayloads[K], msg: Msg) => void,\n ): Promise<() => void> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const sub = nc.subscribe(resolvedSubject)\n\n ;(async () => {\n for await (const msg of sub) {\n handler(msg.json<NatsSubscribePayloads[K]>(), msg)\n }\n })().catch((err: unknown) => {\n console.error(\n `Error handling NATS subscription for \"${resolvedSubject}\"`,\n err,\n )\n })\n\n return () => sub.unsubscribe()\n }\n\n /**\n * Sends a request payload for a NATS subject the server receives, and\n * waits for the JSON-decoded reply.\n *\n * `subject` is the subject template as it appears on the wire, e.g.\n * `\"nova.v2.cells.{cell}.bus-ios.ios.set\"`, with `{param}` placeholders\n * filled in from `params`.\n */\n async request<K extends NatsRequestSubject>(\n subject: K,\n params: NatsOperationParams[K],\n payload: NatsRequestPayloads[K],\n opts: { timeout?: number } = {},\n ): Promise<NatsReplyPayloads[K]> {\n const nc = await this.connect()\n const resolvedSubject = buildSubject(subject, params)\n const msg = await nc.request(resolvedSubject, JSON.stringify(payload), {\n timeout: opts.timeout ?? 5000,\n })\n return msg.json<NatsReplyPayloads[K]>()\n }\n}\n"],"mappings":";;;;;;;;AAKA,SAAgB,aACd,UACA,QACQ;CACR,OAAO,SAAS,QAAQ,iBAAiB,OAAO,cAAsB;EACpE,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,wCAAwC,UAAU,iBAAiB,SAAS,EAC9E;EAEF,IAAI,UAAU,MAAM,UAAU,KAAK,KAAK,GACtC,MAAM,IAAI,MACR,wCAAwC,UAAU,MAAM,MAAM,mGAChE;EAEF,OAAO;CACT,CAAC;AACH;;;;;;;;;ACCA,IAAa,iBAAb,MAA4B;CAC1B;CACA,aAAoC;CAEpC,YAAY,QAA8B;EACxC,KAAK,SAAS;CAChB;;CAGA,MAAM,UAAmC;EACvC,IAAI,CAAC,KAAK,YACR,KAAK,aAAa,OAAA,GAAA,mBAAA,WAAgB,KAAK,MAAM;EAE/C,OAAO,KAAK;CACd;;CAGA,MAAM,QAAuB;EAC3B,MAAM,KAAK,YAAY,MAAM;EAC7B,KAAK,aAAa;CACpB;;;;;;;;;;;CAYA,MAAM,UACJ,SACA,QACA,SACqB;EACrB,MAAM,KAAK,MAAM,KAAK,QAAQ;EAC9B,MAAM,kBAAkB,aAAa,SAAS,MAAM;EACpD,MAAM,MAAM,GAAG,UAAU,eAAe;EAEvC,CAAC,YAAY;GACZ,WAAW,MAAM,OAAO,KACtB,QAAQ,IAAI,KAA+B,GAAG,GAAG;EAErD,GAAG,EAAE,OAAO,QAAiB;GAC3B,QAAQ,MACN,yCAAyC,gBAAgB,IACzD,GACF;EACF,CAAC;EAED,aAAa,IAAI,YAAY;CAC/B;;;;;;;;;CAUA,MAAM,QACJ,SACA,QACA,SACA,OAA6B,CAAC,GACC;EAC/B,MAAM,KAAK,MAAM,KAAK,QAAQ;EAC9B,MAAM,kBAAkB,aAAa,SAAS,MAAM;EAIpD,QAAO,MAHW,GAAG,QAAQ,iBAAiB,KAAK,UAAU,OAAO,GAAG,EACrE,SAAS,KAAK,WAAW,IAC3B,CAAC,GACU,KAA2B;CACxC;AACF"}