@openlv/signaling 0.1.2 → 0.2.0

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/messages.js CHANGED
@@ -20,7 +20,7 @@ export const validatePeerInfo = (info) => {
20
20
  return `info.name must be 1-${MAX_TEXT_LENGTH} characters`;
21
21
  }
22
22
  if (info.icon !== undefined && !isBoundedString(info.icon, MAX_ICON_LENGTH)) {
23
- return `info.icon must be 1-${MAX_ICON_LENGTH} characters — use a smaller image or an image URL`;
23
+ return `info.icon must be 1-${MAX_ICON_LENGTH} characters -- use a smaller image or an image URL`;
24
24
  }
25
25
  return undefined;
26
26
  };
@@ -38,7 +38,7 @@ const parsePeerInfo = (raw) => {
38
38
  return {
39
39
  identity: info["identity"],
40
40
  name: info["name"],
41
- ...(info["icon"] === undefined ? {} : { icon: info["icon"] }),
41
+ ...(info["icon"] !== undefined && { icon: info["icon"] }),
42
42
  };
43
43
  };
44
44
  export const parseCapabilities = (raw) => {
@@ -49,7 +49,7 @@ export const parseCapabilities = (raw) => {
49
49
  if (!Array.isArray(transports)
50
50
  || transports.length === 0
51
51
  || transports.length > MAX_TRANSPORTS
52
- || !transports.every(transportId => isBoundedString(transportId, MAX_TRANSPORT_ID_LENGTH))) {
52
+ || transports.some(transportId => !isBoundedString(transportId, MAX_TRANSPORT_ID_LENGTH))) {
53
53
  return undefined;
54
54
  }
55
55
  if (payload["info"] === undefined)
@@ -74,34 +74,34 @@ export const parseSignalMessage = (raw) => {
74
74
  }
75
75
  if (typeof parsed !== "object" || parsed === null)
76
76
  return undefined;
77
- const msg = parsed;
78
- switch (msg["type"]) {
77
+ const message = parsed;
78
+ switch (message["type"]) {
79
79
  case "flash": {
80
- return typeof msg["payload"] === "object" && msg["payload"] !== null
81
- ? msg
80
+ return typeof message["payload"] === "object" && message["payload"] !== null
81
+ ? message
82
82
  : undefined;
83
83
  }
84
84
  case "pubkey": {
85
- const payload = msg["payload"];
85
+ const payload = message["payload"];
86
86
  return typeof payload === "object"
87
87
  && payload !== null
88
88
  && typeof payload["publicKey"] === "string"
89
- ? msg
89
+ ? message
90
90
  : undefined;
91
91
  }
92
92
  case "capabilities": {
93
- const capabilities = parseCapabilities(msg["payload"]);
93
+ const capabilities = parseCapabilities(message["payload"]);
94
94
  if (!capabilities)
95
95
  return undefined;
96
96
  return {
97
97
  type: "capabilities",
98
98
  payload: capabilities,
99
- timestamp: msg["timestamp"],
99
+ timestamp: message["timestamp"],
100
100
  };
101
101
  }
102
102
  case "data": {
103
- return typeof msg["payload"] === "object" && msg["payload"] !== null
104
- ? msg
103
+ return typeof message["payload"] === "object" && message["payload"] !== null
104
+ ? message
105
105
  : undefined;
106
106
  }
107
107
  default: {
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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","sourcesContent":["export type SignalMessageBase<T extends string, P> = {\n type: T;\n payload: P;\n timestamp: number;\n};\n\n/**\n * Flash message\n * Sent to initiate handshake by non-host\n */\nexport type SignalMessageFlash = SignalMessageBase<\"flash\", object>;\n\nexport type SignalMessagePubkey = SignalMessageBase<\n \"pubkey\",\n {\n publicKey: string;\n }\n>;\n\n/** Optional self-description shown in the other peer's UI. */\nexport type PeerInfo = {\n /** Reverse-DNS application identifier, e.g. \"com.example.wallet\". */\n identity: string;\n name: string;\n /**\n * A data URI or an image URL. Only size is enforced on the wire — anyone\n * rendering or fetching it is responsible for sanity-checking it first.\n */\n icon?: string;\n};\n\nexport type PeerCapabilities = {\n /** Supported transport identifiers, in preference order. */\n transports: string[];\n info?: PeerInfo;\n};\n\n/**\n * Completes the handshake: acknowledges the previous step, negotiates the\n * transport, and shares peer metadata in a single packet.\n */\nexport type SignalMessageCapabilities = SignalMessageBase<\n \"capabilities\",\n PeerCapabilities\n>;\n\nexport type SignalMessageData = SignalMessageBase<\"data\", object>;\n\nexport type SignalMessage =\n | SignalMessageFlash\n | SignalMessagePubkey\n | SignalMessageCapabilities\n | SignalMessageData;\n\nconst MAX_TRANSPORTS = 8;\nconst MAX_TRANSPORT_ID_LENGTH = 32;\nconst MAX_TEXT_LENGTH = 128;\n\n// Relay message limits (ntfy in particular) leave little room after base64\n// and encryption overhead; oversized icons would silently fail to deliver,\n// so they are rejected here instead.\nexport const MAX_ICON_LENGTH = 8192;\n\nconst isBoundedString = (value: unknown, maxLength: number): value is string =>\n typeof value === \"string\" && value.length > 0 && value.length <= maxLength;\n\n/**\n * Check outgoing peer info against the wire limits enforced by receivers.\n * Returns a human-readable problem, or undefined when the info is sendable.\n * Senders must use this: a receiver silently drops an oversized packet, which\n * would otherwise surface only as a generic handshake timeout.\n */\nexport const validatePeerInfo = (info: PeerInfo): string | undefined => {\n if (!isBoundedString(info.identity, MAX_TEXT_LENGTH)) {\n return `info.identity must be 1-${MAX_TEXT_LENGTH} characters`;\n }\n\n if (!isBoundedString(info.name, MAX_TEXT_LENGTH)) {\n return `info.name must be 1-${MAX_TEXT_LENGTH} characters`;\n }\n\n if (info.icon !== undefined && !isBoundedString(info.icon, MAX_ICON_LENGTH)) {\n return `info.icon must be 1-${MAX_ICON_LENGTH} characters — use a smaller image or an image URL`;\n }\n\n return undefined;\n};\n\nconst parsePeerInfo = (raw: unknown): PeerInfo | undefined => {\n if (typeof raw !== \"object\" || raw === null) return undefined;\n\n const info = raw as Record<string, unknown>;\n\n if (!isBoundedString(info[\"identity\"], MAX_TEXT_LENGTH)) return undefined;\n\n if (!isBoundedString(info[\"name\"], MAX_TEXT_LENGTH)) return undefined;\n\n if (info[\"icon\"] !== undefined && !isBoundedString(info[\"icon\"], MAX_ICON_LENGTH)) {\n return undefined;\n }\n\n return {\n identity: info[\"identity\"],\n name: info[\"name\"],\n ...(info[\"icon\"] === undefined ? {} : { icon: info[\"icon\"] }),\n };\n};\n\nexport const parseCapabilities = (raw: unknown): PeerCapabilities | undefined => {\n if (typeof raw !== \"object\" || raw === null) return undefined;\n\n const payload = raw as Record<string, unknown>;\n const transports = payload[\"transports\"];\n\n if (\n !Array.isArray(transports)\n || transports.length === 0\n || transports.length > MAX_TRANSPORTS\n || !transports.every(transportId => isBoundedString(transportId, MAX_TRANSPORT_ID_LENGTH))\n ) {\n return undefined;\n }\n\n if (payload[\"info\"] === undefined) return { transports };\n\n const info = parsePeerInfo(payload[\"info\"]);\n\n if (!info) return undefined;\n\n return { transports, info };\n};\n\n/**\n * Parse and shape-check a decrypted signaling payload. The relay topic is\n * public, so decrypted JSON is still untrusted input: anything that does not\n * match one of the four message shapes is dropped (returns undefined).\n */\nexport const parseSignalMessage = (raw: string): SignalMessage | undefined => {\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n }\n catch {\n return undefined;\n }\n\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n\n const msg = parsed as Record<string, unknown>;\n\n switch (msg[\"type\"]) {\n case \"flash\": {\n return typeof msg[\"payload\"] === \"object\" && msg[\"payload\"] !== null\n ? (msg as SignalMessageFlash)\n : undefined;\n }\n case \"pubkey\": {\n const payload = msg[\"payload\"] as Record<string, unknown> | null;\n\n return typeof payload === \"object\"\n && payload !== null\n && typeof payload[\"publicKey\"] === \"string\"\n ? (msg as SignalMessagePubkey)\n : undefined;\n }\n case \"capabilities\": {\n const capabilities = parseCapabilities(msg[\"payload\"]);\n\n if (!capabilities) return undefined;\n\n return {\n type: \"capabilities\",\n payload: capabilities,\n timestamp: msg[\"timestamp\"] as number,\n };\n }\n case \"data\": {\n return typeof msg[\"payload\"] === \"object\" && msg[\"payload\"] !== null\n ? (msg as SignalMessageData)\n : undefined;\n }\n default: {\n return undefined;\n }\n }\n};\n"]}
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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","sourcesContent":["export type SignalMessageBase<T extends string, P> = {\n type: T;\n payload: P;\n timestamp: number;\n};\n\n/**\n * Flash message\n * Sent to initiate handshake by non-host\n */\nexport type SignalMessageFlash = SignalMessageBase<\"flash\", object>;\n\nexport type SignalMessagePubkey = SignalMessageBase<\n \"pubkey\",\n {\n publicKey: string;\n }\n>;\n\n/** Optional self-description shown in the other peer's UI. */\nexport type PeerInfo = {\n /** Reverse-DNS application identifier, e.g. \"com.example.wallet\". */\n identity: string;\n name: string;\n /**\n * A data URI or an image URL. Only size is enforced on the wire -- anyone\n * rendering or fetching it is responsible for sanity-checking it first.\n */\n icon?: string;\n};\n\nexport type PeerCapabilities = {\n /** Supported transport identifiers, in preference order. */\n transports: string[];\n info?: PeerInfo;\n};\n\n/**\n * Completes the handshake: acknowledges the previous step, negotiates the\n * transport, and shares peer metadata in a single packet.\n */\nexport type SignalMessageCapabilities = SignalMessageBase<\n \"capabilities\",\n PeerCapabilities\n>;\n\nexport type SignalMessageData = SignalMessageBase<\"data\", object>;\n\nexport type SignalMessage\n = | SignalMessageFlash\n | SignalMessagePubkey\n | SignalMessageCapabilities\n | SignalMessageData;\n\nconst MAX_TRANSPORTS = 8;\nconst MAX_TRANSPORT_ID_LENGTH = 32;\nconst MAX_TEXT_LENGTH = 128;\n\n// Relay message limits (ntfy in particular) leave little room after base64\n// and encryption overhead; oversized icons would silently fail to deliver,\n// so they are rejected here instead.\nexport const MAX_ICON_LENGTH = 8192;\n\nconst isBoundedString = (value: unknown, maxLength: number): value is string =>\n typeof value === \"string\" && value.length > 0 && value.length <= maxLength;\n\n/**\n * Check outgoing peer info against the wire limits enforced by receivers.\n * Returns a human-readable problem, or undefined when the info is sendable.\n * Senders must use this: a receiver silently drops an oversized packet, which\n * would otherwise surface only as a generic handshake timeout.\n */\nexport const validatePeerInfo = (info: PeerInfo): string | undefined => {\n if (!isBoundedString(info.identity, MAX_TEXT_LENGTH)) {\n return `info.identity must be 1-${MAX_TEXT_LENGTH} characters`;\n }\n\n if (!isBoundedString(info.name, MAX_TEXT_LENGTH)) {\n return `info.name must be 1-${MAX_TEXT_LENGTH} characters`;\n }\n\n if (info.icon !== undefined && !isBoundedString(info.icon, MAX_ICON_LENGTH)) {\n return `info.icon must be 1-${MAX_ICON_LENGTH} characters -- use a smaller image or an image URL`;\n }\n\n return undefined;\n};\n\nconst parsePeerInfo = (raw: unknown): PeerInfo | undefined => {\n if (typeof raw !== \"object\" || raw === null) return undefined;\n\n const info = raw as Record<string, unknown>;\n\n if (!isBoundedString(info[\"identity\"], MAX_TEXT_LENGTH)) return undefined;\n\n if (!isBoundedString(info[\"name\"], MAX_TEXT_LENGTH)) return undefined;\n\n if (info[\"icon\"] !== undefined && !isBoundedString(info[\"icon\"], MAX_ICON_LENGTH)) {\n return undefined;\n }\n\n return {\n identity: info[\"identity\"],\n name: info[\"name\"],\n ...(info[\"icon\"] !== undefined && { icon: info[\"icon\"] }),\n };\n};\n\nexport const parseCapabilities = (raw: unknown): PeerCapabilities | undefined => {\n if (typeof raw !== \"object\" || raw === null) return undefined;\n\n const payload = raw as Record<string, unknown>;\n const transports = payload[\"transports\"];\n\n if (\n !Array.isArray(transports)\n || transports.length === 0\n || transports.length > MAX_TRANSPORTS\n || transports.some(transportId => !isBoundedString(transportId, MAX_TRANSPORT_ID_LENGTH))\n ) {\n return undefined;\n }\n\n if (payload[\"info\"] === undefined) return { transports };\n\n const info = parsePeerInfo(payload[\"info\"]);\n\n if (!info) return undefined;\n\n return { transports, info };\n};\n\n/**\n * Parse and shape-check a decrypted signaling payload. The relay topic is\n * public, so decrypted JSON is still untrusted input: anything that does not\n * match one of the four message shapes is dropped (returns undefined).\n */\nexport const parseSignalMessage = (raw: string): SignalMessage | undefined => {\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n }\n catch {\n return undefined;\n }\n\n if (typeof parsed !== \"object\" || parsed === null) return undefined;\n\n const message = parsed as Record<string, unknown>;\n\n switch (message[\"type\"]) {\n case \"flash\": {\n return typeof message[\"payload\"] === \"object\" && message[\"payload\"] !== null\n ? (message as SignalMessageFlash)\n : undefined;\n }\n case \"pubkey\": {\n const payload = message[\"payload\"] as Record<string, unknown> | null;\n\n return typeof payload === \"object\"\n && payload !== null\n && typeof payload[\"publicKey\"] === \"string\"\n ? (message as SignalMessagePubkey)\n : undefined;\n }\n case \"capabilities\": {\n const capabilities = parseCapabilities(message[\"payload\"]);\n\n if (!capabilities) return undefined;\n\n return {\n type: \"capabilities\",\n payload: capabilities,\n timestamp: message[\"timestamp\"] as number,\n };\n }\n case \"data\": {\n return typeof message[\"payload\"] === \"object\" && message[\"payload\"] !== null\n ? (message as SignalMessageData)\n : undefined;\n }\n default: {\n return undefined;\n }\n }\n};\n"]}
@@ -24,7 +24,7 @@ describe("parseSignalMessage", () => {
24
24
  timestamp: 1750000000000,
25
25
  });
26
26
  });
27
- it("accepts an icon that is a URL — content vetting is the renderer's job", () => {
27
+ it("accepts an icon that is a URL -- content vetting is the renderer's job", () => {
28
28
  const message = parseSignalMessage(frame("capabilities", {
29
29
  transports: ["wrtc"],
30
30
  info: {
@@ -1 +1 @@
1
- {"version":3,"file":"messages.test.js","sourceRoot":"","sources":["../src/messages.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\n\nimport { MAX_ICON_LENGTH, parseSignalMessage, validatePeerInfo } from \"./messages.js\";\n\nconst frame = (type: string, payload: unknown) =>\n JSON.stringify({ type, payload, timestamp: 1_750_000_000_000 });\n\ndescribe(\"parseSignalMessage\", () => {\n it(\"parses a capabilities packet with info\", () => {\n const message = parseSignalMessage(\n frame(\"capabilities\", {\n transports: [\"wrtc\", \"ws\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"data:image/png;base64,aaaa\",\n },\n }),\n );\n\n expect(message).toEqual({\n type: \"capabilities\",\n payload: {\n transports: [\"wrtc\", \"ws\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"data:image/png;base64,aaaa\",\n },\n },\n timestamp: 1_750_000_000_000,\n });\n });\n\n it(\"accepts an icon that is a URL — content vetting is the renderer's job\", () => {\n const message = parseSignalMessage(\n frame(\"capabilities\", {\n transports: [\"wrtc\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"https://example.com/icon.png\",\n },\n }),\n );\n\n expect(message?.payload).toMatchObject({\n info: { icon: \"https://example.com/icon.png\" },\n });\n });\n\n it(\"parses a capabilities packet without info\", () => {\n expect(\n parseSignalMessage(frame(\"capabilities\", { transports: [\"wrtc\"] })),\n ).toMatchObject({ payload: { transports: [\"wrtc\"] } });\n });\n\n it(\"drops unknown extra fields from capabilities payloads\", () => {\n const message = parseSignalMessage(\n frame(\"capabilities\", { transports: [\"wrtc\"], extra: \"field\" }),\n );\n\n expect(message?.payload).toEqual({ transports: [\"wrtc\"] });\n });\n\n it(\"rejects capabilities without transports\", () => {\n expect(parseSignalMessage(frame(\"capabilities\", {}))).toBeUndefined();\n expect(parseSignalMessage(frame(\"capabilities\", { transports: [] }))).toBeUndefined();\n expect(\n parseSignalMessage(frame(\"capabilities\", { transports: [42] })),\n ).toBeUndefined();\n });\n\n it(\"rejects capabilities with malformed info\", () => {\n expect(\n parseSignalMessage(\n frame(\"capabilities\", { transports: [\"wrtc\"], info: { name: \"No identity\" } }),\n ),\n ).toBeUndefined();\n expect(\n parseSignalMessage(\n frame(\"capabilities\", {\n transports: [\"wrtc\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"x\".repeat(10_000),\n },\n }),\n ),\n ).toBeUndefined();\n });\n\n it(\"rejects retired and unknown packet types\", () => {\n expect(parseSignalMessage(frame(\"ack\", undefined))).toBeUndefined();\n expect(parseSignalMessage(frame(\"nonsense\", {}))).toBeUndefined();\n expect(parseSignalMessage(\"not json\")).toBeUndefined();\n });\n\n it(\"validates outgoing peer info with descriptive reasons\", () => {\n expect(validatePeerInfo({ identity: \"com.example.wallet\", name: \"Example Wallet\" }))\n .toBeUndefined();\n expect(validatePeerInfo({\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"data:image/png;base64,aaaa\",\n })).toBeUndefined();\n expect(validatePeerInfo({ identity: \"\", name: \"Example Wallet\" }))\n .toMatch(/identity/);\n expect(validatePeerInfo({ identity: \"com.example.wallet\", name: \"x\".repeat(200) }))\n .toMatch(/name/);\n expect(validatePeerInfo({\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"x\".repeat(MAX_ICON_LENGTH + 1),\n })).toMatch(/icon/);\n });\n\n it(\"still parses flash, pubkey, and data\", () => {\n expect(parseSignalMessage(frame(\"flash\", {}))).toMatchObject({ type: \"flash\" });\n expect(\n parseSignalMessage(frame(\"pubkey\", { publicKey: \"abc\" })),\n ).toMatchObject({ type: \"pubkey\" });\n expect(\n parseSignalMessage(frame(\"data\", { anything: true })),\n ).toMatchObject({ type: \"data\" });\n });\n});\n"]}
1
+ {"version":3,"file":"messages.test.js","sourceRoot":"","sources":["../src/messages.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\n\nimport { MAX_ICON_LENGTH, parseSignalMessage, validatePeerInfo } from \"./messages.js\";\n\nconst frame = (type: string, payload: unknown) =>\n JSON.stringify({ type, payload, timestamp: 1_750_000_000_000 });\n\ndescribe(\"parseSignalMessage\", () => {\n it(\"parses a capabilities packet with info\", () => {\n const message = parseSignalMessage(\n frame(\"capabilities\", {\n transports: [\"wrtc\", \"ws\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"data:image/png;base64,aaaa\",\n },\n }),\n );\n\n expect(message).toEqual({\n type: \"capabilities\",\n payload: {\n transports: [\"wrtc\", \"ws\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"data:image/png;base64,aaaa\",\n },\n },\n timestamp: 1_750_000_000_000,\n });\n });\n\n it(\"accepts an icon that is a URL -- content vetting is the renderer's job\", () => {\n const message = parseSignalMessage(\n frame(\"capabilities\", {\n transports: [\"wrtc\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"https://example.com/icon.png\",\n },\n }),\n );\n\n expect(message?.payload).toMatchObject({\n info: { icon: \"https://example.com/icon.png\" },\n });\n });\n\n it(\"parses a capabilities packet without info\", () => {\n expect(\n parseSignalMessage(frame(\"capabilities\", { transports: [\"wrtc\"] })),\n ).toMatchObject({ payload: { transports: [\"wrtc\"] } });\n });\n\n it(\"drops unknown extra fields from capabilities payloads\", () => {\n const message = parseSignalMessage(\n frame(\"capabilities\", { transports: [\"wrtc\"], extra: \"field\" }),\n );\n\n expect(message?.payload).toEqual({ transports: [\"wrtc\"] });\n });\n\n it(\"rejects capabilities without transports\", () => {\n expect(parseSignalMessage(frame(\"capabilities\", {}))).toBeUndefined();\n expect(parseSignalMessage(frame(\"capabilities\", { transports: [] }))).toBeUndefined();\n expect(\n parseSignalMessage(frame(\"capabilities\", { transports: [42] })),\n ).toBeUndefined();\n });\n\n it(\"rejects capabilities with malformed info\", () => {\n expect(\n parseSignalMessage(\n frame(\"capabilities\", { transports: [\"wrtc\"], info: { name: \"No identity\" } }),\n ),\n ).toBeUndefined();\n expect(\n parseSignalMessage(\n frame(\"capabilities\", {\n transports: [\"wrtc\"],\n info: {\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"x\".repeat(10_000),\n },\n }),\n ),\n ).toBeUndefined();\n });\n\n it(\"rejects retired and unknown packet types\", () => {\n expect(parseSignalMessage(frame(\"ack\", undefined))).toBeUndefined();\n expect(parseSignalMessage(frame(\"nonsense\", {}))).toBeUndefined();\n expect(parseSignalMessage(\"not json\")).toBeUndefined();\n });\n\n it(\"validates outgoing peer info with descriptive reasons\", () => {\n expect(validatePeerInfo({ identity: \"com.example.wallet\", name: \"Example Wallet\" }))\n .toBeUndefined();\n expect(validatePeerInfo({\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"data:image/png;base64,aaaa\",\n })).toBeUndefined();\n expect(validatePeerInfo({ identity: \"\", name: \"Example Wallet\" }))\n .toMatch(/identity/);\n expect(validatePeerInfo({ identity: \"com.example.wallet\", name: \"x\".repeat(200) }))\n .toMatch(/name/);\n expect(validatePeerInfo({\n identity: \"com.example.wallet\",\n name: \"Example Wallet\",\n icon: \"x\".repeat(MAX_ICON_LENGTH + 1),\n })).toMatch(/icon/);\n });\n\n it(\"still parses flash, pubkey, and data\", () => {\n expect(parseSignalMessage(frame(\"flash\", {}))).toMatchObject({ type: \"flash\" });\n expect(\n parseSignalMessage(frame(\"pubkey\", { publicKey: \"abc\" })),\n ).toMatchObject({ type: \"pubkey\" });\n expect(\n parseSignalMessage(frame(\"data\", { anything: true })),\n ).toMatchObject({ type: \"data\" });\n });\n});\n"]}
@@ -21,7 +21,11 @@ export const mqtt = ({ url, topic }) => {
21
21
  });
22
22
  connection.on("error", (error) => {
23
23
  log("MQTT: Error connecting to URL", error);
24
- reject(error instanceof Error ? error : new Error(String(error)));
24
+ // The client reports transport failures as a DOM Event, which
25
+ // stringifies to "[object Event]" and reaches the user's screen.
26
+ reject(error instanceof Error
27
+ ? error
28
+ : new Error(`MQTT: could not connect to ${endpoint}`));
25
29
  });
26
30
  connection.on("close", () => {
27
31
  reject(new Error("MQTT: Closed connection to URL"));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mqtt/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAmB,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAAsB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;IACxD,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,GAAG,IAAI,oCAAoC,CAAC;IAC7D,IAAI,UAAkC,CAAC;IAEvC,OAAO,oBAAoB,CAAC;QAC1B,IAAI,EAAE,MAAM;QACZ,KAAK;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACrC,UAAU,GAAG,UAAU,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;gBAE3C,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;oBAC5B,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;gBACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC/B,GAAG,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;oBAC5C,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACpE,CAAC,CAAC,CAAC;gBAEH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;oBAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;gBACtD,CAAC,CAAC,CAAC;gBAEH,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC;QACD,QAAQ;YACN,UAAU,EAAE,KAAK,EAAE,CAAC;YACpB,UAAU,GAAG,SAAS,CAAC;QACzB,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,OAAO;YACnB,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACvD,CAAC;YAED,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,OAAO;YACrB,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;YAErD,GAAG,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YAEzC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,aAAa,EAAE,OAAO,EAAE,EAAE;gBAClD,GAAG,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;gBAE9C,IAAI,aAAa,KAAK,KAAK;oBAAE,OAAO;gBAEpC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAElD,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC,CAAC;YAEH,MAAM,UAAU,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC","sourcesContent":["import { SignalNoConnectionError } from \"@openlv/core/errors\";\nimport { createMqtt, type MqttClient } from \"websocket-mqtt\";\n\nimport { createSignalingLayer } from \"../index.js\";\nimport type { SignalingProtocol } from \"../protocol.js\";\nimport { log } from \"../utils/log.js\";\n\n/**\n * MQTT Signaling Layer\n *\n * https://openlv.sh/api/signaling/mqtt\n */\nexport const mqtt: SignalingProtocol = ({ url, topic }) => {\n // `url` may be an empty string when the session URI omits `s`.\n const endpoint = url || \"wss://test.mosquitto.org:8081/mqtt\";\n let connection: MqttClient | undefined;\n\n return createSignalingLayer({\n type: \"mqtt\",\n setup() {\n return new Promise((resolve, reject) => {\n connection = createMqtt({ url: endpoint });\n\n connection.on(\"connect\", () => {\n resolve();\n });\n connection.on(\"error\", (error) => {\n log(\"MQTT: Error connecting to URL\", error);\n reject(error instanceof Error ? error : new Error(String(error)));\n });\n\n connection.on(\"close\", () => {\n reject(new Error(\"MQTT: Closed connection to URL\"));\n });\n\n connection.connect();\n });\n },\n teardown() {\n connection?.close();\n connection = undefined;\n },\n async publish(payload) {\n if (!connection) {\n throw new Error(\"MQTT: No connection to publish to\");\n }\n\n connection?.publish(topic, payload, { retain: false });\n },\n async subscribe(handler) {\n if (!connection) throw new SignalNoConnectionError();\n\n log(\"MQTT: Subscribing to topic\", topic);\n\n connection.on(\"message\", (receivedTopic, message) => {\n log(\"MQTT: Received message on topic\", topic);\n\n if (receivedTopic !== topic) return;\n\n const decoded = new TextDecoder().decode(message);\n\n handler(decoded);\n });\n\n await connection?.subscribe(topic);\n },\n });\n};\n\nObject.defineProperty(mqtt, \"__name\", { value: \"mqtt\" });\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mqtt/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAmB,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAAsB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE;IACxD,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,GAAG,IAAI,oCAAoC,CAAC;IAC7D,IAAI,UAAkC,CAAC;IAEvC,OAAO,oBAAoB,CAAC;QAC1B,IAAI,EAAE,MAAM;QACZ,KAAK;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACrC,UAAU,GAAG,UAAU,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;gBAE3C,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;oBAC5B,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;gBACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC/B,GAAG,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;oBAC5C,8DAA8D;oBAC9D,iEAAiE;oBACjE,MAAM,CACJ,KAAK,YAAY,KAAK;wBACpB,CAAC,CAAC,KAAK;wBACP,CAAC,CAAC,IAAI,KAAK,CAAC,8BAA8B,QAAQ,EAAE,CAAC,CACxD,CAAC;gBACJ,CAAC,CAAC,CAAC;gBAEH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;oBAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;gBACtD,CAAC,CAAC,CAAC;gBAEH,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC;QACD,QAAQ;YACN,UAAU,EAAE,KAAK,EAAE,CAAC;YACpB,UAAU,GAAG,SAAS,CAAC;QACzB,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,OAAO;YACnB,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACvD,CAAC;YAED,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,OAAO;YACrB,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,uBAAuB,EAAE,CAAC;YAErD,GAAG,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YAEzC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,aAAa,EAAE,OAAO,EAAE,EAAE;gBAClD,GAAG,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;gBAE9C,IAAI,aAAa,KAAK,KAAK;oBAAE,OAAO;gBAEpC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAElD,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,CAAC,CAAC,CAAC;YAEH,MAAM,UAAU,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC","sourcesContent":["import { SignalNoConnectionError } from \"@openlv/core/errors\";\nimport { createMqtt, type MqttClient } from \"websocket-mqtt\";\n\nimport { createSignalingLayer } from \"../index.js\";\nimport type { SignalingProtocol } from \"../protocol.js\";\nimport { log } from \"../utils/log.js\";\n\n/**\n * MQTT Signaling Layer\n *\n * https://openlv.sh/api/signaling/mqtt\n */\nexport const mqtt: SignalingProtocol = ({ url, topic }) => {\n // `url` may be an empty string when the session URI omits `s`.\n const endpoint = url || \"wss://test.mosquitto.org:8081/mqtt\";\n let connection: MqttClient | undefined;\n\n return createSignalingLayer({\n type: \"mqtt\",\n setup() {\n return new Promise((resolve, reject) => {\n connection = createMqtt({ url: endpoint });\n\n connection.on(\"connect\", () => {\n resolve();\n });\n connection.on(\"error\", (error) => {\n log(\"MQTT: Error connecting to URL\", error);\n // The client reports transport failures as a DOM Event, which\n // stringifies to \"[object Event]\" and reaches the user's screen.\n reject(\n error instanceof Error\n ? error\n : new Error(`MQTT: could not connect to ${endpoint}`),\n );\n });\n\n connection.on(\"close\", () => {\n reject(new Error(\"MQTT: Closed connection to URL\"));\n });\n\n connection.connect();\n });\n },\n teardown() {\n connection?.close();\n connection = undefined;\n },\n async publish(payload) {\n if (!connection) {\n throw new Error(\"MQTT: No connection to publish to\");\n }\n\n connection?.publish(topic, payload, { retain: false });\n },\n async subscribe(handler) {\n if (!connection) throw new SignalNoConnectionError();\n\n log(\"MQTT: Subscribing to topic\", topic);\n\n connection.on(\"message\", (receivedTopic, message) => {\n log(\"MQTT: Received message on topic\", topic);\n\n if (receivedTopic !== topic) return;\n\n const decoded = new TextDecoder().decode(message);\n\n handler(decoded);\n });\n\n await connection?.subscribe(topic);\n },\n });\n};\n\nObject.defineProperty(mqtt, \"__name\", { value: \"mqtt\" });\n"]}
package/dist/ntfy/url.js CHANGED
@@ -2,7 +2,7 @@ export const NTFY_URL_REGEX = /^(?<protocol>https?|ntfys?):\/\/(?:(?:(?<user>[^:
2
2
  /*
3
3
  * URL format supports the ?auth= parameter
4
4
  * example: https://ntfy.sh/?auth=mytoken
5
- * example: http://authtoken123@myserver.example.com/
5
+ * example: https://authtoken123@myserver.example.com/
6
6
  *
7
7
  * The same goes for apprise ntfy urls
8
8
  * example: ntfy://{token}@{host}/
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/ntfy/url.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,cAAc,GACvB,oHAAoH,CAAC;AAEzH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAW,EAAE;IACnD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAExC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,EACJ,QAAQ,EAAE,YAAY,EACtB,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,UAAU,GACX,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;IAEvB,qBAAqB;IACrB,IAAI,QAA0B,CAAC;IAE/B,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5C,QAAQ,GAAG,MAAM,CAAC;IACpB,CAAC;SACI,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACnD,QAAQ,GAAG,OAAO,CAAC;IACrB,CAAC;SACI,CAAC;QACJ,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI;QACtB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;QACpB,CAAC,CAAC,CAAC,QAAQ;YACP,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;YACrB,CAAC,CAAC,SAAS,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,WAAW;QACX,UAAU,EAAE,UAAU,IAAI,SAAS;KACpC,CAAC;AACJ,CAAC,CAAC","sourcesContent":["export type NtfyUrl = {\n host: string;\n protocol: \"http\" | \"https\";\n credentials?:\n | {\n user: string;\n password: string;\n }\n | { token: string; };\n parameters?: string;\n};\n\nexport const NTFY_URL_REGEX\n = /^(?<protocol>https?|ntfys?):\\/\\/(?:(?:(?<user>[^:@]+):)?(?<password>[^@]+)?@)?(?<host>[^/?]+)\\/?(?<parameters>.*)$/;\n\n/*\n * URL format supports the ?auth= parameter\n * example: https://ntfy.sh/?auth=mytoken\n * example: http://authtoken123@myserver.example.com/\n *\n * The same goes for apprise ntfy urls\n * example: ntfy://{token}@{host}/\n * example: ntfy://{user}:{password}@{host}/\n * https variant: ntfys://{user}:{password}@{host}/\n */\nexport const parseNtfyUrl = (url: string): NtfyUrl => {\n const match = url.match(NTFY_URL_REGEX);\n\n if (!match) {\n throw new Error(`Invalid NTFY URL: ${url}`);\n }\n\n const {\n protocol: protocolType,\n user,\n password,\n host,\n parameters,\n } = match.groups || {};\n\n // Determine protocol\n let protocol: \"http\" | \"https\";\n\n if ([\"http\", \"ntfy\"].includes(protocolType)) {\n protocol = \"http\";\n }\n else if ([\"https\", \"ntfys\"].includes(protocolType)) {\n protocol = \"https\";\n }\n else {\n throw new Error(`Invalid NTFY URL: ${url}`);\n }\n\n const credentials = user\n ? { user, password }\n : (password\n ? { token: password }\n : undefined);\n\n return {\n host,\n protocol,\n credentials,\n parameters: parameters || undefined,\n };\n};\n"]}
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/ntfy/url.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,cAAc,GACvB,oHAAoH,CAAC;AAEzH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAW,EAAE;IACnD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAExC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,EACJ,QAAQ,EAAE,YAAY,EACtB,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,UAAU,GACX,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;IAEvB,qBAAqB;IACrB,IAAI,QAA0B,CAAC;IAE/B,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5C,QAAQ,GAAG,MAAM,CAAC;IACpB,CAAC;SACI,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACnD,QAAQ,GAAG,OAAO,CAAC;IACrB,CAAC;SACI,CAAC;QACJ,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,WAAW,GAAG,IAAI;QACtB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;QACpB,CAAC,CAAC,CAAC,QAAQ;YACP,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;YACrB,CAAC,CAAC,SAAS,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,WAAW;QACX,UAAU,EAAE,UAAU,IAAI,SAAS;KACpC,CAAC;AACJ,CAAC,CAAC","sourcesContent":["export type NtfyUrl = {\n host: string;\n protocol: \"http\" | \"https\";\n credentials?:\n | {\n user: string;\n password: string;\n }\n | { token: string; };\n parameters?: string;\n};\n\nexport const NTFY_URL_REGEX\n = /^(?<protocol>https?|ntfys?):\\/\\/(?:(?:(?<user>[^:@]+):)?(?<password>[^@]+)?@)?(?<host>[^/?]+)\\/?(?<parameters>.*)$/;\n\n/*\n * URL format supports the ?auth= parameter\n * example: https://ntfy.sh/?auth=mytoken\n * example: https://authtoken123@myserver.example.com/\n *\n * The same goes for apprise ntfy urls\n * example: ntfy://{token}@{host}/\n * example: ntfy://{user}:{password}@{host}/\n * https variant: ntfys://{user}:{password}@{host}/\n */\nexport const parseNtfyUrl = (url: string): NtfyUrl => {\n const match = url.match(NTFY_URL_REGEX);\n\n if (!match) {\n throw new Error(`Invalid NTFY URL: ${url}`);\n }\n\n const {\n protocol: protocolType,\n user,\n password,\n host,\n parameters,\n } = match.groups || {};\n\n // Determine protocol\n let protocol: \"http\" | \"https\";\n\n if ([\"http\", \"ntfy\"].includes(protocolType)) {\n protocol = \"http\";\n }\n else if ([\"https\", \"ntfys\"].includes(protocolType)) {\n protocol = \"https\";\n }\n else {\n throw new Error(`Invalid NTFY URL: ${url}`);\n }\n\n const credentials = user\n ? { user, password }\n : (password\n ? { token: password }\n : undefined);\n\n return {\n host,\n protocol,\n credentials,\n parameters: parameters || undefined,\n };\n};\n"]}
@@ -10,10 +10,10 @@ const testCases = [
10
10
  },
11
11
  },
12
12
  {
13
- url: "http://ntfy.sh/?auth=mytoken2",
13
+ url: "https://ntfy.sh/?auth=mytoken2",
14
14
  expected: {
15
15
  host: "ntfy.sh",
16
- protocol: "http",
16
+ protocol: "https",
17
17
  parameters: "?auth=mytoken2",
18
18
  },
19
19
  },
@@ -44,11 +44,11 @@ const testCases = [
44
44
  },
45
45
  ];
46
46
  describe("should parse NTFY URL", () => {
47
- testCases.forEach((testCase) => {
47
+ for (const testCase of testCases) {
48
48
  it(`should parse NTFY URL ${testCase.url}`, () => {
49
49
  const result = parseNtfyUrl(testCase.url);
50
50
  expect(result).toEqual(testCase.expected);
51
51
  });
52
- });
52
+ }
53
53
  });
54
54
  //# sourceMappingURL=url.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.test.js","sourceRoot":"","sources":["../../src/ntfy/url.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,SAAS,GAAG;IAChB;QACE,GAAG,EAAE,gCAAgC;QACrC,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,OAAO;YACjB,UAAU,EAAE,gBAAgB;SAC7B;KACF;IACD;QACE,GAAG,EAAE,+BAA+B;QACpC,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,MAAM;YAChB,UAAU,EAAE,gBAAgB;SAC7B;KACF;IACD;QACE,GAAG,EAAE,4BAA4B;QACjC,QAAQ,EAAE;YACR,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;SAClC;KACF;IACD;QACE,GAAG,EAAE,oCAAoC;QACzC,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,OAAO;YACjB,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE;SACxD;KACF;IACD;QACE,GAAG,EAAE,sCAAsC;QAC3C,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;YACjC,UAAU,EAAE,eAAe;SAC5B;KACF;CACF,CAAC;AAEF,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC7B,EAAE,CAAC,yBAAyB,QAAQ,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE;YAC/C,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAE1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, it } from \"vitest\";\n\nimport { parseNtfyUrl } from \"./url.js\";\n\nconst testCases = [\n {\n url: \"https://ntfy.sh/?auth=mytoken1\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"https\",\n parameters: \"?auth=mytoken1\",\n },\n },\n {\n url: \"http://ntfy.sh/?auth=mytoken2\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"http\",\n parameters: \"?auth=mytoken2\",\n },\n },\n {\n url: \"ntfy://mytoken@example.com\",\n expected: {\n host: \"example.com\",\n protocol: \"http\",\n credentials: { token: \"mytoken\" },\n },\n },\n {\n url: \"ntfys://myuser:mypassword@ntfy.sh/\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"https\",\n credentials: { user: \"myuser\", password: \"mypassword\" },\n },\n },\n {\n url: \"ntfy://mytoken@ntfy.sh/?auth=mytoken\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"http\",\n credentials: { token: \"mytoken\" },\n parameters: \"?auth=mytoken\",\n },\n },\n];\n\ndescribe(\"should parse NTFY URL\", () => {\n testCases.forEach((testCase) => {\n it(`should parse NTFY URL ${testCase.url}`, () => {\n const result = parseNtfyUrl(testCase.url);\n\n expect(result).toEqual(testCase.expected);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"url.test.js","sourceRoot":"","sources":["../../src/ntfy/url.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,SAAS,GAAG;IAChB;QACE,GAAG,EAAE,gCAAgC;QACrC,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,OAAO;YACjB,UAAU,EAAE,gBAAgB;SAC7B;KACF;IACD;QACE,GAAG,EAAE,gCAAgC;QACrC,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,OAAO;YACjB,UAAU,EAAE,gBAAgB;SAC7B;KACF;IACD;QACE,GAAG,EAAE,4BAA4B;QACjC,QAAQ,EAAE;YACR,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;SAClC;KACF;IACD;QACE,GAAG,EAAE,oCAAoC;QACzC,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,OAAO;YACjB,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE;SACxD;KACF;IACD;QACE,GAAG,EAAE,sCAAsC;QAC3C,QAAQ,EAAE;YACR,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;YACjC,UAAU,EAAE,eAAe;SAC5B;KACF;CACF,CAAC;AAEF,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,EAAE,CAAC,yBAAyB,QAAQ,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE;YAC/C,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAE1C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, it } from \"vitest\";\n\nimport { parseNtfyUrl } from \"./url.js\";\n\nconst testCases = [\n {\n url: \"https://ntfy.sh/?auth=mytoken1\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"https\",\n parameters: \"?auth=mytoken1\",\n },\n },\n {\n url: \"https://ntfy.sh/?auth=mytoken2\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"https\",\n parameters: \"?auth=mytoken2\",\n },\n },\n {\n url: \"ntfy://mytoken@example.com\",\n expected: {\n host: \"example.com\",\n protocol: \"http\",\n credentials: { token: \"mytoken\" },\n },\n },\n {\n url: \"ntfys://myuser:mypassword@ntfy.sh/\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"https\",\n credentials: { user: \"myuser\", password: \"mypassword\" },\n },\n },\n {\n url: \"ntfy://mytoken@ntfy.sh/?auth=mytoken\",\n expected: {\n host: \"ntfy.sh\",\n protocol: \"http\",\n credentials: { token: \"mytoken\" },\n parameters: \"?auth=mytoken\",\n },\n },\n];\n\ndescribe(\"should parse NTFY URL\", () => {\n for (const testCase of testCases) {\n it(`should parse NTFY URL ${testCase.url}`, () => {\n const result = parseNtfyUrl(testCase.url);\n\n expect(result).toEqual(testCase.expected);\n });\n }\n});\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { MaybePromise } from "viem";
2
- import type { SignalingLayerFn } from "./index.js";
2
+ import type { SignalingLayerFunction } from "./index.js";
3
3
  export type SignalingChannel = {
4
4
  type: string;
5
5
  setup: () => MaybePromise<void>;
@@ -11,4 +11,4 @@ export type SignalingProtocolOptions = {
11
11
  topic: string;
12
12
  url: string;
13
13
  };
14
- export type SignalingProtocol = (properties: SignalingProtocolOptions) => MaybePromise<SignalingLayerFn>;
14
+ export type SignalingProtocol = (properties: SignalingProtocolOptions) => MaybePromise<SignalingLayerFunction>;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"","sourcesContent":["import type { MaybePromise } from \"viem\";\n\nimport type { SignalingLayerFn } from \"./index.js\";\n\nexport type SignalingChannel = {\n type: string;\n setup: () => MaybePromise<void>;\n teardown: () => MaybePromise<void>;\n publish: (payload: string) => MaybePromise<void>;\n subscribe: (handler: (payload: string) => void) => MaybePromise<void>;\n};\n\nexport type SignalingProtocolOptions = {\n topic: string;\n url: string;\n};\n\nexport type SignalingProtocol = (\n properties: SignalingProtocolOptions,\n) => MaybePromise<SignalingLayerFn>;\n"]}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"","sourcesContent":["import type { MaybePromise } from \"viem\";\n\nimport type { SignalingLayerFunction } from \"./index.js\";\n\nexport type SignalingChannel = {\n type: string;\n setup: () => MaybePromise<void>;\n teardown: () => MaybePromise<void>;\n publish: (payload: string) => MaybePromise<void>;\n subscribe: (handler: (payload: string) => void) => MaybePromise<void>;\n};\n\nexport type SignalingProtocolOptions = {\n topic: string;\n url: string;\n};\n\nexport type SignalingProtocol = (\n properties: SignalingProtocolOptions,\n) => MaybePromise<SignalingLayerFunction>;\n"]}
@@ -1 +1 @@
1
- export declare const log: (...args: Parameters<typeof console.log>) => void;
1
+ export declare const log: (...arguments_: Parameters<typeof console.log>) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlv/signaling",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "keywords": [
5
5
  "open-lavatory"
6
6
  ],
@@ -40,11 +40,6 @@
40
40
  "types": "./dist/gundb/index.d.ts",
41
41
  "import": "./dist/gundb/index.js",
42
42
  "default": "./dist/gundb/index.js"
43
- },
44
- "./dynamic": {
45
- "types": "./dist/dynamic.d.ts",
46
- "import": "./dist/dynamic.js",
47
- "default": "./dist/dynamic.js"
48
43
  }
49
44
  },
50
45
  "main": "./dist/index.js",
@@ -59,7 +54,7 @@
59
54
  "gun": "^0.2020.1241",
60
55
  "websocket-mqtt": "0.0.7",
61
56
  "ts-pattern": "^5",
62
- "@openlv/core": "0.1.1"
57
+ "@openlv/core": "0.2.0"
63
58
  },
64
59
  "devDependencies": {
65
60
  "typescript": "^5",
package/dist/dynamic.d.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Imports the signaling layer based on the protocol at runtime
3
- *
4
- * TODO: Make this a built-in feature of session/provider
5
- */
6
- export declare const dynamicSignalingLayer: (protocol: string) => Promise<import("./protocol.js").SignalingProtocol>;
package/dist/dynamic.js DELETED
@@ -1,23 +0,0 @@
1
- import { match } from "ts-pattern";
2
- /**
3
- * Imports the signaling layer based on the protocol at runtime
4
- *
5
- * TODO: Make this a built-in feature of session/provider
6
- */
7
- export const dynamicSignalingLayer = async (protocol) => match(protocol)
8
- .with("mqtt", async () => {
9
- const mod = await import("./mqtt/index.js");
10
- return mod.mqtt;
11
- })
12
- .with("ntfy", async () => {
13
- const mod = await import("./ntfy/index.js");
14
- return mod.ntfy;
15
- })
16
- .with("gun", async () => {
17
- const mod = await import("./gundb/index.js");
18
- return mod.gundb;
19
- })
20
- .otherwise(() => {
21
- throw new Error(`Unknown signaling protocol: ${protocol}`);
22
- });
23
- //# sourceMappingURL=dynamic.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dynamic.js","sourceRoot":"","sources":["../src/dynamic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;KAC7E,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;IACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAE5C,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC,CAAC;KACD,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;IACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAE5C,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC,CAAC;KACD,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;IACtB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAE7C,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,CAAC,CAAC;KACD,SAAS,CAAC,GAAG,EAAE;IACd,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC","sourcesContent":["import { match } from \"ts-pattern\";\n\n/**\n * Imports the signaling layer based on the protocol at runtime\n *\n * TODO: Make this a built-in feature of session/provider\n */\nexport const dynamicSignalingLayer = async (protocol: string) => match(protocol)\n .with(\"mqtt\", async () => {\n const mod = await import(\"./mqtt/index.js\");\n\n return mod.mqtt;\n })\n .with(\"ntfy\", async () => {\n const mod = await import(\"./ntfy/index.js\");\n\n return mod.ntfy;\n })\n .with(\"gun\", async () => {\n const mod = await import(\"./gundb/index.js\");\n\n return mod.gundb;\n })\n .otherwise(() => {\n throw new Error(`Unknown signaling protocol: ${protocol}`);\n });\n"]}