@openlv/core 0.1.1 → 0.1.2

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.
package/dist/log.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const createLogger: (scope: string, color?: string) => (...args: Parameters<typeof console.log>) => void;
1
+ export declare const createLogger: (scope: string, color?: string) => (...arguments_: Parameters<typeof console.log>) => void;
package/dist/log.js CHANGED
@@ -17,15 +17,15 @@ const isDebugEnabled = () => {
17
17
  return false;
18
18
  }
19
19
  };
20
- const supportsColor = typeof document !== "undefined";
21
- export const createLogger = (scope, color = "gray") => (...args) => {
20
+ const isSupportsColor = typeof document !== "undefined";
21
+ export const createLogger = (scope, color = "gray") => (...arguments_) => {
22
22
  if (!isDebugEnabled())
23
23
  return;
24
- if (supportsColor) {
25
- console.log(`%c[${scope}]%c`, `color: ${color}; font-weight: bold`, "color: inherit; font-weight: normal", ...args);
24
+ if (isSupportsColor) {
25
+ console.log(`%c[${scope}]%c`, `color: ${color}; font-weight: bold`, "color: inherit; font-weight: normal", ...arguments_);
26
26
  }
27
27
  else {
28
- console.log(`[${scope}]`, ...args);
28
+ console.log(`[${scope}]`, ...arguments_);
29
29
  }
30
30
  };
31
31
  //# sourceMappingURL=log.js.map
package/dist/log.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,GAAY,EAAE;IACnC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,UAIT,CAAC;QAEF,OAAO,OAAO,CACZ,CAAC,CAAC,YAAY;eACX,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,cAAc,CAAC;eACvC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,CACpC,CAAC;IACJ,CAAC;IACD,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC;AAEtD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,KAAK,GAAG,MAAM,EAAE,EAAE,CAC5D,CAAC,GAAG,IAAoC,EAAE,EAAE;IAC1C,IAAI,CAAC,cAAc,EAAE;QAAE,OAAO;IAE9B,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CACT,MAAM,KAAK,KAAK,EAChB,UAAU,KAAK,qBAAqB,EACpC,qCAAqC,EACrC,GAAG,IAAI,CACR,CAAC;IACJ,CAAC;SACI,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACrC,CAAC;AACH,CAAC,CAAC","sourcesContent":["/**\n * Debug logging is disabled by default so that key material, handshake\n * traffic, and RPC payloads never reach the console in production.\n *\n * Enable it by setting `globalThis.OPENLV_DEBUG = true`, adding an\n * `openlv:debug` key to localStorage, or setting the `OPENLV_DEBUG`\n * environment variable (Node).\n */\nconst isDebugEnabled = (): boolean => {\n try {\n const g = globalThis as {\n OPENLV_DEBUG?: unknown;\n localStorage?: Storage;\n process?: { env?: Record<string, string | undefined>; };\n };\n\n return Boolean(\n g.OPENLV_DEBUG\n ?? g.localStorage?.getItem(\"openlv:debug\")\n ?? g.process?.env?.[\"OPENLV_DEBUG\"],\n );\n }\n catch {\n return false;\n }\n};\n\nconst supportsColor = typeof document !== \"undefined\";\n\nexport const createLogger = (scope: string, color = \"gray\") =>\n (...args: Parameters<typeof console.log>) => {\n if (!isDebugEnabled()) return;\n\n if (supportsColor) {\n console.log(\n `%c[${scope}]%c`,\n `color: ${color}; font-weight: bold`,\n \"color: inherit; font-weight: normal\",\n ...args,\n );\n }\n else {\n console.log(`[${scope}]`, ...args);\n }\n };\n"]}
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,GAAY,EAAE;IACnC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,UAIT,CAAC;QAEF,OAAO,OAAO,CACZ,CAAC,CAAC,YAAY;eACX,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,cAAc,CAAC;eACvC,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,CACpC,CAAC;IACJ,CAAC;IACD,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC;AAExD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,KAAK,GAAG,MAAM,EAAE,EAAE,CAC5D,CAAC,GAAG,UAA0C,EAAE,EAAE;IAChD,IAAI,CAAC,cAAc,EAAE;QAAE,OAAO;IAE9B,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CACT,MAAM,KAAK,KAAK,EAChB,UAAU,KAAK,qBAAqB,EACpC,qCAAqC,EACrC,GAAG,UAAU,CACd,CAAC;IACJ,CAAC;SACI,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC","sourcesContent":["/**\n * Debug logging is disabled by default so that key material, handshake\n * traffic, and RPC payloads never reach the console in production.\n *\n * Enable it by setting `globalThis.OPENLV_DEBUG = true`, adding an\n * `openlv:debug` key to localStorage, or setting the `OPENLV_DEBUG`\n * environment variable (Node).\n */\nconst isDebugEnabled = (): boolean => {\n try {\n const g = globalThis as {\n OPENLV_DEBUG?: unknown;\n localStorage?: Storage;\n process?: { env?: Record<string, string | undefined>; };\n };\n\n return Boolean(\n g.OPENLV_DEBUG\n ?? g.localStorage?.getItem(\"openlv:debug\")\n ?? g.process?.env?.[\"OPENLV_DEBUG\"],\n );\n }\n catch {\n return false;\n }\n};\n\nconst isSupportsColor = typeof document !== \"undefined\";\n\nexport const createLogger = (scope: string, color = \"gray\") =>\n (...arguments_: Parameters<typeof console.log>) => {\n if (!isDebugEnabled()) return;\n\n if (isSupportsColor) {\n console.log(\n `%c[${scope}]%c`,\n `color: ${color}; font-weight: bold`,\n \"color: inherit; font-weight: normal\",\n ...arguments_,\n );\n }\n else {\n console.log(`[${scope}]`, ...arguments_);\n }\n };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"","sourcesContent":["import type { DecryptionKey, EncryptionKey } from \"./encryption/asymmetric.js\";\n\n// typescript type for 32 character hex string\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport type Hex<_L> = string;\n\n/**\n * The parameters from the connection url\n */\nexport type SessionHandshakeParameters = {\n // Version of the openlv protocol\n version: 1;\n // Unique session identifier\n sessionId: Hex<16>;\n // Hash of peerA's public key\n h: Hex<16>;\n // Shared key for symmetric encryption during handshake (32 hex chars = 16 bytes)\n k: Hex<32>;\n // Protocol for signaling\n p: string;\n // Signaling server URL\n s: string;\n};\n\n/**\n * The parameters that describe a session\n * these can be used to restart a session\n */\nexport type SessionConnectionParameters = SessionHandshakeParameters & {\n relyingPublicKey: EncryptionKey;\n // Our public key for the relying party to encrypt messages to us\n encryptionKey: EncryptionKey;\n // Our private key for decrypting messages from the relying party\n decryptionKey: DecryptionKey;\n};\n\nexport type SessionCreationParameters = Pick<\n SessionHandshakeParameters,\n \"p\" | \"s\"\n>;\n\nexport type SessionLinkParameters =\n | SessionConnectionParameters\n | SessionHandshakeParameters\n | SessionCreationParameters;\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"","sourcesContent":["import type { DecryptionKey, EncryptionKey } from \"./encryption/asymmetric.js\";\n\n// typescript type for 32 character hex string\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport type Hex<_L> = string;\n\n/**\n * The parameters from the connection url\n */\nexport type SessionHandshakeParameters = {\n // Version of the openlv protocol\n version: 1;\n // Unique session identifier\n sessionId: Hex<16>;\n // Hash of peerA's public key\n h: Hex<16>;\n // Shared key for symmetric encryption during handshake (32 hex chars = 16 bytes)\n k: Hex<32>;\n // Protocol for signaling\n p: string;\n // Signaling server URL\n s: string;\n};\n\n/**\n * The parameters that describe a session\n * these can be used to restart a session\n */\nexport type SessionConnectionParameters = SessionHandshakeParameters & {\n relyingPublicKey: EncryptionKey;\n // Our public key for the relying party to encrypt messages to us\n encryptionKey: EncryptionKey;\n // Our private key for decrypting messages from the relying party\n decryptionKey: DecryptionKey;\n};\n\nexport type SessionCreationParameters = Pick<\n SessionHandshakeParameters,\n \"p\" | \"s\"\n>;\n\nexport type SessionLinkParameters\n = | SessionConnectionParameters\n | SessionHandshakeParameters\n | SessionCreationParameters;\n"]}
package/dist/url/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  export const OPENLV_PROTOCOL_VERSION = 1;
2
2
  export const encodeConnectionURL = (payload) => {
3
- const params = new URLSearchParams();
4
- params.set("h", payload.h);
5
- params.set("k", payload.k);
3
+ const parameters = new URLSearchParams();
4
+ parameters.set("h", payload.h);
5
+ parameters.set("k", payload.k);
6
6
  if (payload.p) {
7
- params.set("p", payload.p);
7
+ parameters.set("p", payload.p);
8
8
  }
9
9
  if (payload.s) {
10
- params.set("s", payload.s);
10
+ parameters.set("s", payload.s);
11
11
  }
12
- return `openlv://${payload.sessionId}@${OPENLV_PROTOCOL_VERSION}?${params.toString()}`;
12
+ return `openlv://${payload.sessionId}@${OPENLV_PROTOCOL_VERSION}?${parameters.toString()}`;
13
13
  };
14
14
  export const decodeConnectionURL = (url) => {
15
15
  if (typeof url !== "string")
@@ -19,16 +19,16 @@ export const decodeConnectionURL = (url) => {
19
19
  if (!url.startsWith("openlv://"))
20
20
  throw new Error(`Invalid URL format: must start with 'openlv://', got: ${url}`);
21
21
  try {
22
- const urlObj = new URL(url);
23
- const sessionId = urlObj.username;
24
- const version = Number(urlObj.hostname || urlObj.pathname.replace("/", ""));
22
+ const urlObject = new URL(url);
23
+ const sessionId = urlObject.username;
24
+ const version = Number(urlObject.hostname || urlObject.pathname.replace("/", ""));
25
25
  if (version !== OPENLV_PROTOCOL_VERSION) {
26
26
  throw new Error(`Invalid protocol version: expected ${OPENLV_PROTOCOL_VERSION}, got ${version}`);
27
27
  }
28
- const h = urlObj.searchParams.get("h") || "";
29
- const k = urlObj.searchParams.get("k") || "";
30
- const s = urlObj.searchParams.get("s") || undefined;
31
- const p = urlObj.searchParams.get("p") || "mqtt";
28
+ const h = urlObject.searchParams.get("h") || "";
29
+ const k = urlObject.searchParams.get("k") || "";
30
+ const s = urlObject.searchParams.get("s") || undefined;
31
+ const p = urlObject.searchParams.get("p") || "mqtt";
32
32
  if (!sessionId) {
33
33
  throw new Error("Session ID is required in URL");
34
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/url/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionHandshakeParameters } from \"../session.js\";\n\nexport const OPENLV_PROTOCOL_VERSION = 1;\n\nexport const encodeConnectionURL = (payload: SessionHandshakeParameters) => {\n const params = new URLSearchParams();\n\n params.set(\"h\", payload.h);\n params.set(\"k\", payload.k);\n\n if (payload.p) {\n params.set(\"p\", payload.p);\n }\n\n if (payload.s) {\n params.set(\"s\", payload.s);\n }\n\n return `openlv://${payload.sessionId}@${OPENLV_PROTOCOL_VERSION}?${params.toString()}`;\n};\n\nexport const decodeConnectionURL = (\n url: string,\n): SessionHandshakeParameters => {\n if (typeof url !== \"string\") throw new TypeError(`Invalid URL type: expected string, got ${typeof url}`);\n\n if (!url || url.trim() === \"\") throw new Error(\"URL cannot be empty\");\n\n if (!url.startsWith(\"openlv://\")) throw new Error(\n `Invalid URL format: must start with 'openlv://', got: ${url}`,\n );\n\n try {\n const urlObj = new URL(url);\n const sessionId = urlObj.username;\n const version = Number(urlObj.hostname || urlObj.pathname.replace(\"/\", \"\"));\n\n if (version !== OPENLV_PROTOCOL_VERSION) {\n throw new Error(\n `Invalid protocol version: expected ${OPENLV_PROTOCOL_VERSION}, got ${version}`,\n );\n }\n\n const h = urlObj.searchParams.get(\"h\") || \"\";\n const k = urlObj.searchParams.get(\"k\") || \"\";\n const s = urlObj.searchParams.get(\"s\") || undefined;\n\n const p = urlObj.searchParams.get(\"p\") || \"mqtt\";\n\n if (!sessionId) {\n throw new Error(\"Session ID is required in URL\");\n }\n\n // Validate session ID format (16 characters, URL-safe alphabet)\n if (!/^[A-Za-z0-9_-]{16}$/.test(sessionId)) {\n throw new Error(\n \"Invalid session ID format: must be 16 URL-safe characters\",\n );\n }\n\n if (!h) {\n throw new Error(\"Public key hash (h parameter) is required in URL\");\n }\n\n // Validate public key hash format (16 hex characters)\n if (!/^[0-9a-f]{16}$/.test(h)) {\n throw new Error(\n \"Invalid public key hash format: must be 16 hex characters\",\n );\n }\n\n if (!k) {\n throw new Error(\"Shared key (k parameter) is required in URL\");\n }\n\n // Validate shared key format (32 hex characters)\n if (!/^[0-9a-f]{32}$/.test(k)) {\n throw new Error(\"Invalid shared key format: must be 32 lowercase hex characters\");\n }\n\n return {\n version,\n sessionId,\n h,\n k,\n s: s ?? \"\",\n p,\n };\n }\n catch (error) {\n const errorMessage\n = error instanceof Error ? error.message : \"Unknown error\";\n\n // Strip query string so handshake secrets (k) are not exposed in error messages\n const safeUrl = url.includes(\"?\") ? url.slice(0, url.indexOf(\"?\")) + \"?[redacted]\" : url;\n\n throw new Error(`Failed to parse URL: ${safeUrl}. Original error: ${errorMessage}`);\n }\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/url/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionHandshakeParameters } from \"../session.js\";\n\nexport const OPENLV_PROTOCOL_VERSION = 1;\n\nexport const encodeConnectionURL = (payload: SessionHandshakeParameters) => {\n const parameters = new URLSearchParams();\n\n parameters.set(\"h\", payload.h);\n parameters.set(\"k\", payload.k);\n\n if (payload.p) {\n parameters.set(\"p\", payload.p);\n }\n\n if (payload.s) {\n parameters.set(\"s\", payload.s);\n }\n\n return `openlv://${payload.sessionId}@${OPENLV_PROTOCOL_VERSION}?${parameters.toString()}`;\n};\n\nexport const decodeConnectionURL = (\n url: string,\n): SessionHandshakeParameters => {\n if (typeof url !== \"string\") throw new TypeError(`Invalid URL type: expected string, got ${typeof url}`);\n\n if (!url || url.trim() === \"\") throw new Error(\"URL cannot be empty\");\n\n if (!url.startsWith(\"openlv://\")) throw new Error(\n `Invalid URL format: must start with 'openlv://', got: ${url}`,\n );\n\n try {\n const urlObject = new URL(url);\n const sessionId = urlObject.username;\n const version = Number(urlObject.hostname || urlObject.pathname.replace(\"/\", \"\"));\n\n if (version !== OPENLV_PROTOCOL_VERSION) {\n throw new Error(\n `Invalid protocol version: expected ${OPENLV_PROTOCOL_VERSION}, got ${version}`,\n );\n }\n\n const h = urlObject.searchParams.get(\"h\") || \"\";\n const k = urlObject.searchParams.get(\"k\") || \"\";\n const s = urlObject.searchParams.get(\"s\") || undefined;\n\n const p = urlObject.searchParams.get(\"p\") || \"mqtt\";\n\n if (!sessionId) {\n throw new Error(\"Session ID is required in URL\");\n }\n\n // Validate session ID format (16 characters, URL-safe alphabet)\n if (!/^[A-Za-z0-9_-]{16}$/.test(sessionId)) {\n throw new Error(\n \"Invalid session ID format: must be 16 URL-safe characters\",\n );\n }\n\n if (!h) {\n throw new Error(\"Public key hash (h parameter) is required in URL\");\n }\n\n // Validate public key hash format (16 hex characters)\n if (!/^[0-9a-f]{16}$/.test(h)) {\n throw new Error(\n \"Invalid public key hash format: must be 16 hex characters\",\n );\n }\n\n if (!k) {\n throw new Error(\"Shared key (k parameter) is required in URL\");\n }\n\n // Validate shared key format (32 hex characters)\n if (!/^[0-9a-f]{32}$/.test(k)) {\n throw new Error(\"Invalid shared key format: must be 32 lowercase hex characters\");\n }\n\n return {\n version,\n sessionId,\n h,\n k,\n s: s ?? \"\",\n p,\n };\n }\n catch (error) {\n const errorMessage\n = error instanceof Error ? error.message : \"Unknown error\";\n\n // Strip query string so handshake secrets (k) are not exposed in error messages\n const safeUrl = url.includes(\"?\") ? url.slice(0, url.indexOf(\"?\")) + \"?[redacted]\" : url;\n\n throw new Error(`Failed to parse URL: ${safeUrl}. Original error: ${errorMessage}`);\n }\n};\n"]}
@@ -15,16 +15,16 @@ const testCases = [
15
15
  ];
16
16
  describe("URL Utilities", () => {
17
17
  test("should decode connection URL", () => {
18
- testCases.forEach((testCase) => {
18
+ for (const testCase of testCases) {
19
19
  const result = decodeConnectionURL(testCase.url);
20
20
  expect(result).toEqual(testCase.expected);
21
- });
21
+ }
22
22
  });
23
23
  test("should encode connection URL", () => {
24
- testCases.forEach((testCase) => {
24
+ for (const testCase of testCases) {
25
25
  const result = encodeConnectionURL(testCase.expected);
26
26
  expect(result).toEqual(testCase.url);
27
- });
27
+ }
28
28
  });
29
29
  });
30
30
  //# sourceMappingURL=index.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/url/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,SAAS,GAAG;IAChB;QACE,GAAG,EAAE,yIAAyI;QAC9I,QAAQ,EAAE;YACR,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,kBAAkB;YAC7B,CAAC,EAAE,kBAAkB;YACrB,CAAC,EAAE,kCAAkC;YACrC,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,oCAAoC;SAC/B;KACX;CACF,CAAC;AAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7B,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAEjD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7B,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAEtD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, test } from \"vitest\";\n\nimport { decodeConnectionURL, encodeConnectionURL } from \"./index.js\";\n\nconst testCases = [\n {\n url: \"openlv://k7n8m9x2w5q1p3r6@1?h=a1b2c3d4e5f60708&k=0123456789abcdef0123456789abcdef&p=mqtt&s=wss%3A%2F%2Ftest.mosquitto.org%3A8081%2Fmqtt\",\n expected: {\n version: 1,\n sessionId: \"k7n8m9x2w5q1p3r6\",\n h: \"a1b2c3d4e5f60708\",\n k: \"0123456789abcdef0123456789abcdef\",\n p: \"mqtt\",\n s: \"wss://test.mosquitto.org:8081/mqtt\",\n } as const,\n },\n];\n\ndescribe(\"URL Utilities\", () => {\n test(\"should decode connection URL\", () => {\n testCases.forEach((testCase) => {\n const result = decodeConnectionURL(testCase.url);\n\n expect(result).toEqual(testCase.expected);\n });\n });\n\n test(\"should encode connection URL\", () => {\n testCases.forEach((testCase) => {\n const result = encodeConnectionURL(testCase.expected);\n\n expect(result).toEqual(testCase.url);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/url/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,SAAS,GAAG;IAChB;QACE,GAAG,EAAE,yIAAyI;QAC9I,QAAQ,EAAE;YACR,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,kBAAkB;YAC7B,CAAC,EAAE,kBAAkB;YACrB,CAAC,EAAE,kCAAkC;YACrC,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,oCAAoC;SAC/B;KACX;CACF,CAAC;AAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAEjD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAEtD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, test } from \"vitest\";\n\nimport { decodeConnectionURL, encodeConnectionURL } from \"./index.js\";\n\nconst testCases = [\n {\n url: \"openlv://k7n8m9x2w5q1p3r6@1?h=a1b2c3d4e5f60708&k=0123456789abcdef0123456789abcdef&p=mqtt&s=wss%3A%2F%2Ftest.mosquitto.org%3A8081%2Fmqtt\",\n expected: {\n version: 1,\n sessionId: \"k7n8m9x2w5q1p3r6\",\n h: \"a1b2c3d4e5f60708\",\n k: \"0123456789abcdef0123456789abcdef\",\n p: \"mqtt\",\n s: \"wss://test.mosquitto.org:8081/mqtt\",\n } as const,\n },\n];\n\ndescribe(\"URL Utilities\", () => {\n test(\"should decode connection URL\", () => {\n for (const testCase of testCases) {\n const result = decodeConnectionURL(testCase.url);\n\n expect(result).toEqual(testCase.expected);\n }\n });\n\n test(\"should encode connection URL\", () => {\n for (const testCase of testCases) {\n const result = encodeConnectionURL(testCase.expected);\n\n expect(result).toEqual(testCase.url);\n }\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlv/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "keywords": [
5
5
  "open-lavatory"
6
6
  ],