@openlv/signaling 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dynamic.js CHANGED
@@ -6,16 +6,16 @@ import { match } from "ts-pattern";
6
6
  */
7
7
  export const dynamicSignalingLayer = async (protocol) => match(protocol)
8
8
  .with("mqtt", async () => {
9
- const mod = await import("./mqtt/index.js");
10
- return mod.mqtt;
9
+ const module_ = await import("./mqtt/index.js");
10
+ return module_.mqtt;
11
11
  })
12
12
  .with("ntfy", async () => {
13
- const mod = await import("./ntfy/index.js");
14
- return mod.ntfy;
13
+ const module_ = await import("./ntfy/index.js");
14
+ return module_.ntfy;
15
15
  })
16
16
  .with("gun", async () => {
17
- const mod = await import("./gundb/index.js");
18
- return mod.gundb;
17
+ const module_ = await import("./gundb/index.js");
18
+ return module_.gundb;
19
19
  })
20
20
  .otherwise(() => {
21
21
  throw new Error(`Unknown signaling protocol: ${protocol}`);
@@ -1 +1 @@
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"]}
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,OAAO,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEhD,OAAO,OAAO,CAAC,IAAI,CAAC;AACtB,CAAC,CAAC;KACD,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;IACvB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEhD,OAAO,OAAO,CAAC,IAAI,CAAC;AACtB,CAAC,CAAC;KACD,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;IACtB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAEjD,OAAO,OAAO,CAAC,KAAK,CAAC;AACvB,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 module_ = await import(\"./mqtt/index.js\");\n\n return module_.mqtt;\n })\n .with(\"ntfy\", async () => {\n const module_ = await import(\"./ntfy/index.js\");\n\n return module_.ntfy;\n })\n .with(\"gun\", async () => {\n const module_ = await import(\"./gundb/index.js\");\n\n return module_.gundb;\n })\n .otherwise(() => {\n throw new Error(`Unknown signaling protocol: ${protocol}`);\n });\n"]}
@@ -12,8 +12,8 @@ const createTopic = () => {
12
12
  let drop = neverDrop;
13
13
  let published = 0;
14
14
  return {
15
- setDrop(fn) {
16
- drop = fn;
15
+ setDrop(function_) {
16
+ drop = function_;
17
17
  },
18
18
  inject(payload) {
19
19
  for (const subscriber of subscribers)
@@ -1 +1 @@
1
- {"version":3,"file":"handshake.test.js","sourceRoot":"","sources":["../src/handshake.test.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deriveSymmetricKey,\n type EncryptionKey,\n generateKeyPair,\n hashPublicKey,\n parseEncryptionKey,\n} from \"@openlv/core/encryption\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createSignalingLayer, SIGNAL_STATE, type SignalingLayer } from \"./index.js\";\nimport type { PeerCapabilities } from \"./messages.js\";\nimport type { SignalingChannel } from \"./protocol.js\";\n\n/**\n * In-memory topic emulating a public relay: every publish is delivered to\n * all subscribers (including the sender, like MQTT echo). A `drop` predicate\n * simulates lossy delivery.\n */\nconst neverDrop = () => false;\n\nconst createTopic = () => {\n const subscribers: ((payload: string) => void)[] = [];\n let drop: (payload: string, index: number) => boolean = neverDrop;\n let published = 0;\n\n return {\n setDrop(fn: (payload: string, index: number) => boolean) {\n drop = fn;\n },\n inject(payload: string) {\n for (const subscriber of subscribers) subscriber(payload);\n },\n channel(): SignalingChannel {\n return {\n type: \"memory\",\n setup: () => {},\n teardown: () => {},\n publish: (payload: string) => {\n const index = published++;\n\n if (drop(payload, index)) return;\n\n // Deliver asynchronously, as a real relay would.\n queueMicrotask(() => {\n for (const subscriber of subscribers) subscriber(payload);\n });\n },\n subscribe: (handler) => {\n subscribers.push(handler);\n },\n };\n },\n };\n};\n\nconst CLIENT_CAPABILITIES: PeerCapabilities = {\n transports: [\"wrtc\"],\n info: { identity: \"com.example.wallet\", name: \"Example Wallet\" },\n};\n\nconst createPeer = async (\n channel: SignalingChannel,\n { isHost, h, k }: { isHost: boolean; h: string; k: string; },\n) => {\n const { encryptionKey, decryptionKey } = await generateKeyPair();\n let relyingKey: EncryptionKey | undefined;\n let peerCapabilities: PeerCapabilities | undefined;\n\n const layer = await createSignalingLayer(channel)({\n isHost,\n h,\n k: await deriveSymmetricKey(k),\n publicKey: encryptionKey,\n canEncrypt: () => relyingKey !== undefined,\n encrypt: (message: string) => {\n if (!relyingKey) throw new Error(\"no peer key\");\n\n return relyingKey.encrypt(message);\n },\n decrypt: (message: string) => decryptionKey.decrypt(message),\n rpDiscovered: async (rpKey: string) => {\n relyingKey = await parseEncryptionKey(rpKey);\n },\n capabilities: CLIENT_CAPABILITIES,\n peerCapabilities: (capabilities) => {\n peerCapabilities = capabilities;\n },\n });\n\n return { layer, encryptionKey, getPeerCapabilities: () => peerCapabilities };\n};\n\nconst K = \"00112233445566778899aabbccddeeff\";\n\nconst setupPair = async (topic: ReturnType<typeof createTopic>) => {\n // Mirror createSession: the host's h is the hash of its own key, the\n // client receives that h out-of-band (via the URI).\n const hostKeys = await generateKeyPair();\n const h = await hashPublicKey(hostKeys.encryptionKey);\n\n const HOST_CAPABILITIES: PeerCapabilities = {\n transports: [\"wrtc\", \"ws\"],\n info: { identity: \"com.example.dapp\", name: \"Example dApp\" },\n };\n\n let hostRelying: EncryptionKey | undefined;\n let hostPeerCapabilities: PeerCapabilities | undefined;\n const host = await createSignalingLayer(topic.channel())({\n isHost: true,\n h,\n k: await deriveSymmetricKey(K),\n publicKey: hostKeys.encryptionKey,\n canEncrypt: () => hostRelying !== undefined,\n encrypt: (message: string) => {\n if (!hostRelying) throw new Error(\"no peer key\");\n\n return hostRelying.encrypt(message);\n },\n decrypt: (message: string) => hostKeys.decryptionKey.decrypt(message),\n rpDiscovered: async (rpKey: string) => {\n hostRelying = await parseEncryptionKey(rpKey);\n },\n capabilities: HOST_CAPABILITIES,\n peerCapabilities: (capabilities) => {\n hostPeerCapabilities = capabilities;\n },\n });\n\n const { layer: client, getPeerCapabilities: getClientPeerCapabilities }\n = await createPeer(topic.channel(), {\n isHost: false,\n h,\n k: K,\n });\n\n return {\n host,\n client,\n hostCapabilities: HOST_CAPABILITIES,\n getHostPeerCapabilities: () => hostPeerCapabilities,\n getClientPeerCapabilities,\n };\n};\n\nconst waitForState = (layer: SignalingLayer, target: string) =>\n new Promise<void>((resolve, reject) => {\n if (layer.getState().state === target) return resolve();\n\n layer.on(\"state_change\", (state) => {\n if (state === target) resolve();\n\n if (state === SIGNAL_STATE.ERROR && target !== SIGNAL_STATE.ERROR) {\n reject(new Error(\"signaling errored\"));\n }\n });\n });\n\ndescribe(\"signaling handshake (in-memory relay)\", () => {\n let host: SignalingLayer;\n let client: SignalingLayer;\n\n afterEach(async () => {\n await host?.teardown();\n await client?.teardown();\n });\n\n it(\"completes and exchanges data on a reliable relay\", async () => {\n const topic = createTopic();\n const pair = await setupPair(topic);\n\n ({ host, client } = pair);\n\n await host.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n await encrypted;\n\n const received = new Promise<object>(resolve => host.on(\"message\", resolve));\n\n await client.send({ hello: \"world\" });\n expect(await received).toEqual({ hello: \"world\" });\n });\n\n it(\"exchanges capabilities in both directions during the handshake\", async () => {\n const topic = createTopic();\n const pair = await setupPair(topic);\n\n ({ host, client } = pair);\n\n await host.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n await encrypted;\n\n expect(pair.getClientPeerCapabilities()).toEqual(pair.hostCapabilities);\n expect(pair.getHostPeerCapabilities()).toEqual(CLIENT_CAPABILITIES);\n });\n\n it(\"ignores garbage frames on the public topic\", async () => {\n const topic = createTopic();\n\n ({ host, client } = await setupPair(topic));\n\n await host.setup();\n\n // Garbage before and during the handshake: random text, valid-looking\n // frames with undecryptable bodies, and truncated frames.\n topic.inject(\"not-a-frame\");\n topic.inject(\"hh\");\n topic.inject(\"hcQUJDRA==\");\n topic.inject(\"xh!!!not-base64!!!\");\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n topic.inject(\"hcgarbage-mid-handshake\");\n await encrypted;\n });\n\n describe(\"lossy relay\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"recovers when handshake frames are dropped\", async () => {\n const topic = createTopic();\n\n // Drop the first delivery of every frame; re-sends get through.\n const seen = new Set<string>();\n\n topic.setDrop((payload) => {\n if (seen.has(payload.slice(0, 2) + payload.length)) return false;\n\n seen.add(payload.slice(0, 2) + payload.length);\n\n return true;\n });\n\n ({ host, client } = await setupPair(topic));\n\n await host.setup();\n await client.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n // Let several resend intervals elapse.\n for (let index = 0; index < 10; index += 1) {\n await vi.advanceTimersByTimeAsync(2100);\n }\n\n await encrypted;\n });\n\n it(\"errors out when the peer never appears\", async () => {\n const topic = createTopic();\n\n topic.setDrop(() => true);\n\n ({ host, client } = await setupPair(topic));\n\n await client.setup();\n\n const errored = waitForState(client, SIGNAL_STATE.ERROR);\n\n await vi.advanceTimersByTimeAsync(31_000);\n await errored;\n expect(client.getState().state).toBe(SIGNAL_STATE.ERROR);\n });\n });\n});\n"]}
1
+ {"version":3,"file":"handshake.test.js","sourceRoot":"","sources":["../src/handshake.test.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deriveSymmetricKey,\n type EncryptionKey,\n generateKeyPair,\n hashPublicKey,\n parseEncryptionKey,\n} from \"@openlv/core/encryption\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\n\nimport { createSignalingLayer, SIGNAL_STATE, type SignalingLayer } from \"./index.js\";\nimport type { PeerCapabilities } from \"./messages.js\";\nimport type { SignalingChannel } from \"./protocol.js\";\n\n/**\n * In-memory topic emulating a public relay: every publish is delivered to\n * all subscribers (including the sender, like MQTT echo). A `drop` predicate\n * simulates lossy delivery.\n */\nconst neverDrop = () => false;\n\nconst createTopic = () => {\n const subscribers: ((payload: string) => void)[] = [];\n let drop: (payload: string, index: number) => boolean = neverDrop;\n let published = 0;\n\n return {\n setDrop(function_: (payload: string, index: number) => boolean) {\n drop = function_;\n },\n inject(payload: string) {\n for (const subscriber of subscribers) subscriber(payload);\n },\n channel(): SignalingChannel {\n return {\n type: \"memory\",\n setup: () => {},\n teardown: () => {},\n publish: (payload: string) => {\n const index = published++;\n\n if (drop(payload, index)) return;\n\n // Deliver asynchronously, as a real relay would.\n queueMicrotask(() => {\n for (const subscriber of subscribers) subscriber(payload);\n });\n },\n subscribe: (handler) => {\n subscribers.push(handler);\n },\n };\n },\n };\n};\n\nconst CLIENT_CAPABILITIES: PeerCapabilities = {\n transports: [\"wrtc\"],\n info: { identity: \"com.example.wallet\", name: \"Example Wallet\" },\n};\n\nconst createPeer = async (\n channel: SignalingChannel,\n { isHost, h, k }: { isHost: boolean; h: string; k: string; },\n) => {\n const { encryptionKey, decryptionKey } = await generateKeyPair();\n let relyingKey: EncryptionKey | undefined;\n let peerCapabilities: PeerCapabilities | undefined;\n\n const layer = await createSignalingLayer(channel)({\n isHost,\n h,\n k: await deriveSymmetricKey(k),\n publicKey: encryptionKey,\n canEncrypt: () => relyingKey !== undefined,\n encrypt: (message: string) => {\n if (!relyingKey) throw new Error(\"no peer key\");\n\n return relyingKey.encrypt(message);\n },\n decrypt: (message: string) => decryptionKey.decrypt(message),\n rpDiscovered: async (rpKey: string) => {\n relyingKey = await parseEncryptionKey(rpKey);\n },\n capabilities: CLIENT_CAPABILITIES,\n peerCapabilities: (capabilities) => {\n peerCapabilities = capabilities;\n },\n });\n\n return { layer, encryptionKey, getPeerCapabilities: () => peerCapabilities };\n};\n\nconst K = \"00112233445566778899aabbccddeeff\";\n\nconst setupPair = async (topic: ReturnType<typeof createTopic>) => {\n // Mirror createSession: the host's h is the hash of its own key, the\n // client receives that h out-of-band (via the URI).\n const hostKeys = await generateKeyPair();\n const h = await hashPublicKey(hostKeys.encryptionKey);\n\n const HOST_CAPABILITIES: PeerCapabilities = {\n transports: [\"wrtc\", \"ws\"],\n info: { identity: \"com.example.dapp\", name: \"Example dApp\" },\n };\n\n let hostRelying: EncryptionKey | undefined;\n let hostPeerCapabilities: PeerCapabilities | undefined;\n const host = await createSignalingLayer(topic.channel())({\n isHost: true,\n h,\n k: await deriveSymmetricKey(K),\n publicKey: hostKeys.encryptionKey,\n canEncrypt: () => hostRelying !== undefined,\n encrypt: (message: string) => {\n if (!hostRelying) throw new Error(\"no peer key\");\n\n return hostRelying.encrypt(message);\n },\n decrypt: (message: string) => hostKeys.decryptionKey.decrypt(message),\n rpDiscovered: async (rpKey: string) => {\n hostRelying = await parseEncryptionKey(rpKey);\n },\n capabilities: HOST_CAPABILITIES,\n peerCapabilities: (capabilities) => {\n hostPeerCapabilities = capabilities;\n },\n });\n\n const { layer: client, getPeerCapabilities: getClientPeerCapabilities }\n = await createPeer(topic.channel(), {\n isHost: false,\n h,\n k: K,\n });\n\n return {\n host,\n client,\n hostCapabilities: HOST_CAPABILITIES,\n getHostPeerCapabilities: () => hostPeerCapabilities,\n getClientPeerCapabilities,\n };\n};\n\nconst waitForState = (layer: SignalingLayer, target: string) =>\n new Promise<void>((resolve, reject) => {\n if (layer.getState().state === target) return resolve();\n\n layer.on(\"state_change\", (state) => {\n if (state === target) resolve();\n\n if (state === SIGNAL_STATE.ERROR && target !== SIGNAL_STATE.ERROR) {\n reject(new Error(\"signaling errored\"));\n }\n });\n });\n\ndescribe(\"signaling handshake (in-memory relay)\", () => {\n let host: SignalingLayer;\n let client: SignalingLayer;\n\n afterEach(async () => {\n await host?.teardown();\n await client?.teardown();\n });\n\n it(\"completes and exchanges data on a reliable relay\", async () => {\n const topic = createTopic();\n const pair = await setupPair(topic);\n\n ({ host, client } = pair);\n\n await host.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n await encrypted;\n\n const received = new Promise<object>(resolve => host.on(\"message\", resolve));\n\n await client.send({ hello: \"world\" });\n expect(await received).toEqual({ hello: \"world\" });\n });\n\n it(\"exchanges capabilities in both directions during the handshake\", async () => {\n const topic = createTopic();\n const pair = await setupPair(topic);\n\n ({ host, client } = pair);\n\n await host.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n await encrypted;\n\n expect(pair.getClientPeerCapabilities()).toEqual(pair.hostCapabilities);\n expect(pair.getHostPeerCapabilities()).toEqual(CLIENT_CAPABILITIES);\n });\n\n it(\"ignores garbage frames on the public topic\", async () => {\n const topic = createTopic();\n\n ({ host, client } = await setupPair(topic));\n\n await host.setup();\n\n // Garbage before and during the handshake: random text, valid-looking\n // frames with undecryptable bodies, and truncated frames.\n topic.inject(\"not-a-frame\");\n topic.inject(\"hh\");\n topic.inject(\"hcQUJDRA==\");\n topic.inject(\"xh!!!not-base64!!!\");\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n await client.setup();\n topic.inject(\"hcgarbage-mid-handshake\");\n await encrypted;\n });\n\n describe(\"lossy relay\", () => {\n beforeEach(() => {\n vi.useFakeTimers();\n });\n\n afterEach(() => {\n vi.useRealTimers();\n });\n\n it(\"recovers when handshake frames are dropped\", async () => {\n const topic = createTopic();\n\n // Drop the first delivery of every frame; re-sends get through.\n const seen = new Set<string>();\n\n topic.setDrop((payload) => {\n if (seen.has(payload.slice(0, 2) + payload.length)) return false;\n\n seen.add(payload.slice(0, 2) + payload.length);\n\n return true;\n });\n\n ({ host, client } = await setupPair(topic));\n\n await host.setup();\n await client.setup();\n\n const encrypted = Promise.all([\n waitForState(host, SIGNAL_STATE.ENCRYPTED),\n waitForState(client, SIGNAL_STATE.ENCRYPTED),\n ]);\n\n // Let several resend intervals elapse.\n for (let index = 0; index < 10; index += 1) {\n await vi.advanceTimersByTimeAsync(2100);\n }\n\n await encrypted;\n });\n\n it(\"errors out when the peer never appears\", async () => {\n const topic = createTopic();\n\n topic.setDrop(() => true);\n\n ({ host, client } = await setupPair(topic));\n\n await client.setup();\n\n const errored = waitForState(client, SIGNAL_STATE.ERROR);\n\n await vi.advanceTimersByTimeAsync(31_000);\n await errored;\n expect(client.getState().state).toBe(SIGNAL_STATE.ERROR);\n });\n });\n});\n"]}
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export type SignalingContext = {
41
41
  };
42
42
  };
43
43
  export type SignalingLayer = EventEmitter<SignalEventMap> & SignalingContext;
44
- export type SignalingLayerFn = (properties: SignalingProperties) => Promise<SignalingLayer>;
44
+ export type SignalingLayerFunction = (properties: SignalingProperties) => Promise<SignalingLayer>;
45
45
  export declare const XR_PREFIX = "x";
46
46
  export declare const XR_H_PREFIX = "h";
47
47
  /**
@@ -49,4 +49,4 @@ export declare const XR_H_PREFIX = "h";
49
49
  *
50
50
  * https://openlv.sh/api/signaling
51
51
  */
52
- export declare const createSignalingLayer: (init: SignalingChannel) => SignalingLayerFn;
52
+ export declare const createSignalingLayer: (init: SignalingChannel) => SignalingLayerFunction;
package/dist/index.js CHANGED
@@ -32,8 +32,8 @@ const HANDSHAKE_TIMEOUT_MS = 30000;
32
32
  export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decrypt, rpDiscovered, capabilities, peerCapabilities, h, k, publicKey, isHost, }) => {
33
33
  const emitter = new EventEmitter();
34
34
  let state = SIGNAL_STATE.STANDBY;
35
- let peerKeyRecorded = false;
36
- let peerCapabilitiesRecorded = false;
35
+ let isPeerKeyRecorded = false;
36
+ let isPeerCapabilitiesRecorded = false;
37
37
  let resendTimer;
38
38
  let deadlineTimer;
39
39
  const handshakeKey = k || undefined;
@@ -102,18 +102,18 @@ export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decr
102
102
  const recordPeerKey = async (key) => {
103
103
  // Never overwrite an established peer key — a second, different pubkey
104
104
  // mid-handshake is either a duplicate delivery or an injection attempt.
105
- if (peerKeyRecorded)
105
+ if (isPeerKeyRecorded)
106
106
  return false;
107
- peerKeyRecorded = true;
107
+ isPeerKeyRecorded = true;
108
108
  await rpDiscovered(key);
109
109
  return true;
110
110
  };
111
111
  const recordPeerCapabilities = async (peerCaps) => {
112
112
  // Same overwrite guard as the peer key: only the first delivery counts,
113
113
  // re-sent duplicates are no-ops.
114
- if (peerCapabilitiesRecorded)
114
+ if (isPeerCapabilitiesRecorded)
115
115
  return;
116
- peerCapabilitiesRecorded = true;
116
+ isPeerCapabilitiesRecorded = true;
117
117
  await peerCapabilities(peerCaps);
118
118
  };
119
119
  const capabilitiesMessage = () => ({
@@ -126,8 +126,8 @@ export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decr
126
126
  payload: { publicKey: publicKey.toString() },
127
127
  timestamp: Date.now(),
128
128
  });
129
- const handleHandshakeFrame = async (msg) => {
130
- await match({ msg, state, isHost })
129
+ const handleHandshakeFrame = async (message) => {
130
+ await match({ msg: message, state, isHost })
131
131
  // Host: a client wants to connect. Re-entered on duplicate `flash`
132
132
  // deliveries (client re-sends until it hears our pubkey).
133
133
  .with({ msg: { type: "flash" }, state: SIGNAL_STATE.READY, isHost: true }, async () => {
@@ -136,9 +136,9 @@ export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decr
136
136
  await sendRepeating("handshake", "c", pubkeyMessage());
137
137
  })
138
138
  // Client: host announced its public key.
139
- .with({ msg: { type: "pubkey" }, isHost: false, state: SIGNAL_STATE.HANDSHAKE }, async ({ msg: { payload: msgPayload } }) => {
139
+ .with({ msg: { type: "pubkey" }, isHost: false, state: SIGNAL_STATE.HANDSHAKE }, async ({ msg: { payload: messagePayload } }) => {
140
140
  try {
141
- const receivedKey = await parseEncryptionKey(msgPayload.publicKey);
141
+ const receivedKey = await parseEncryptionKey(messagePayload.publicKey);
142
142
  if (!await validatePublicKeyHash(receivedKey, h)) {
143
143
  setState(SIGNAL_STATE.ERROR);
144
144
  log("Received host public key does not match expected hash — possible tampering");
@@ -150,26 +150,26 @@ export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decr
150
150
  log("Failed to parse received host public key");
151
151
  return;
152
152
  }
153
- if (!await recordPeerKey(msgPayload.publicKey))
153
+ if (!await recordPeerKey(messagePayload.publicKey))
154
154
  return;
155
155
  setState(SIGNAL_STATE.HANDSHAKE_PARTIAL);
156
156
  return await sendRepeating("encrypted", "h", pubkeyMessage());
157
157
  })
158
158
  .otherwise(() => {
159
- log("Ignoring handshake frame", msg.type, "in state", state);
159
+ log("Ignoring handshake frame", message.type, "in state", state);
160
160
  });
161
161
  };
162
- const handleEncryptedFrame = async (msg) => {
163
- await match({ msg, state, isHost })
162
+ const handleEncryptedFrame = async (message) => {
163
+ await match({ msg: message, state, isHost })
164
164
  // Host: client responded with its public key.
165
- .with({ msg: { type: "pubkey" }, isHost: true, state: SIGNAL_STATE.HANDSHAKE }, async ({ msg: { payload: msgPayload } }) => {
166
- if (!await recordPeerKey(msgPayload.publicKey))
165
+ .with({ msg: { type: "pubkey" }, isHost: true, state: SIGNAL_STATE.HANDSHAKE }, async ({ msg: { payload: messagePayload } }) => {
166
+ if (!await recordPeerKey(messagePayload.publicKey))
167
167
  return;
168
168
  setState(SIGNAL_STATE.HANDSHAKE_PARTIAL);
169
169
  return await sendRepeating("encrypted", "c", capabilitiesMessage());
170
170
  })
171
- .with({ msg: { type: "capabilities" }, state: SIGNAL_STATE.HANDSHAKE_PARTIAL }, async ({ msg: { payload: msgPayload } }) => {
172
- await recordPeerCapabilities(msgPayload);
171
+ .with({ msg: { type: "capabilities" }, state: SIGNAL_STATE.HANDSHAKE_PARTIAL }, async ({ msg: { payload: messagePayload } }) => {
172
+ await recordPeerCapabilities(messagePayload);
173
173
  setState(SIGNAL_STATE.ENCRYPTED);
174
174
  if (isHost)
175
175
  return;
@@ -180,10 +180,10 @@ export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decr
180
180
  // can finish.
181
181
  .with({ msg: { type: "capabilities" }, state: SIGNAL_STATE.ENCRYPTED, isHost: false }, async () => await send("encrypted", "h", capabilitiesMessage()))
182
182
  .with({ msg: { type: "data" }, state: SIGNAL_STATE.ENCRYPTED }, async () => {
183
- emitter.emit("message", msg.payload);
183
+ emitter.emit("message", message.payload);
184
184
  })
185
185
  .otherwise(() => {
186
- log("Ignoring encrypted frame", msg.type, "in state", state);
186
+ log("Ignoring encrypted frame", message.type, "in state", state);
187
187
  });
188
188
  };
189
189
  const handleReceive = async (payload) => {
@@ -200,14 +200,14 @@ export const createSignalingLayer = (init) => async ({ canEncrypt, encrypt, decr
200
200
  if (prefix === XR_H_PREFIX) {
201
201
  if (!handshakeKey)
202
202
  return;
203
- const msg = parseSignalMessage(await handshakeKey.decrypt(body));
204
- if (msg)
205
- await handleHandshakeFrame(msg);
203
+ const message = parseSignalMessage(await handshakeKey.decrypt(body));
204
+ if (message)
205
+ await handleHandshakeFrame(message);
206
206
  }
207
207
  else if (prefix === XR_PREFIX) {
208
- const msg = parseSignalMessage(await decrypt(body));
209
- if (msg)
210
- await handleEncryptedFrame(msg);
208
+ const message = parseSignalMessage(await decrypt(body));
209
+ if (message)
210
+ await handleEncryptedFrame(message);
211
211
  }
212
212
  else {
213
213
  log("Dropping frame with unknown prefix");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { make } from \"@openlv/core\";\nimport type { EncryptionKey, SymmetricKey } from \"@openlv/core/encryption\";\nimport { parseEncryptionKey, validatePublicKeyHash } from \"@openlv/core/encryption\";\nimport { EventEmitter } from \"eventemitter3\";\nimport { match } from \"ts-pattern\";\nimport type { MaybePromise } from \"viem\";\n\nimport {\n parseSignalMessage,\n type PeerCapabilities,\n type SignalMessage,\n} from \"./messages.js\";\nimport type { SignalingChannel } from \"./protocol.js\";\nimport { log } from \"./utils/log.js\";\n\nexport * from \"./messages.js\";\nexport * from \"./protocol.js\";\n\nexport const SIGNAL_STATE = {\n STANDBY: \"standby\",\n CONNECTING: \"connecting\",\n READY: \"ready\",\n HANDSHAKE: \"handshake\",\n HANDSHAKE_PARTIAL: \"handshake-partial\",\n ENCRYPTED: \"encrypted\",\n ERROR: \"error\",\n} as const;\nexport type SignalState = (typeof SIGNAL_STATE)[keyof typeof SIGNAL_STATE];\n\nexport type SignalEventMap = {\n state_change: (state: SignalState) => void;\n message: (message: object) => void;\n};\n\nexport type SignalingProperties = {\n isHost: boolean;\n h: string;\n k?: SymmetricKey;\n rpDiscovered: (rpKey: string) => MaybePromise<void>;\n // Our capabilities, advertised to the peer during the handshake\n capabilities: PeerCapabilities;\n peerCapabilities: (capabilities: PeerCapabilities) => MaybePromise<void>;\n // Decrypt using our private key\n decrypt: (message: string) => MaybePromise<string>;\n // Encrypt to relying party\n encrypt: (message: string) => MaybePromise<string>;\n // our public key\n publicKey: EncryptionKey;\n canEncrypt: () => boolean;\n};\n\nexport type SignalingContext = {\n type: string;\n\n // Sending only works once keys are exchanged\n send: (message: object) => MaybePromise<void>;\n setup: () => MaybePromise<void>;\n teardown: () => MaybePromise<void>;\n\n getState: () => {\n state: SignalState;\n };\n};\nexport type SignalingLayer = EventEmitter<SignalEventMap> & SignalingContext;\nexport type SignalingLayerFn = (\n properties: SignalingProperties,\n) => Promise<SignalingLayer>;\n\nexport const XR_PREFIX = \"x\";\nexport const XR_H_PREFIX = \"h\";\n\n/**\n * Relays are lossy (MQTT QoS 0, ntfy best-effort), so every handshake step\n * is re-sent on an interval until the state machine observes progress.\n * Receivers treat duplicates as no-ops, which keeps re-sends wire-compatible.\n */\nconst HANDSHAKE_RESEND_INTERVAL_MS = 2000;\nconst HANDSHAKE_TIMEOUT_MS = 30_000;\n\n/**\n * Base Signaling Layer implementation\n *\n * https://openlv.sh/api/signaling\n */\nexport const createSignalingLayer = (\n init: SignalingChannel,\n): SignalingLayerFn => async ({\n canEncrypt,\n encrypt,\n decrypt,\n rpDiscovered,\n capabilities,\n peerCapabilities,\n h,\n k,\n publicKey,\n isHost,\n}: SignalingProperties) => {\n const emitter = new EventEmitter<SignalEventMap>();\n let state: SignalState = SIGNAL_STATE.STANDBY;\n let peerKeyRecorded = false;\n let peerCapabilitiesRecorded = false;\n let resendTimer: ReturnType<typeof setInterval> | undefined;\n let deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n const handshakeKey = k || undefined;\n\n const stopResend = () => {\n clearInterval(resendTimer);\n resendTimer = undefined;\n };\n const stopTimers = () => {\n stopResend();\n clearTimeout(deadlineTimer);\n deadlineTimer = undefined;\n };\n\n const setState = (_state: SignalState) => {\n if (state === _state) return;\n\n state = _state;\n\n if (_state === SIGNAL_STATE.ENCRYPTED || _state === SIGNAL_STATE.ERROR) {\n stopTimers();\n }\n\n emitter.emit(\"state_change\", _state);\n };\n\n const send = async (\n method: \"handshake\" | \"encrypted\",\n recipient: \"h\" | \"c\",\n payload: SignalMessage,\n ) => {\n const prefix = match(method)\n .with(\"handshake\", () => XR_H_PREFIX)\n .with(\"encrypted\", () => XR_PREFIX)\n .exhaustive();\n\n const message = await match(method)\n .with(\"handshake\", () => {\n if (!handshakeKey) return;\n\n return handshakeKey.encrypt(JSON.stringify(payload));\n })\n .with(\"encrypted\", () => {\n if (!canEncrypt()) return;\n\n return encrypt(JSON.stringify(payload));\n })\n .exhaustive();\n\n if (message === undefined) {\n throw new Error(`Cannot encrypt ${method} frame: key not available`);\n }\n\n await init.publish(prefix + recipient + message);\n };\n\n /**\n * Send a handshake step now, then keep re-sending it until the next step\n * (or teardown) supersedes it. Errors are logged and swallowed — the next\n * tick retries.\n */\n const sendRepeating = (\n method: \"handshake\" | \"encrypted\",\n recipient: \"h\" | \"c\",\n payload: SignalMessage,\n ) => {\n stopResend();\n\n const attempt = () => send(method, recipient, payload)\n .catch(error => log(\"handshake send failed, will retry\", error));\n\n resendTimer = setInterval(attempt, HANDSHAKE_RESEND_INTERVAL_MS);\n\n return attempt();\n };\n\n const startHandshakeDeadline = () => {\n if (deadlineTimer) return;\n\n deadlineTimer = setTimeout(() => {\n if (state === SIGNAL_STATE.ENCRYPTED) return;\n\n log(\"handshake timed out\");\n setState(SIGNAL_STATE.ERROR);\n }, HANDSHAKE_TIMEOUT_MS);\n };\n\n const recordPeerKey = async (key: string): Promise<boolean> => {\n // Never overwrite an established peer key — a second, different pubkey\n // mid-handshake is either a duplicate delivery or an injection attempt.\n if (peerKeyRecorded) return false;\n\n peerKeyRecorded = true;\n await rpDiscovered(key);\n\n return true;\n };\n\n const recordPeerCapabilities = async (\n peerCaps: PeerCapabilities,\n ): Promise<void> => {\n // Same overwrite guard as the peer key: only the first delivery counts,\n // re-sent duplicates are no-ops.\n if (peerCapabilitiesRecorded) return;\n\n peerCapabilitiesRecorded = true;\n await peerCapabilities(peerCaps);\n };\n\n const capabilitiesMessage = (): SignalMessage => ({\n type: \"capabilities\",\n payload: capabilities,\n timestamp: Date.now(),\n });\n const pubkeyMessage = (): SignalMessage => ({\n type: \"pubkey\",\n payload: { publicKey: publicKey.toString() },\n timestamp: Date.now(),\n });\n\n const handleHandshakeFrame = async (msg: SignalMessage) => {\n await match({ msg, state, isHost })\n // Host: a client wants to connect. Re-entered on duplicate `flash`\n // deliveries (client re-sends until it hears our pubkey).\n .with({ msg: { type: \"flash\" }, state: SIGNAL_STATE.READY, isHost: true }, async () => {\n setState(SIGNAL_STATE.HANDSHAKE);\n startHandshakeDeadline();\n await sendRepeating(\"handshake\", \"c\", pubkeyMessage());\n })\n // Client: host announced its public key.\n .with({ msg: { type: \"pubkey\" }, isHost: false, state: SIGNAL_STATE.HANDSHAKE }, async ({ msg: { payload: msgPayload } }) => {\n try {\n const receivedKey = await parseEncryptionKey(msgPayload.publicKey);\n\n if (!await validatePublicKeyHash(receivedKey, h)) {\n setState(SIGNAL_STATE.ERROR);\n log(\"Received host public key does not match expected hash — possible tampering\");\n\n return;\n }\n }\n catch {\n setState(SIGNAL_STATE.ERROR);\n log(\"Failed to parse received host public key\");\n\n return;\n }\n\n if (!await recordPeerKey(msgPayload.publicKey)) return;\n\n setState(SIGNAL_STATE.HANDSHAKE_PARTIAL);\n\n return await sendRepeating(\"encrypted\", \"h\", pubkeyMessage());\n })\n .otherwise(() => {\n log(\"Ignoring handshake frame\", msg.type, \"in state\", state);\n });\n };\n\n const handleEncryptedFrame = async (msg: SignalMessage) => {\n await match({ msg, state, isHost })\n // Host: client responded with its public key.\n .with(\n { msg: { type: \"pubkey\" }, isHost: true, state: SIGNAL_STATE.HANDSHAKE },\n async ({ msg: { payload: msgPayload } }) => {\n if (!await recordPeerKey(msgPayload.publicKey)) return;\n\n setState(SIGNAL_STATE.HANDSHAKE_PARTIAL);\n\n return await sendRepeating(\"encrypted\", \"c\", capabilitiesMessage());\n },\n )\n .with(\n { msg: { type: \"capabilities\" }, state: SIGNAL_STATE.HANDSHAKE_PARTIAL },\n async ({ msg: { payload: msgPayload } }) => {\n await recordPeerCapabilities(msgPayload);\n setState(SIGNAL_STATE.ENCRYPTED);\n\n if (isHost) return;\n\n return await send(\"encrypted\", \"h\", capabilitiesMessage());\n },\n )\n // Client already encrypted, but the host is still re-sending its\n // capabilities (our final packet was lost): answer again so the host\n // can finish.\n .with(\n { msg: { type: \"capabilities\" }, state: SIGNAL_STATE.ENCRYPTED, isHost: false },\n async () => await send(\"encrypted\", \"h\", capabilitiesMessage()),\n )\n .with({ msg: { type: \"data\" }, state: SIGNAL_STATE.ENCRYPTED }, async () => {\n emitter.emit(\"message\", msg.payload as object);\n })\n .otherwise(() => {\n log(\"Ignoring encrypted frame\", msg.type, \"in state\", state);\n });\n };\n\n const handleReceive = async (payload: string) => {\n // The topic is public: anyone can publish garbage. Nothing in this\n // handler may throw past this boundary, otherwise a single malformed\n // frame becomes an unhandled rejection.\n try {\n const prefix = payload.slice(0, 1);\n const recipient = payload.slice(1, 2);\n const body = payload.slice(2);\n const isRecipient = (isHost ? \"h\" : \"c\") === recipient;\n\n if (!isRecipient) return;\n\n if (prefix === XR_H_PREFIX) {\n if (!handshakeKey) return;\n\n const msg = parseSignalMessage(await handshakeKey.decrypt(body));\n\n if (msg) await handleHandshakeFrame(msg);\n }\n else if (prefix === XR_PREFIX) {\n const msg = parseSignalMessage(await decrypt(body));\n\n if (msg) await handleEncryptedFrame(msg);\n }\n else {\n log(\"Dropping frame with unknown prefix\");\n }\n }\n catch (error) {\n log(\"Dropping undecryptable or malformed frame\", error);\n }\n };\n\n return make(emitter, {\n type: init.type,\n async setup() {\n setState(SIGNAL_STATE.CONNECTING);\n await init.setup();\n await init.subscribe(handleReceive);\n\n if (canEncrypt()) {\n setState(SIGNAL_STATE.ENCRYPTED);\n\n return;\n }\n\n setState(SIGNAL_STATE.READY);\n\n if (!isHost) {\n // Enter HANDSHAKE before publishing: the host's pubkey reply can\n // arrive while the publish is still in flight.\n setState(SIGNAL_STATE.HANDSHAKE);\n startHandshakeDeadline();\n await sendRepeating(\"handshake\", \"h\", {\n type: \"flash\",\n payload: {},\n timestamp: Date.now(),\n });\n }\n },\n async teardown() {\n log(\"teardown\");\n stopTimers();\n await init.teardown?.();\n },\n send(message: object) {\n if (!canEncrypt()) {\n return Promise.reject(\n new Error(\"Cannot encrypt message before keys are exchanged\"),\n );\n }\n\n const them = isHost ? \"c\" : \"h\";\n\n return send(\"encrypted\", them, {\n type: \"data\",\n payload: message,\n timestamp: Date.now(),\n });\n },\n getState() {\n return { state: state };\n },\n });\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { make } from \"@openlv/core\";\nimport type { EncryptionKey, SymmetricKey } from \"@openlv/core/encryption\";\nimport { parseEncryptionKey, validatePublicKeyHash } from \"@openlv/core/encryption\";\nimport { EventEmitter } from \"eventemitter3\";\nimport { match } from \"ts-pattern\";\nimport type { MaybePromise } from \"viem\";\n\nimport {\n parseSignalMessage,\n type PeerCapabilities,\n type SignalMessage,\n} from \"./messages.js\";\nimport type { SignalingChannel } from \"./protocol.js\";\nimport { log } from \"./utils/log.js\";\n\nexport * from \"./messages.js\";\nexport * from \"./protocol.js\";\n\nexport const SIGNAL_STATE = {\n STANDBY: \"standby\",\n CONNECTING: \"connecting\",\n READY: \"ready\",\n HANDSHAKE: \"handshake\",\n HANDSHAKE_PARTIAL: \"handshake-partial\",\n ENCRYPTED: \"encrypted\",\n ERROR: \"error\",\n} as const;\nexport type SignalState = (typeof SIGNAL_STATE)[keyof typeof SIGNAL_STATE];\n\nexport type SignalEventMap = {\n state_change: (state: SignalState) => void;\n message: (message: object) => void;\n};\n\nexport type SignalingProperties = {\n isHost: boolean;\n h: string;\n k?: SymmetricKey;\n rpDiscovered: (rpKey: string) => MaybePromise<void>;\n // Our capabilities, advertised to the peer during the handshake\n capabilities: PeerCapabilities;\n peerCapabilities: (capabilities: PeerCapabilities) => MaybePromise<void>;\n // Decrypt using our private key\n decrypt: (message: string) => MaybePromise<string>;\n // Encrypt to relying party\n encrypt: (message: string) => MaybePromise<string>;\n // our public key\n publicKey: EncryptionKey;\n canEncrypt: () => boolean;\n};\n\nexport type SignalingContext = {\n type: string;\n\n // Sending only works once keys are exchanged\n send: (message: object) => MaybePromise<void>;\n setup: () => MaybePromise<void>;\n teardown: () => MaybePromise<void>;\n\n getState: () => {\n state: SignalState;\n };\n};\nexport type SignalingLayer = EventEmitter<SignalEventMap> & SignalingContext;\nexport type SignalingLayerFunction = (\n properties: SignalingProperties,\n) => Promise<SignalingLayer>;\n\nexport const XR_PREFIX = \"x\";\nexport const XR_H_PREFIX = \"h\";\n\n/**\n * Relays are lossy (MQTT QoS 0, ntfy best-effort), so every handshake step\n * is re-sent on an interval until the state machine observes progress.\n * Receivers treat duplicates as no-ops, which keeps re-sends wire-compatible.\n */\nconst HANDSHAKE_RESEND_INTERVAL_MS = 2000;\nconst HANDSHAKE_TIMEOUT_MS = 30_000;\n\n/**\n * Base Signaling Layer implementation\n *\n * https://openlv.sh/api/signaling\n */\nexport const createSignalingLayer = (\n init: SignalingChannel,\n): SignalingLayerFunction => async ({\n canEncrypt,\n encrypt,\n decrypt,\n rpDiscovered,\n capabilities,\n peerCapabilities,\n h,\n k,\n publicKey,\n isHost,\n}: SignalingProperties) => {\n const emitter = new EventEmitter<SignalEventMap>();\n let state: SignalState = SIGNAL_STATE.STANDBY;\n let isPeerKeyRecorded = false;\n let isPeerCapabilitiesRecorded = false;\n let resendTimer: ReturnType<typeof setInterval> | undefined;\n let deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n const handshakeKey = k || undefined;\n\n const stopResend = () => {\n clearInterval(resendTimer);\n resendTimer = undefined;\n };\n const stopTimers = () => {\n stopResend();\n clearTimeout(deadlineTimer);\n deadlineTimer = undefined;\n };\n\n const setState = (_state: SignalState) => {\n if (state === _state) return;\n\n state = _state;\n\n if (_state === SIGNAL_STATE.ENCRYPTED || _state === SIGNAL_STATE.ERROR) {\n stopTimers();\n }\n\n emitter.emit(\"state_change\", _state);\n };\n\n const send = async (\n method: \"handshake\" | \"encrypted\",\n recipient: \"h\" | \"c\",\n payload: SignalMessage,\n ) => {\n const prefix = match(method)\n .with(\"handshake\", () => XR_H_PREFIX)\n .with(\"encrypted\", () => XR_PREFIX)\n .exhaustive();\n\n const message = await match(method)\n .with(\"handshake\", () => {\n if (!handshakeKey) return;\n\n return handshakeKey.encrypt(JSON.stringify(payload));\n })\n .with(\"encrypted\", () => {\n if (!canEncrypt()) return;\n\n return encrypt(JSON.stringify(payload));\n })\n .exhaustive();\n\n if (message === undefined) {\n throw new Error(`Cannot encrypt ${method} frame: key not available`);\n }\n\n await init.publish(prefix + recipient + message);\n };\n\n /**\n * Send a handshake step now, then keep re-sending it until the next step\n * (or teardown) supersedes it. Errors are logged and swallowed — the next\n * tick retries.\n */\n const sendRepeating = (\n method: \"handshake\" | \"encrypted\",\n recipient: \"h\" | \"c\",\n payload: SignalMessage,\n ) => {\n stopResend();\n\n const attempt = () => send(method, recipient, payload)\n .catch(error => log(\"handshake send failed, will retry\", error));\n\n resendTimer = setInterval(attempt, HANDSHAKE_RESEND_INTERVAL_MS);\n\n return attempt();\n };\n\n const startHandshakeDeadline = () => {\n if (deadlineTimer) return;\n\n deadlineTimer = setTimeout(() => {\n if (state === SIGNAL_STATE.ENCRYPTED) return;\n\n log(\"handshake timed out\");\n setState(SIGNAL_STATE.ERROR);\n }, HANDSHAKE_TIMEOUT_MS);\n };\n\n const recordPeerKey = async (key: string): Promise<boolean> => {\n // Never overwrite an established peer key — a second, different pubkey\n // mid-handshake is either a duplicate delivery or an injection attempt.\n if (isPeerKeyRecorded) return false;\n\n isPeerKeyRecorded = true;\n await rpDiscovered(key);\n\n return true;\n };\n\n const recordPeerCapabilities = async (\n peerCaps: PeerCapabilities,\n ): Promise<void> => {\n // Same overwrite guard as the peer key: only the first delivery counts,\n // re-sent duplicates are no-ops.\n if (isPeerCapabilitiesRecorded) return;\n\n isPeerCapabilitiesRecorded = true;\n await peerCapabilities(peerCaps);\n };\n\n const capabilitiesMessage = (): SignalMessage => ({\n type: \"capabilities\",\n payload: capabilities,\n timestamp: Date.now(),\n });\n const pubkeyMessage = (): SignalMessage => ({\n type: \"pubkey\",\n payload: { publicKey: publicKey.toString() },\n timestamp: Date.now(),\n });\n\n const handleHandshakeFrame = async (message: SignalMessage) => {\n await match({ msg: message, state, isHost })\n // Host: a client wants to connect. Re-entered on duplicate `flash`\n // deliveries (client re-sends until it hears our pubkey).\n .with({ msg: { type: \"flash\" }, state: SIGNAL_STATE.READY, isHost: true }, async () => {\n setState(SIGNAL_STATE.HANDSHAKE);\n startHandshakeDeadline();\n await sendRepeating(\"handshake\", \"c\", pubkeyMessage());\n })\n // Client: host announced its public key.\n .with({ msg: { type: \"pubkey\" }, isHost: false, state: SIGNAL_STATE.HANDSHAKE }, async ({ msg: { payload: messagePayload } }) => {\n try {\n const receivedKey = await parseEncryptionKey(messagePayload.publicKey);\n\n if (!await validatePublicKeyHash(receivedKey, h)) {\n setState(SIGNAL_STATE.ERROR);\n log(\"Received host public key does not match expected hash — possible tampering\");\n\n return;\n }\n }\n catch {\n setState(SIGNAL_STATE.ERROR);\n log(\"Failed to parse received host public key\");\n\n return;\n }\n\n if (!await recordPeerKey(messagePayload.publicKey)) return;\n\n setState(SIGNAL_STATE.HANDSHAKE_PARTIAL);\n\n return await sendRepeating(\"encrypted\", \"h\", pubkeyMessage());\n })\n .otherwise(() => {\n log(\"Ignoring handshake frame\", message.type, \"in state\", state);\n });\n };\n\n const handleEncryptedFrame = async (message: SignalMessage) => {\n await match({ msg: message, state, isHost })\n // Host: client responded with its public key.\n .with(\n { msg: { type: \"pubkey\" }, isHost: true, state: SIGNAL_STATE.HANDSHAKE },\n async ({ msg: { payload: messagePayload } }) => {\n if (!await recordPeerKey(messagePayload.publicKey)) return;\n\n setState(SIGNAL_STATE.HANDSHAKE_PARTIAL);\n\n return await sendRepeating(\"encrypted\", \"c\", capabilitiesMessage());\n },\n )\n .with(\n { msg: { type: \"capabilities\" }, state: SIGNAL_STATE.HANDSHAKE_PARTIAL },\n async ({ msg: { payload: messagePayload } }) => {\n await recordPeerCapabilities(messagePayload);\n setState(SIGNAL_STATE.ENCRYPTED);\n\n if (isHost) return;\n\n return await send(\"encrypted\", \"h\", capabilitiesMessage());\n },\n )\n // Client already encrypted, but the host is still re-sending its\n // capabilities (our final packet was lost): answer again so the host\n // can finish.\n .with(\n { msg: { type: \"capabilities\" }, state: SIGNAL_STATE.ENCRYPTED, isHost: false },\n async () => await send(\"encrypted\", \"h\", capabilitiesMessage()),\n )\n .with({ msg: { type: \"data\" }, state: SIGNAL_STATE.ENCRYPTED }, async () => {\n emitter.emit(\"message\", message.payload as object);\n })\n .otherwise(() => {\n log(\"Ignoring encrypted frame\", message.type, \"in state\", state);\n });\n };\n\n const handleReceive = async (payload: string) => {\n // The topic is public: anyone can publish garbage. Nothing in this\n // handler may throw past this boundary, otherwise a single malformed\n // frame becomes an unhandled rejection.\n try {\n const prefix = payload.slice(0, 1);\n const recipient = payload.slice(1, 2);\n const body = payload.slice(2);\n const isRecipient = (isHost ? \"h\" : \"c\") === recipient;\n\n if (!isRecipient) return;\n\n if (prefix === XR_H_PREFIX) {\n if (!handshakeKey) return;\n\n const message = parseSignalMessage(await handshakeKey.decrypt(body));\n\n if (message) await handleHandshakeFrame(message);\n }\n else if (prefix === XR_PREFIX) {\n const message = parseSignalMessage(await decrypt(body));\n\n if (message) await handleEncryptedFrame(message);\n }\n else {\n log(\"Dropping frame with unknown prefix\");\n }\n }\n catch (error) {\n log(\"Dropping undecryptable or malformed frame\", error);\n }\n };\n\n return make(emitter, {\n type: init.type,\n async setup() {\n setState(SIGNAL_STATE.CONNECTING);\n await init.setup();\n await init.subscribe(handleReceive);\n\n if (canEncrypt()) {\n setState(SIGNAL_STATE.ENCRYPTED);\n\n return;\n }\n\n setState(SIGNAL_STATE.READY);\n\n if (!isHost) {\n // Enter HANDSHAKE before publishing: the host's pubkey reply can\n // arrive while the publish is still in flight.\n setState(SIGNAL_STATE.HANDSHAKE);\n startHandshakeDeadline();\n await sendRepeating(\"handshake\", \"h\", {\n type: \"flash\",\n payload: {},\n timestamp: Date.now(),\n });\n }\n },\n async teardown() {\n log(\"teardown\");\n stopTimers();\n await init.teardown?.();\n },\n send(message: object) {\n if (!canEncrypt()) {\n return Promise.reject(\n new Error(\"Cannot encrypt message before keys are exchanged\"),\n );\n }\n\n const them = isHost ? \"c\" : \"h\";\n\n return send(\"encrypted\", them, {\n type: \"data\",\n payload: message,\n timestamp: Date.now(),\n });\n },\n getState() {\n return { state: state };\n },\n });\n};\n"]}
@@ -69,10 +69,10 @@ const providersByType = [
69
69
  // ],
70
70
  // ],
71
71
  ];
72
- const testSignalingLayer = async (layer, props) => {
72
+ const testSignalingLayer = async (layer, properties) => {
73
73
  const { encryptionKey: publicKey, decryptionKey } = await generateKeyPair();
74
74
  const h = hKey;
75
- const signalingLayer = await layer(props);
75
+ const signalingLayer = await layer(properties);
76
76
  const signalA = await signalingLayer({
77
77
  h,
78
78
  rpDiscovered: v => log("rpKey", v),
@@ -120,17 +120,17 @@ const testSignalingLayer = async (layer, props) => {
120
120
  await signalB.teardown();
121
121
  };
122
122
  const PROVIDER_TIMEOUT_MS = 5000;
123
- const testProvider = async (layer, props) => {
123
+ const testProvider = async (layer, properties) => {
124
124
  const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${PROVIDER_TIMEOUT_MS}ms`)), PROVIDER_TIMEOUT_MS));
125
125
  const startTime = performance.now();
126
126
  try {
127
- await Promise.race([testSignalingLayer(layer, props), timeout]);
127
+ await Promise.race([testSignalingLayer(layer, properties), timeout]);
128
128
  const duration = Math.round(performance.now() - startTime);
129
- return { url: props.url, ok: true, duration };
129
+ return { url: properties.url, ok: true, duration };
130
130
  }
131
131
  catch (error) {
132
132
  return {
133
- url: props.url,
133
+ url: properties.url,
134
134
  ok: false,
135
135
  error: error.message,
136
136
  };
@@ -146,7 +146,7 @@ const formatResults = (typeName, results) => {
146
146
  };
147
147
  describe.for(providersByType)("signaling: %s", ([typeName, providers]) => {
148
148
  it("should pass signaling messages between peers", { timeout: PROVIDER_TIMEOUT_MS * providers.length + 5000 }, async () => {
149
- const results = await Promise.all(providers.map(([, layer, props]) => testProvider(layer, props)));
149
+ const results = await Promise.all(providers.map(([, layer, properties]) => testProvider(layer, properties)));
150
150
  const summary = formatResults(typeName, results);
151
151
  console.log(`\n${summary}`);
152
152
  const passed = results.filter(r => r.ok).length;
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,MAAM,eAAe,GAGf;IACJ;QACE,MAAM;QACN;YACE;gBACE,gBAAgB;gBAChB,IAAI;gBACJ;oBACE,KAAK,EAAE,iBAAiB;oBACxB,GAAG,EAAE,oCAAoC;iBAC1C;aACF;YACD;gBACE,gBAAgB;gBAChB,IAAI;gBACJ;oBACE,KAAK,EAAE,iBAAiB;oBACxB,GAAG,EAAE,+BAA+B;iBACrC;aACF;YACD;gBACE,oBAAoB;gBACpB,IAAI;gBACJ;oBACE,KAAK,EAAE,iBAAiB;oBACxB,GAAG,EAAE,mCAAmC;iBACzC;aACF;YACD;gBACE,kBAAkB;gBAClB,IAAI;gBACJ;oBACE,KAAK,EAAE,iBAAiB;oBACxB,GAAG,EAAE,6BAA6B;iBACnC;aACF;SACF;KACF;IACD;QACE,MAAM;QACN;YACE;gBACE,SAAS;gBACT,IAAI;gBACJ,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,kBAAkB,EAAE;aACtD;YACD;gBACE,eAAe;gBACf,IAAI;gBACJ,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,wBAAwB,EAAE;aAC5D;SACF;KACF;IACD,0BAA0B;IAC1B,IAAI;IACJ,aAAa;IACb,MAAM;IACN,QAAQ;IACR,uBAAuB;IACvB,eAAe;IACf,oEAAoE;IACpE,SAAS;IACT,OAAO;IACP,KAAK;CACG,CAAC;AAEX,MAAM,kBAAkB,GAAG,KAAK,EAC9B,KAAwB,EACxB,KAA+B,EAChB,EAAE;IACjB,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,eAAe,EAAE,CAAC;IAC5E,MAAM,CAAC,GAAG,IAAI,CAAC;IAEf,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;QACnC,CAAC;QACD,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAClC,YAAY,EAAE,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE;QACtC,gBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACjD,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI;QACtB,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,OAAO,EAAE,aAAa,CAAC,OAAO;QAC9B,SAAS;QACT,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;QACnC,CAAC;QACD,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAClC,YAAY,EAAE,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE;QACtC,gBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACjD,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI;QACtB,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,OAAO,EAAE,aAAa,CAAC,OAAO;QAC9B,SAAS;QACT,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAE9B,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAEtD,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACrD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;YAChC,GAAG,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;YACjC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC3C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACrD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;YAChC,GAAG,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;YACjC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC3C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAEtC,MAAM,gBAAgB,CAAC;IAEvB,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAEtC,MAAM,gBAAgB,CAAC;IAEvB,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;IACzB,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAMjC,MAAM,YAAY,GAAG,KAAK,EACxB,KAAwB,EACxB,KAA+B,EACN,EAAE;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/C,UAAU,CACR,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iBAAiB,mBAAmB,IAAI,CAAC,CAAC,EACjE,mBAAmB,CACpB,CACF,CAAC;IAEF,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;QAE3D,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,EAAE,CAAC;QACb,OAAO;YACL,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,EAAE,EAAE,KAAK;YACT,KAAK,EAAG,KAAe,CAAC,OAAO;SAChC,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,QAAgB,EAAE,OAAyB,EAAU,EAAE;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAE7B,OAAO;QACL,GAAG,QAAQ,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,KAAK,EAAE;QAC/C,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CACjB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,CAClE;KACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC,CAAC;AAEF,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,EAAE;IACvE,EAAE,CACA,8CAA8C,EAC9C,EAAE,OAAO,EAAE,mBAAmB,GAAG,SAAS,CAAC,MAAM,GAAG,IAAI,EAAE,EAC1D,KAAK,IAAI,EAAE;QACT,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAChE,CAAC;QAEF,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEjD,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;QAE5B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;QAEhD,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import { generateKeyPair } from \"@openlv/core/encryption\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { mqtt } from \"./mqtt/index.js\";\nimport { ntfy } from \"./ntfy/index.js\";\nimport type { SignalingProtocol, SignalingProtocolOptions } from \"./protocol.js\";\nimport { log } from \"./utils/log.js\";\n\nconst hKey = \"test\";\n\nconst providersByType: readonly [\n string,\n readonly [string, SignalingProtocol, SignalingProtocolOptions][],\n][] = [\n [\n \"mqtt\",\n [\n [\n \"mqtt-dashboard\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"wss://mqtt-dashboard.com:8884/mqtt\",\n },\n ],\n [\n \"broker.emqx.io\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"ws://broker.emqx.io:8083/mqtt\",\n },\n ],\n [\n \"test.mosquitto.org\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"ws://test.mosquitto.org:8080/mqtt\",\n },\n ],\n [\n \"broker.itdata.nu\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"wss://broker.itdata.nu/mqtt\",\n },\n ],\n ],\n ],\n [\n \"ntfy\",\n [\n [\n \"ntfy.sh\",\n ntfy,\n { topic: \"mytesttopic1111\", url: \"https://ntfy.sh/\" },\n ],\n [\n \"ntfy.envs.net\",\n ntfy,\n { topic: \"mytesttopic1111\", url: \"https://ntfy.envs.net/\" },\n ],\n ],\n ],\n // TODO: GunDB is disabled\n // [\n // \"gundb\",\n // [\n // [\n // \"try.axe.eco\",\n // gundb,\n // { topic: \"mytesttopic1111\", url: \"wss://try.axe.eco/gun\" },\n // ],\n // ],\n // ],\n] as const;\n\nconst testSignalingLayer = async (\n layer: SignalingProtocol,\n props: SignalingProtocolOptions,\n): Promise<void> => {\n const { encryptionKey: publicKey, decryptionKey } = await generateKeyPair();\n const h = hKey;\n\n const signalingLayer = await layer(props);\n const signalA = await signalingLayer({\n h,\n rpDiscovered: v => log(\"rpKey\", v),\n capabilities: { transports: [\"wrtc\"] },\n peerCapabilities: v => log(\"peerCapabilities\", v),\n canEncrypt: () => true,\n encrypt: publicKey.encrypt,\n decrypt: decryptionKey.decrypt,\n publicKey,\n isHost: true,\n });\n const signalB = await signalingLayer({\n h,\n rpDiscovered: v => log(\"rpKey\", v),\n capabilities: { transports: [\"wrtc\"] },\n peerCapabilities: v => log(\"peerCapabilities\", v),\n canEncrypt: () => true,\n encrypt: publicKey.encrypt,\n decrypt: decryptionKey.decrypt,\n publicKey,\n isHost: false,\n });\n\n expect(signalA).toBeDefined();\n expect(signalB).toBeDefined();\n\n await Promise.all([signalA.setup(), signalB.setup()]);\n\n const messageReceivedA = new Promise<void>((resolve) => {\n signalA.on(\"message\", (message) => {\n log(\"messageReceivedA\", message);\n expect(message).toEqual({ data: \"test2\" });\n resolve();\n });\n });\n const messageReceivedB = new Promise<void>((resolve) => {\n signalB.on(\"message\", (message) => {\n log(\"messageReceivedB\", message);\n expect(message).toEqual({ data: \"test1\" });\n resolve();\n });\n });\n\n await signalA.send({ data: \"test1\" });\n\n await messageReceivedB;\n\n await signalB.send({ data: \"test2\" });\n\n await messageReceivedA;\n\n await signalA.teardown();\n await signalB.teardown();\n};\n\nconst PROVIDER_TIMEOUT_MS = 5000;\n\ntype ProviderResult =\n | { url: string; ok: true; duration: number; }\n | { url: string; ok: false; error: string; };\n\nconst testProvider = async (\n layer: SignalingProtocol,\n props: SignalingProtocolOptions,\n): Promise<ProviderResult> => {\n const timeout = new Promise<never>((_, reject) =>\n setTimeout(\n () => reject(new Error(`Timeout after ${PROVIDER_TIMEOUT_MS}ms`)),\n PROVIDER_TIMEOUT_MS,\n ),\n );\n\n const startTime = performance.now();\n\n try {\n await Promise.race([testSignalingLayer(layer, props), timeout]);\n const duration = Math.round(performance.now() - startTime);\n\n return { url: props.url, ok: true, duration };\n }\n catch (error) {\n return {\n url: props.url,\n ok: false,\n error: (error as Error).message,\n };\n }\n};\n\nconst formatResults = (typeName: string, results: ProviderResult[]): string => {\n const passed = results.filter(r => r.ok).length;\n const total = results.length;\n\n return [\n `${typeName.toUpperCase()}: ${passed}/${total}`,\n ...results.map(r =>\n (r.ok ? `✓ ${r.url} ${r.duration}ms` : `✗ ${r.url} - ${r.error}`),\n ),\n ].join(\"\\n\");\n};\n\ndescribe.for(providersByType)(\"signaling: %s\", ([typeName, providers]) => {\n it(\n \"should pass signaling messages between peers\",\n { timeout: PROVIDER_TIMEOUT_MS * providers.length + 5000 },\n async () => {\n const results = await Promise.all(\n providers.map(([, layer, props]) => testProvider(layer, props)),\n );\n\n const summary = formatResults(typeName, results);\n\n console.log(`\\n${summary}`);\n\n const passed = results.filter(r => r.ok).length;\n\n expect(passed).toBeGreaterThan(0);\n },\n );\n});\n"]}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { generateKeyPair } from \"@openlv/core/encryption\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { mqtt } from \"./mqtt/index.js\";\nimport { ntfy } from \"./ntfy/index.js\";\nimport type { SignalingProtocol, SignalingProtocolOptions } from \"./protocol.js\";\nimport { log } from \"./utils/log.js\";\n\nconst hKey = \"test\";\n\nconst providersByType: readonly [\n string,\n readonly [string, SignalingProtocol, SignalingProtocolOptions][],\n][] = [\n [\n \"mqtt\",\n [\n [\n \"mqtt-dashboard\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"wss://mqtt-dashboard.com:8884/mqtt\",\n },\n ],\n [\n \"broker.emqx.io\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"ws://broker.emqx.io:8083/mqtt\",\n },\n ],\n [\n \"test.mosquitto.org\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"ws://test.mosquitto.org:8080/mqtt\",\n },\n ],\n [\n \"broker.itdata.nu\",\n mqtt,\n {\n topic: \"mytesttopic1111\",\n url: \"wss://broker.itdata.nu/mqtt\",\n },\n ],\n ],\n ],\n [\n \"ntfy\",\n [\n [\n \"ntfy.sh\",\n ntfy,\n { topic: \"mytesttopic1111\", url: \"https://ntfy.sh/\" },\n ],\n [\n \"ntfy.envs.net\",\n ntfy,\n { topic: \"mytesttopic1111\", url: \"https://ntfy.envs.net/\" },\n ],\n ],\n ],\n // TODO: GunDB is disabled\n // [\n // \"gundb\",\n // [\n // [\n // \"try.axe.eco\",\n // gundb,\n // { topic: \"mytesttopic1111\", url: \"wss://try.axe.eco/gun\" },\n // ],\n // ],\n // ],\n] as const;\n\nconst testSignalingLayer = async (\n layer: SignalingProtocol,\n properties: SignalingProtocolOptions,\n): Promise<void> => {\n const { encryptionKey: publicKey, decryptionKey } = await generateKeyPair();\n const h = hKey;\n\n const signalingLayer = await layer(properties);\n const signalA = await signalingLayer({\n h,\n rpDiscovered: v => log(\"rpKey\", v),\n capabilities: { transports: [\"wrtc\"] },\n peerCapabilities: v => log(\"peerCapabilities\", v),\n canEncrypt: () => true,\n encrypt: publicKey.encrypt,\n decrypt: decryptionKey.decrypt,\n publicKey,\n isHost: true,\n });\n const signalB = await signalingLayer({\n h,\n rpDiscovered: v => log(\"rpKey\", v),\n capabilities: { transports: [\"wrtc\"] },\n peerCapabilities: v => log(\"peerCapabilities\", v),\n canEncrypt: () => true,\n encrypt: publicKey.encrypt,\n decrypt: decryptionKey.decrypt,\n publicKey,\n isHost: false,\n });\n\n expect(signalA).toBeDefined();\n expect(signalB).toBeDefined();\n\n await Promise.all([signalA.setup(), signalB.setup()]);\n\n const messageReceivedA = new Promise<void>((resolve) => {\n signalA.on(\"message\", (message) => {\n log(\"messageReceivedA\", message);\n expect(message).toEqual({ data: \"test2\" });\n resolve();\n });\n });\n const messageReceivedB = new Promise<void>((resolve) => {\n signalB.on(\"message\", (message) => {\n log(\"messageReceivedB\", message);\n expect(message).toEqual({ data: \"test1\" });\n resolve();\n });\n });\n\n await signalA.send({ data: \"test1\" });\n\n await messageReceivedB;\n\n await signalB.send({ data: \"test2\" });\n\n await messageReceivedA;\n\n await signalA.teardown();\n await signalB.teardown();\n};\n\nconst PROVIDER_TIMEOUT_MS = 5000;\n\ntype ProviderResult\n = | { url: string; ok: true; duration: number; }\n | { url: string; ok: false; error: string; };\n\nconst testProvider = async (\n layer: SignalingProtocol,\n properties: SignalingProtocolOptions,\n): Promise<ProviderResult> => {\n const timeout = new Promise<never>((_, reject) =>\n setTimeout(\n () => reject(new Error(`Timeout after ${PROVIDER_TIMEOUT_MS}ms`)),\n PROVIDER_TIMEOUT_MS,\n ),\n );\n\n const startTime = performance.now();\n\n try {\n await Promise.race([testSignalingLayer(layer, properties), timeout]);\n const duration = Math.round(performance.now() - startTime);\n\n return { url: properties.url, ok: true, duration };\n }\n catch (error) {\n return {\n url: properties.url,\n ok: false,\n error: (error as Error).message,\n };\n }\n};\n\nconst formatResults = (typeName: string, results: ProviderResult[]): string => {\n const passed = results.filter(r => r.ok).length;\n const total = results.length;\n\n return [\n `${typeName.toUpperCase()}: ${passed}/${total}`,\n ...results.map(r =>\n (r.ok ? `✓ ${r.url} ${r.duration}ms` : `✗ ${r.url} - ${r.error}`),\n ),\n ].join(\"\\n\");\n};\n\ndescribe.for(providersByType)(\"signaling: %s\", ([typeName, providers]) => {\n it(\n \"should pass signaling messages between peers\",\n { timeout: PROVIDER_TIMEOUT_MS * providers.length + 5000 },\n async () => {\n const results = await Promise.all(\n providers.map(([, layer, properties]) => testProvider(layer, properties)),\n );\n\n const summary = formatResults(typeName, results);\n\n console.log(`\\n${summary}`);\n\n const passed = results.filter(r => r.ok).length;\n\n expect(passed).toBeGreaterThan(0);\n },\n );\n});\n"]}
package/dist/messages.js CHANGED
@@ -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"]}
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.1.3",
4
4
  "keywords": [
5
5
  "open-lavatory"
6
6
  ],
@@ -59,7 +59,7 @@
59
59
  "gun": "^0.2020.1241",
60
60
  "websocket-mqtt": "0.0.7",
61
61
  "ts-pattern": "^5",
62
- "@openlv/core": "0.1.1"
62
+ "@openlv/core": "0.1.2"
63
63
  },
64
64
  "devDependencies": {
65
65
  "typescript": "^5",