@vielzeug/pulse 3.0.0 → 26.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/_connection.cjs +1 -1
  3. package/dist/_connection.cjs.map +1 -1
  4. package/dist/_connection.js +1 -106
  5. package/dist/_connection.js.map +1 -1
  6. package/dist/_store.cjs +1 -1
  7. package/dist/_store.cjs.map +1 -1
  8. package/dist/_store.d.ts +2 -2
  9. package/dist/_store.d.ts.map +1 -1
  10. package/dist/_store.js +1 -23
  11. package/dist/_store.js.map +1 -1
  12. package/dist/_utils.cjs +1 -1
  13. package/dist/_utils.cjs.map +1 -1
  14. package/dist/_utils.js +1 -24
  15. package/dist/_utils.js.map +1 -1
  16. package/dist/_wait.cjs +1 -1
  17. package/dist/_wait.cjs.map +1 -1
  18. package/dist/_wait.js +1 -27
  19. package/dist/_wait.js.map +1 -1
  20. package/dist/channel.cjs +1 -1
  21. package/dist/channel.cjs.map +1 -1
  22. package/dist/channel.js +1 -56
  23. package/dist/channel.js.map +1 -1
  24. package/dist/errors.cjs +1 -1
  25. package/dist/errors.cjs.map +1 -1
  26. package/dist/errors.js +1 -37
  27. package/dist/errors.js.map +1 -1
  28. package/dist/heartbeat.cjs +1 -1
  29. package/dist/heartbeat.cjs.map +1 -1
  30. package/dist/heartbeat.js +1 -35
  31. package/dist/heartbeat.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -3
  35. package/dist/protocol.cjs +1 -1
  36. package/dist/protocol.cjs.map +1 -1
  37. package/dist/protocol.js +1 -121
  38. package/dist/protocol.js.map +1 -1
  39. package/dist/pulse.cjs +1 -1
  40. package/dist/pulse.cjs.map +1 -1
  41. package/dist/pulse.d.ts.map +1 -1
  42. package/dist/pulse.iife.js +1 -1
  43. package/dist/pulse.iife.js.map +1 -1
  44. package/dist/pulse.js +1 -1
  45. package/dist/pulse.js.map +1 -1
  46. package/dist/room.cjs +1 -1
  47. package/dist/room.cjs.map +1 -1
  48. package/dist/room.js +1 -255
  49. package/dist/room.js.map +1 -1
  50. package/dist/testing/index.d.ts +10 -0
  51. package/dist/testing/index.d.ts.map +1 -0
  52. package/dist/testing/mock-websocket.cjs +2 -0
  53. package/dist/testing/mock-websocket.cjs.map +1 -0
  54. package/dist/testing/mock-websocket.d.ts +48 -0
  55. package/dist/testing/mock-websocket.d.ts.map +1 -0
  56. package/dist/testing/mock-websocket.js +2 -0
  57. package/dist/testing/mock-websocket.js.map +1 -0
  58. package/dist/testing.cjs +1 -0
  59. package/dist/testing.js +1 -0
  60. package/dist/types.d.ts +7 -7
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +20 -5
package/dist/errors.js CHANGED
@@ -1,38 +1,2 @@
1
- //#region src/errors.ts
2
- var e = class extends Error {
3
- constructor(e, t) {
4
- super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
- }
6
- }, t = class extends e {
7
- url;
8
- constructor(e, t, n) {
9
- super(e, n), this.url = t;
10
- }
11
- }, n = class extends e {
12
- event;
13
- constructor(e, t) {
14
- super(`Timed out waiting for "${e}"`, t), this.event = e;
15
- }
16
- }, r = class extends e {
17
- room;
18
- constructor(e, t) {
19
- super(`Timed out waiting for room "${e}"`, t), this.room = e;
20
- }
21
- }, i = class extends e {
22
- constructor(e) {
23
- super("Aborted", e);
24
- }
25
- }, a = class extends e {
26
- constructor(e = "Pulse", t) {
27
- super(`${e} instance is disposed`, t);
28
- }
29
- }, o = class extends e {
30
- raw;
31
- constructor(e, t, n) {
32
- super(e, n), this.raw = t;
33
- }
34
- };
35
- //#endregion
36
- export { i as PulseAbortError, t as PulseConnectionError, a as PulseDisposedError, e as PulseError, o as PulseProtocolError, r as PulseRoomTimeoutError, n as PulseTimeoutError };
37
-
1
+ var PulseError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},PulseConnectionError=class extends PulseError{url;constructor(e,t,n){super(e,n),this.url=t}},PulseTimeoutError=class extends PulseError{event;constructor(e,t){super(`Timed out waiting for "${e}"`,t),this.event=e}},PulseRoomTimeoutError=class extends PulseError{room;constructor(e,t){super(`Timed out waiting for room "${e}"`,t),this.room=e}},PulseAbortError=class extends PulseError{constructor(e){super(`Aborted`,e)}},PulseDisposedError=class extends PulseError{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},PulseProtocolError=class extends PulseError{raw;constructor(e,t,n){super(e,n),this.raw=t}};export{PulseAbortError,PulseConnectionError,PulseDisposedError,PulseError,PulseProtocolError,PulseRoomTimeoutError,PulseTimeoutError};
38
2
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` times out before the server event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when a room scope's `joined` promise times out before the server\n * confirms membership, or when an AbortSignal fires before confirmation.\n */\nexport class PulseRoomTimeoutError extends PulseError {\n readonly room: string;\n\n constructor(room: string, opts?: ErrorOptions) {\n super(`Timed out waiting for room \"${room}\"`, opts);\n this.room = room;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires,\n * or when a room scope's join is aborted before the server confirms.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n"],"mappings":";AAIA,IAAa,IAAb,cAAgC,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAMa,IAAb,cAA0C,EAAW;CACnD;CAEA,YAAY,GAAiB,GAAa,GAAqB;EAE7D,AADA,MAAM,GAAS,CAAI,GACnB,KAAK,MAAM;CACb;AACF,GAKa,IAAb,cAAuC,EAAW;CAChD;CAEA,YAAY,GAAe,GAAqB;EAE9C,AADA,MAAM,0BAA0B,EAAM,IAAI,CAAI,GAC9C,KAAK,QAAQ;CACf;AACF,GAMa,IAAb,cAA2C,EAAW;CACpD;CAEA,YAAY,GAAc,GAAqB;EAE7C,AADA,MAAM,+BAA+B,EAAK,IAAI,CAAI,GAClD,KAAK,OAAO;CACd;AACF,GAMa,IAAb,cAAqC,EAAW;CAC9C,YAAY,GAAqB;EAC/B,MAAM,WAAW,CAAI;CACvB;AACF,GAKa,IAAb,cAAwC,EAAW;CACjD,YAAY,IAAS,SAAS,GAAqB;EACjD,MAAM,GAAG,EAAO,wBAAwB,CAAI;CAC9C;AACF,GAMa,IAAb,cAAwC,EAAW;CACjD;CAEA,YAAY,GAAiB,GAAe,GAAqB;EAE/D,AADA,MAAM,GAAS,CAAI,GACnB,KAAK,MAAM;CACb;AACF"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Base class for all pulse errors.\n * Use `instanceof PulseError` to catch any pulse-originated error in one branch.\n */\nexport class PulseError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when a WebSocket connection cannot be established or is lost\n * without triggering auto-reconnect (budget exhausted, or reconnect disabled).\n */\nexport class PulseConnectionError extends PulseError {\n readonly url: string;\n\n constructor(message: string, url: string, opts?: ErrorOptions) {\n super(message, opts);\n this.url = url;\n }\n}\n\n/**\n * Thrown when `wait()` times out before the server event arrives.\n */\nexport class PulseTimeoutError extends PulseError {\n readonly event: string;\n\n constructor(event: string, opts?: ErrorOptions) {\n super(`Timed out waiting for \"${event}\"`, opts);\n this.event = event;\n }\n}\n\n/**\n * Thrown when a room scope's `joined` promise times out before the server\n * confirms membership, or when an AbortSignal fires before confirmation.\n */\nexport class PulseRoomTimeoutError extends PulseError {\n readonly room: string;\n\n constructor(room: string, opts?: ErrorOptions) {\n super(`Timed out waiting for room \"${room}\"`, opts);\n this.room = room;\n }\n}\n\n/**\n * Thrown when `wait()` is aborted via an AbortSignal before the event fires,\n * or when a room scope's join is aborted before the server confirms.\n */\nexport class PulseAbortError extends PulseError {\n constructor(opts?: ErrorOptions) {\n super('Aborted', opts);\n }\n}\n\n/**\n * Thrown when a method is called on a disposed Pulse instance or channel.\n */\nexport class PulseDisposedError extends PulseError {\n constructor(target = 'Pulse', opts?: ErrorOptions) {\n super(`${target} instance is disposed`, opts);\n }\n}\n\n/**\n * Thrown when the server sends a frame that cannot be parsed or violates\n * the wire protocol schema.\n */\nexport class PulseProtocolError extends PulseError {\n readonly raw: unknown;\n\n constructor(message: string, raw?: unknown, opts?: ErrorOptions) {\n super(message, opts);\n this.raw = raw;\n }\n}\n"],"mappings":"AAIA,IAAa,WAAb,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAMa,qBAAb,cAA0C,UAAW,CACnD,IAEA,YAAY,EAAiB,EAAa,EAAqB,CAC7D,MAAM,EAAS,CAAI,EACnB,KAAK,IAAM,CACb,CACF,EAKa,kBAAb,cAAuC,UAAW,CAChD,MAEA,YAAY,EAAe,EAAqB,CAC9C,MAAM,0BAA0B,EAAM,GAAI,CAAI,EAC9C,KAAK,MAAQ,CACf,CACF,EAMa,sBAAb,cAA2C,UAAW,CACpD,KAEA,YAAY,EAAc,EAAqB,CAC7C,MAAM,+BAA+B,EAAK,GAAI,CAAI,EAClD,KAAK,KAAO,CACd,CACF,EAMa,gBAAb,cAAqC,UAAW,CAC9C,YAAY,EAAqB,CAC/B,MAAM,UAAW,CAAI,CACvB,CACF,EAKa,mBAAb,cAAwC,UAAW,CACjD,YAAY,EAAS,QAAS,EAAqB,CACjD,MAAM,GAAG,EAAO,uBAAwB,CAAI,CAC9C,CACF,EAMa,mBAAb,cAAwC,UAAW,CACjD,IAEA,YAAY,EAAiB,EAAe,EAAqB,CAC/D,MAAM,EAAS,CAAI,EACnB,KAAK,IAAM,CACb,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("./protocol.cjs");var t=3e4,n=5e3;function r(r,i,a){if(!r)return{onPong(){},start(){},stop(){}};let o=r===!0?{}:r,s=o.interval??t,c=o.timeout??n,l,u,d=!1;function f(){l=setTimeout(()=>{i(e.encode({ts:Date.now(),type:`ping`})),u=setTimeout(()=>{a()},c)},s)}return{onPong(){clearTimeout(u),u=void 0,d&&f()},start(){d=!0,clearTimeout(l),clearTimeout(u),u=void 0,f()},stop(){d=!1,clearTimeout(l),clearTimeout(u),l=void 0,u=void 0}}}exports.createHeartbeat=r;
1
+ const e=require("./protocol.cjs");var t=3e4,n=5e3;function createHeartbeat(r,i,a){if(!r)return{onPong(){},start(){},stop(){}};let o=r===!0?{}:r,s=o.interval??t,c=o.timeout??n,l,u,d=!1;function schedulePing(){l=setTimeout(()=>{i(e.encode({ts:Date.now(),type:`ping`})),u=setTimeout(()=>{a()},c)},s)}return{onPong(){clearTimeout(u),u=void 0,d&&schedulePing()},start(){d=!0,clearTimeout(l),clearTimeout(u),u=void 0,schedulePing()},stop(){d=!1,clearTimeout(l),clearTimeout(u),l=void 0,u=void 0}}}exports.createHeartbeat=createHeartbeat;
2
2
  //# sourceMappingURL=heartbeat.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat.cjs","names":[],"sources":["../src/heartbeat.ts"],"sourcesContent":["import { encode } from './protocol';\nimport type { HeartbeatOptions } from './types';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n"],"mappings":"kCAGA,IAAM,EAAmB,IACnB,EAAkB,IAgBxB,SAAgB,EACd,EACA,EACA,EACiB,CACjB,GAAI,CAAC,EACH,MAAO,CAAE,QAAS,CAAC,EAAG,OAAQ,CAAC,EAAG,MAAO,CAAC,CAAE,EAG9C,IAAM,EAA4B,IAAS,GAAO,CAAC,EAAI,EACjD,EAAW,EAAQ,UAAY,EAC/B,EAAU,EAAQ,SAAW,EAE/B,EACA,EACA,EAAU,GAEd,SAAS,GAAqB,CAC5B,EAAY,eAAiB,CAG3B,EAAK,EAAA,OAAO,CAAE,GAFH,KAAK,IAEF,EAAI,KAAM,MAAO,CAAC,CAAC,EAEjC,EAAY,eAAiB,CAC3B,EAAO,CACT,EAAG,CAAO,CACZ,EAAG,CAAQ,CACb,CAEA,MAAO,CACL,QAAS,CACP,aAAa,CAAS,EACtB,EAAY,IAAA,GAER,GAAS,EAAa,CAC5B,EAEA,OAAQ,CACN,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,EAAa,CACf,EAEA,MAAO,CACL,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,EAAY,IAAA,EACd,CACF,CACF"}
1
+ {"version":3,"file":"heartbeat.cjs","names":[],"sources":["../src/heartbeat.ts"],"sourcesContent":["import { encode } from './protocol';\nimport type { HeartbeatOptions } from './types';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n"],"mappings":"kCAGA,IAAM,EAAmB,IACnB,EAAkB,IAgBxB,SAAgB,gBACd,EACA,EACA,EACiB,CACjB,GAAI,CAAC,EACH,MAAO,CAAE,QAAS,CAAC,EAAG,OAAQ,CAAC,EAAG,MAAO,CAAC,CAAE,EAG9C,IAAM,EAA4B,IAAS,GAAO,CAAC,EAAI,EACjD,EAAW,EAAQ,UAAY,EAC/B,EAAU,EAAQ,SAAW,EAE/B,EACA,EACA,EAAU,GAEd,SAAS,cAAqB,CAC5B,EAAY,eAAiB,CAG3B,EAAK,EAAA,OAAO,CAAE,GAFH,KAAK,IAEF,EAAI,KAAM,MAAO,CAAC,CAAC,EAEjC,EAAY,eAAiB,CAC3B,EAAO,CACT,EAAG,CAAO,CACZ,EAAG,CAAQ,CACb,CAEA,MAAO,CACL,QAAS,CACP,aAAa,CAAS,EACtB,EAAY,IAAA,GAER,GAAS,aAAa,CAC5B,EAEA,OAAQ,CACN,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,aAAa,CACf,EAEA,MAAO,CACL,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,EAAY,IAAA,EACd,CACF,CACF"}
package/dist/heartbeat.js CHANGED
@@ -1,36 +1,2 @@
1
- import { encode as e } from "./protocol.js";
2
- //#region src/heartbeat.ts
3
- var t = 3e4, n = 5e3;
4
- function r(r, i, a) {
5
- if (!r) return {
6
- onPong() {},
7
- start() {},
8
- stop() {}
9
- };
10
- let o = r === !0 ? {} : r, s = o.interval ?? t, c = o.timeout ?? n, l, u, d = !1;
11
- function f() {
12
- l = setTimeout(() => {
13
- i(e({
14
- ts: Date.now(),
15
- type: "ping"
16
- })), u = setTimeout(() => {
17
- a();
18
- }, c);
19
- }, s);
20
- }
21
- return {
22
- onPong() {
23
- clearTimeout(u), u = void 0, d && f();
24
- },
25
- start() {
26
- d = !0, clearTimeout(l), clearTimeout(u), u = void 0, f();
27
- },
28
- stop() {
29
- d = !1, clearTimeout(l), clearTimeout(u), l = void 0, u = void 0;
30
- }
31
- };
32
- }
33
- //#endregion
34
- export { r as createHeartbeat };
35
-
1
+ import{encode as e}from"./protocol.js";var t=3e4,n=5e3;function createHeartbeat(r,i,a){if(!r)return{onPong(){},start(){},stop(){}};let o=r===!0?{}:r,s=o.interval??t,c=o.timeout??n,l,u,d=!1;function schedulePing(){l=setTimeout(()=>{i(e({ts:Date.now(),type:`ping`})),u=setTimeout(()=>{a()},c)},s)}return{onPong(){clearTimeout(u),u=void 0,d&&schedulePing()},start(){d=!0,clearTimeout(l),clearTimeout(u),u=void 0,schedulePing()},stop(){d=!1,clearTimeout(l),clearTimeout(u),l=void 0,u=void 0}}}export{createHeartbeat};
36
2
  //# sourceMappingURL=heartbeat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"heartbeat.js","names":[],"sources":["../src/heartbeat.ts"],"sourcesContent":["import { encode } from './protocol';\nimport type { HeartbeatOptions } from './types';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n"],"mappings":";;AAGA,IAAM,IAAmB,KACnB,IAAkB;AAgBxB,SAAgB,EACd,GACA,GACA,GACiB;CACjB,IAAI,CAAC,GACH,OAAO;EAAE,SAAS,CAAC;EAAG,QAAQ,CAAC;EAAG,OAAO,CAAC;CAAE;CAG9C,IAAM,IAA4B,MAAS,KAAO,CAAC,IAAI,GACjD,IAAW,EAAQ,YAAY,GAC/B,IAAU,EAAQ,WAAW,GAE/B,GACA,GACA,IAAU;CAEd,SAAS,IAAqB;EAC5B,IAAY,iBAAiB;GAK3B,AAFA,EAAK,EAAO;IAAE,IAFH,KAAK,IAEF;IAAI,MAAM;GAAO,CAAC,CAAC,GAEjC,IAAY,iBAAiB;IAC3B,EAAO;GACT,GAAG,CAAO;EACZ,GAAG,CAAQ;CACb;CAEA,OAAO;EACL,SAAS;GAIP,AAHA,aAAa,CAAS,GACtB,IAAY,KAAA,GAER,KAAS,EAAa;EAC5B;EAEA,QAAQ;GAKN,AAJA,IAAU,IACV,aAAa,CAAS,GACtB,aAAa,CAAS,GACtB,IAAY,KAAA,GACZ,EAAa;EACf;EAEA,OAAO;GAKL,AAJA,IAAU,IACV,aAAa,CAAS,GACtB,aAAa,CAAS,GACtB,IAAY,KAAA,GACZ,IAAY,KAAA;EACd;CACF;AACF"}
1
+ {"version":3,"file":"heartbeat.js","names":[],"sources":["../src/heartbeat.ts"],"sourcesContent":["import { encode } from './protocol';\nimport type { HeartbeatOptions } from './types';\n\nconst DEFAULT_INTERVAL = 30_000;\nconst DEFAULT_TIMEOUT = 5_000;\n\nexport type HeartbeatHandle = {\n /** Called when a `pong` frame is received. Resets the timeout timer. */\n onPong(): void;\n /** Start the heartbeat loop. */\n start(): void;\n /** Stop the heartbeat loop and cancel any pending timers. */\n stop(): void;\n};\n\n/**\n * Create a heartbeat manager that sends periodic pings and expects pong replies.\n * Calls `onDead` when a pong is not received within the timeout window.\n * @internal\n */\nexport function createHeartbeat(\n opts: boolean | HeartbeatOptions | undefined,\n send: (frame: string) => void,\n onDead: () => void,\n): HeartbeatHandle {\n if (!opts) {\n return { onPong() {}, start() {}, stop() {} };\n }\n\n const options: HeartbeatOptions = opts === true ? {} : opts;\n const interval = options.interval ?? DEFAULT_INTERVAL;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let pingTimer: ReturnType<typeof setTimeout> | undefined;\n let pongTimer: ReturnType<typeof setTimeout> | undefined;\n let running = false;\n\n function schedulePing(): void {\n pingTimer = setTimeout(() => {\n const ts = Date.now();\n\n send(encode({ ts, type: 'ping' }));\n\n pongTimer = setTimeout(() => {\n onDead();\n }, timeout);\n }, interval);\n }\n\n return {\n onPong() {\n clearTimeout(pongTimer);\n pongTimer = undefined;\n\n if (running) schedulePing();\n },\n\n start() {\n running = true;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pongTimer = undefined;\n schedulePing();\n },\n\n stop() {\n running = false;\n clearTimeout(pingTimer);\n clearTimeout(pongTimer);\n pingTimer = undefined;\n pongTimer = undefined;\n },\n };\n}\n"],"mappings":"uCAGA,IAAM,EAAmB,IACnB,EAAkB,IAgBxB,SAAgB,gBACd,EACA,EACA,EACiB,CACjB,GAAI,CAAC,EACH,MAAO,CAAE,QAAS,CAAC,EAAG,OAAQ,CAAC,EAAG,MAAO,CAAC,CAAE,EAG9C,IAAM,EAA4B,IAAS,GAAO,CAAC,EAAI,EACjD,EAAW,EAAQ,UAAY,EAC/B,EAAU,EAAQ,SAAW,EAE/B,EACA,EACA,EAAU,GAEd,SAAS,cAAqB,CAC5B,EAAY,eAAiB,CAG3B,EAAK,EAAO,CAAE,GAFH,KAAK,IAEF,EAAI,KAAM,MAAO,CAAC,CAAC,EAEjC,EAAY,eAAiB,CAC3B,EAAO,CACT,EAAG,CAAO,CACZ,EAAG,CAAQ,CACb,CAEA,MAAO,CACL,QAAS,CACP,aAAa,CAAS,EACtB,EAAY,IAAA,GAER,GAAS,aAAa,CAC5B,EAEA,OAAQ,CACN,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,aAAa,CACf,EAEA,MAAO,CACL,EAAU,GACV,aAAa,CAAS,EACtB,aAAa,CAAS,EACtB,EAAY,IAAA,GACZ,EAAY,IAAA,EACd,CACF,CACF"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { PulseAbortError, PulseConnectionError, PulseDisposedError, PulseError, PulseProtocolError, PulseRoomTimeoutError, PulseTimeoutError, } from './errors';
2
2
  export { createPulse } from './pulse';
3
- export type { ChannelDefinition, ChannelDefinitions, ClientEvents, EventKey, ExternalStore, HeartbeatOptions, MessageMap, OutgoingMessage, OutgoingTransform, PresenceRoomScope, Pulse, PulseChannel, PulseEvent, PulseOptions, PulseSchema, PulseStatus, ReconnectOptions, RoomDefinition, RoomDefinitions, RoomMap, RoomOptions, RoomScope, RoomScopeBase, ServerEvents, Unsubscribe, } from './types';
3
+ export type { ChannelDefinition, ChannelDefinitions, ClientEvents, EventKey, HeartbeatOptions, MessageMap, OutgoingMessage, OutgoingTransform, PresenceRoomScope, Pulse, PulseChannel, PulseEvent, PulseOptions, PulseSchema, PulseStatus, ReconnectOptions, RoomDefinition, RoomDefinitions, RoomMap, RoomOptions, RoomScope, RoomScopeBase, ServerEvents, Subscribable, Unsubscribe, } from './types';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,OAAO,EACP,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,OAAO,EACP,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1 @@
1
- import { PulseAbortError as e, PulseConnectionError as t, PulseDisposedError as n, PulseError as r, PulseProtocolError as i, PulseRoomTimeoutError as a, PulseTimeoutError as o } from "./errors.js";
2
- import { createPulse as s } from "./pulse.js";
3
- export { e as PulseAbortError, t as PulseConnectionError, n as PulseDisposedError, r as PulseError, i as PulseProtocolError, a as PulseRoomTimeoutError, o as PulseTimeoutError, s as createPulse };
1
+ import{PulseAbortError as e,PulseConnectionError as t,PulseDisposedError as n,PulseError as r,PulseProtocolError as i,PulseRoomTimeoutError as a,PulseTimeoutError as o}from"./errors.js";import{createPulse as s}from"./pulse.js";export{e as PulseAbortError,t as PulseConnectionError,n as PulseDisposedError,r as PulseError,i as PulseProtocolError,a as PulseRoomTimeoutError,o as PulseTimeoutError,s as createPulse};
package/dist/protocol.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");function t(e){return JSON.stringify(e)}function n(t){if(typeof t!=`string`)throw new e.PulseProtocolError(`Expected string message data`,t);let n;try{n=JSON.parse(t)}catch{throw new e.PulseProtocolError(`JSON parse error`,t)}if(typeof n!=`object`||!n||!(`type`in n)||typeof n.type!=`string`)throw new e.PulseProtocolError(`Missing or invalid "type" field`,n);return n}function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e,t){if(!r(e))return null;let n=e[t];return typeof n==`string`?n:null}function a(e){return i(e,`room`)!==null}function o(e){return i(e,`code`)!==null&&i(e,`message`)!==null}function s(e){return!r(e)||i(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function c(e){return r(e)&&typeof e.ts==`number`}function l(e){return r(e)&&i(e,`id`)!==null&&i(e,`room`)!==null&&`state`in e}function u(e){return i(e,`id`)!==null&&i(e,`room`)!==null}function d(e){return r(e)&&i(e,`room`)!==null&&r(e.members)}function f(e){return i(e,`channel`)!==null}function p(t){let r=n(t);switch(r.type){case`error`:if(o(r))return{frame:r,kind:`known`};break;case`joined`:if(a(r))return{frame:r,kind:`known`};break;case`left`:if(a(r))return{frame:r,kind:`known`};break;case`message`:if(s(r))return{frame:r,kind:`known`};break;case`pong`:if(c(r))return{frame:r,kind:`known`};break;case`presence_join`:if(l(r))return{frame:r,kind:`known`};break;case`presence_leave`:if(u(r))return{frame:r,kind:`known`};break;case`presence_state`:if(d(r))return{frame:r,kind:`known`};break;case`subscribed`:if(f(r))return{frame:r,kind:`known`};break;case`unsubscribed`:if(f(r))return{frame:r,kind:`known`};break;default:return{kind:`unknown`,type:r.type}}throw new e.PulseProtocolError(`Malformed "${r.type}" frame`,r)}exports.decode=n,exports.decodeValidated=p,exports.encode=t,exports.isErrorFrame=o,exports.isMessageFrame=s,exports.isPongFrame=c,exports.isPresenceJoinFrame=l,exports.isPresenceLeaveFrame=u,exports.isPresenceStateFrame=d;
1
+ const e=require("./errors.cjs");function encode(e){return JSON.stringify(e)}function decode(t){if(typeof t!=`string`)throw new e.PulseProtocolError(`Expected string message data`,t);let n;try{n=JSON.parse(t)}catch{throw new e.PulseProtocolError(`JSON parse error`,t)}if(typeof n!=`object`||!n||!(`type`in n)||typeof n.type!=`string`)throw new e.PulseProtocolError(`Missing or invalid "type" field`,n);return n}function isObjectRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function getStringProp(e,t){if(!isObjectRecord(e))return null;let n=e[t];return typeof n==`string`?n:null}function hasRoom(e){return getStringProp(e,`room`)!==null}function isErrorFrame(e){return getStringProp(e,`code`)!==null&&getStringProp(e,`message`)!==null}function isMessageFrame(e){return!isObjectRecord(e)||getStringProp(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function isPongFrame(e){return isObjectRecord(e)&&typeof e.ts==`number`}function isPresenceJoinFrame(e){return isObjectRecord(e)&&getStringProp(e,`id`)!==null&&getStringProp(e,`room`)!==null&&`state`in e}function isPresenceLeaveFrame(e){return getStringProp(e,`id`)!==null&&getStringProp(e,`room`)!==null}function isPresenceStateFrame(e){return isObjectRecord(e)&&getStringProp(e,`room`)!==null&&isObjectRecord(e.members)}function hasChannel(e){return getStringProp(e,`channel`)!==null}function decodeValidated(t){let n=decode(t);switch(n.type){case`error`:if(isErrorFrame(n))return{frame:n,kind:`known`};break;case`joined`:if(hasRoom(n))return{frame:n,kind:`known`};break;case`left`:if(hasRoom(n))return{frame:n,kind:`known`};break;case`message`:if(isMessageFrame(n))return{frame:n,kind:`known`};break;case`pong`:if(isPongFrame(n))return{frame:n,kind:`known`};break;case`presence_join`:if(isPresenceJoinFrame(n))return{frame:n,kind:`known`};break;case`presence_leave`:if(isPresenceLeaveFrame(n))return{frame:n,kind:`known`};break;case`presence_state`:if(isPresenceStateFrame(n))return{frame:n,kind:`known`};break;case`subscribed`:if(hasChannel(n))return{frame:n,kind:`known`};break;case`unsubscribed`:if(hasChannel(n))return{frame:n,kind:`known`};break;default:return{kind:`unknown`,type:n.type}}throw new e.PulseProtocolError(`Malformed "${n.type}" frame`,n)}exports.decode=decode,exports.decodeValidated=decodeValidated,exports.encode=encode,exports.isErrorFrame=isErrorFrame,exports.isMessageFrame=isMessageFrame,exports.isPongFrame=isPongFrame,exports.isPresenceJoinFrame=isPresenceJoinFrame,exports.isPresenceLeaveFrame=isPresenceLeaveFrame,exports.isPresenceStateFrame=isPresenceStateFrame;
2
2
  //# sourceMappingURL=protocol.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (_cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction hasRoom(frame: unknown): frame is { room: string } {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction hasChannel(frame: unknown): frame is { channel: string } {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (hasRoom(frame)) return { frame: frame as InJoinedFrame, kind: 'known' };\n\n break;\n case 'left':\n if (hasRoom(frame)) return { frame: frame as InLeftFrame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (hasChannel(frame)) return { frame: frame as InSubscribedFrame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (hasChannel(frame)) return { frame: frame as InUnsubscribedFrame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (_cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction hasRoom(frame: unknown): frame is { room: string } {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction hasChannel(frame: unknown): frame is { channel: string } {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (hasRoom(frame)) return { frame: frame as InJoinedFrame, kind: 'known' };\n\n break;\n case 'left':\n if (hasRoom(frame)) return { frame: frame as InLeftFrame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (hasChannel(frame)) return { frame: frame as InSubscribedFrame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (hasChannel(frame)) return { frame: frame as InUnsubscribedFrame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n"],"mappings":"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"}
package/dist/protocol.js CHANGED
@@ -1,122 +1,2 @@
1
- import { PulseProtocolError as e } from "./errors.js";
2
- //#region src/protocol.ts
3
- function t(e) {
4
- return JSON.stringify(e);
5
- }
6
- function n(t) {
7
- if (typeof t != "string") throw new e("Expected string message data", t);
8
- let n;
9
- try {
10
- n = JSON.parse(t);
11
- } catch {
12
- throw new e("JSON parse error", t);
13
- }
14
- if (typeof n != "object" || !n || !("type" in n) || typeof n.type != "string") throw new e("Missing or invalid \"type\" field", n);
15
- return n;
16
- }
17
- function r(e) {
18
- return typeof e == "object" && !!e && !Array.isArray(e);
19
- }
20
- function i(e, t) {
21
- if (!r(e)) return null;
22
- let n = e[t];
23
- return typeof n == "string" ? n : null;
24
- }
25
- function a(e) {
26
- return i(e, "room") !== null;
27
- }
28
- function o(e) {
29
- return i(e, "code") !== null && i(e, "message") !== null;
30
- }
31
- function s(e) {
32
- return !r(e) || i(e, "event") === null || !("payload" in e) ? !1 : e.channel === void 0 || typeof e.channel == "string";
33
- }
34
- function c(e) {
35
- return r(e) && typeof e.ts == "number";
36
- }
37
- function l(e) {
38
- return r(e) && i(e, "id") !== null && i(e, "room") !== null && "state" in e;
39
- }
40
- function u(e) {
41
- return i(e, "id") !== null && i(e, "room") !== null;
42
- }
43
- function d(e) {
44
- return r(e) && i(e, "room") !== null && r(e.members);
45
- }
46
- function f(e) {
47
- return i(e, "channel") !== null;
48
- }
49
- function p(t) {
50
- let r = n(t);
51
- switch (r.type) {
52
- case "error":
53
- if (o(r)) return {
54
- frame: r,
55
- kind: "known"
56
- };
57
- break;
58
- case "joined":
59
- if (a(r)) return {
60
- frame: r,
61
- kind: "known"
62
- };
63
- break;
64
- case "left":
65
- if (a(r)) return {
66
- frame: r,
67
- kind: "known"
68
- };
69
- break;
70
- case "message":
71
- if (s(r)) return {
72
- frame: r,
73
- kind: "known"
74
- };
75
- break;
76
- case "pong":
77
- if (c(r)) return {
78
- frame: r,
79
- kind: "known"
80
- };
81
- break;
82
- case "presence_join":
83
- if (l(r)) return {
84
- frame: r,
85
- kind: "known"
86
- };
87
- break;
88
- case "presence_leave":
89
- if (u(r)) return {
90
- frame: r,
91
- kind: "known"
92
- };
93
- break;
94
- case "presence_state":
95
- if (d(r)) return {
96
- frame: r,
97
- kind: "known"
98
- };
99
- break;
100
- case "subscribed":
101
- if (f(r)) return {
102
- frame: r,
103
- kind: "known"
104
- };
105
- break;
106
- case "unsubscribed":
107
- if (f(r)) return {
108
- frame: r,
109
- kind: "known"
110
- };
111
- break;
112
- default: return {
113
- kind: "unknown",
114
- type: r.type
115
- };
116
- }
117
- throw new e(`Malformed "${r.type}" frame`, r);
118
- }
119
- //#endregion
120
- export { n as decode, p as decodeValidated, t as encode, o as isErrorFrame, s as isMessageFrame, c as isPongFrame, l as isPresenceJoinFrame, u as isPresenceLeaveFrame, d as isPresenceStateFrame };
121
-
1
+ import{PulseProtocolError as e}from"./errors.js";function encode(e){return JSON.stringify(e)}function decode(t){if(typeof t!=`string`)throw new e(`Expected string message data`,t);let n;try{n=JSON.parse(t)}catch{throw new e(`JSON parse error`,t)}if(typeof n!=`object`||!n||!(`type`in n)||typeof n.type!=`string`)throw new e(`Missing or invalid "type" field`,n);return n}function isObjectRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function getStringProp(e,t){if(!isObjectRecord(e))return null;let n=e[t];return typeof n==`string`?n:null}function hasRoom(e){return getStringProp(e,`room`)!==null}function isErrorFrame(e){return getStringProp(e,`code`)!==null&&getStringProp(e,`message`)!==null}function isMessageFrame(e){return!isObjectRecord(e)||getStringProp(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function isPongFrame(e){return isObjectRecord(e)&&typeof e.ts==`number`}function isPresenceJoinFrame(e){return isObjectRecord(e)&&getStringProp(e,`id`)!==null&&getStringProp(e,`room`)!==null&&`state`in e}function isPresenceLeaveFrame(e){return getStringProp(e,`id`)!==null&&getStringProp(e,`room`)!==null}function isPresenceStateFrame(e){return isObjectRecord(e)&&getStringProp(e,`room`)!==null&&isObjectRecord(e.members)}function hasChannel(e){return getStringProp(e,`channel`)!==null}function decodeValidated(t){let n=decode(t);switch(n.type){case`error`:if(isErrorFrame(n))return{frame:n,kind:`known`};break;case`joined`:if(hasRoom(n))return{frame:n,kind:`known`};break;case`left`:if(hasRoom(n))return{frame:n,kind:`known`};break;case`message`:if(isMessageFrame(n))return{frame:n,kind:`known`};break;case`pong`:if(isPongFrame(n))return{frame:n,kind:`known`};break;case`presence_join`:if(isPresenceJoinFrame(n))return{frame:n,kind:`known`};break;case`presence_leave`:if(isPresenceLeaveFrame(n))return{frame:n,kind:`known`};break;case`presence_state`:if(isPresenceStateFrame(n))return{frame:n,kind:`known`};break;case`subscribed`:if(hasChannel(n))return{frame:n,kind:`known`};break;case`unsubscribed`:if(hasChannel(n))return{frame:n,kind:`known`};break;default:return{kind:`unknown`,type:n.type}}throw new e(`Malformed "${n.type}" frame`,n)}export{decode,decodeValidated,encode,isErrorFrame,isMessageFrame,isPongFrame,isPresenceJoinFrame,isPresenceLeaveFrame,isPresenceStateFrame};
122
2
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (_cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction hasRoom(frame: unknown): frame is { room: string } {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction hasChannel(frame: unknown): frame is { channel: string } {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (hasRoom(frame)) return { frame: frame as InJoinedFrame, kind: 'known' };\n\n break;\n case 'left':\n if (hasRoom(frame)) return { frame: frame as InLeftFrame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (hasChannel(frame)) return { frame: frame as InSubscribedFrame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (hasChannel(frame)) return { frame: frame as InUnsubscribedFrame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import { PulseProtocolError } from './errors';\n\n// ─── Outgoing frames (client → server) ────────────────────────────────────────\n\nexport type OutMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type OutSubscribeFrame = { channel: string; type: 'subscribe' };\nexport type OutUnsubscribeFrame = { channel: string; type: 'unsubscribe' };\nexport type OutJoinFrame = { room: string; type: 'join' };\nexport type OutLeaveFrame = { room: string; type: 'leave' };\nexport type OutPresenceFrame = { room: string; state: unknown; type: 'presence' };\nexport type OutPingFrame = { ts: number; type: 'ping' };\n\nexport type OutFrame =\n | OutJoinFrame\n | OutLeaveFrame\n | OutMessageFrame\n | OutPingFrame\n | OutPresenceFrame\n | OutSubscribeFrame\n | OutUnsubscribeFrame;\n\n// ─── Incoming frames (server → client) ────────────────────────────────────────\n\nexport type InMessageFrame = { channel?: string; event: string; payload: unknown; type: 'message' };\nexport type InSubscribedFrame = { channel: string; type: 'subscribed' };\nexport type InUnsubscribedFrame = { channel: string; type: 'unsubscribed' };\nexport type InJoinedFrame = { room: string; type: 'joined' };\nexport type InLeftFrame = { room: string; type: 'left' };\nexport type InPresenceStateFrame = { members: Record<string, unknown>; room: string; type: 'presence_state' };\nexport type InPresenceJoinFrame = { id: string; room: string; state: unknown; type: 'presence_join' };\nexport type InPresenceLeaveFrame = { id: string; room: string; type: 'presence_leave' };\nexport type InPongFrame = { ts: number; type: 'pong' };\nexport type InErrorFrame = { code: string; message: string; type: 'error' };\n\nexport type InFrame =\n | InErrorFrame\n | InJoinedFrame\n | InLeftFrame\n | InMessageFrame\n | InPongFrame\n | InPresenceJoinFrame\n | InPresenceLeaveFrame\n | InPresenceStateFrame\n | InSubscribedFrame\n | InUnsubscribedFrame;\n\ntype ParsedFrame = { type: string } & Record<string, unknown>;\nexport type DecodedInFrame = { frame: InFrame; kind: 'known' } | { kind: 'unknown'; type: string };\n\n// ─── Codec ────────────────────────────────────────────────────────────────────\n\n/**\n * Encode an outgoing frame to a JSON string.\n * @internal\n */\nexport function encode(frame: OutFrame): string {\n return JSON.stringify(frame);\n}\n\n/**\n * Parse a raw MessageEvent data string and ensure it has a string `type` field.\n * Throws `PulseProtocolError` if the data is not valid JSON or missing the `type` field.\n * @internal\n */\nexport function decode(raw: unknown): ParsedFrame {\n if (typeof raw !== 'string') {\n throw new PulseProtocolError('Expected string message data', raw);\n }\n\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(raw);\n } catch (_cause) {\n throw new PulseProtocolError('JSON parse error', raw);\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n !('type' in parsed) ||\n typeof (parsed as { type: unknown }).type !== 'string'\n ) {\n throw new PulseProtocolError('Missing or invalid \"type\" field', parsed);\n }\n\n return parsed as ParsedFrame;\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction getStringProp(frame: unknown, key: string): string | null {\n if (!isObjectRecord(frame)) return null;\n\n const value = frame[key];\n\n return typeof value === 'string' ? value : null;\n}\n\nfunction hasRoom(frame: unknown): frame is { room: string } {\n return getStringProp(frame, 'room') !== null;\n}\n\nexport function isErrorFrame(frame: unknown): frame is InErrorFrame {\n return getStringProp(frame, 'code') !== null && getStringProp(frame, 'message') !== null;\n}\n\nexport function isMessageFrame(frame: unknown): frame is InMessageFrame {\n if (!isObjectRecord(frame) || getStringProp(frame, 'event') === null || !('payload' in frame)) return false;\n\n return frame.channel === undefined || typeof frame.channel === 'string';\n}\n\nexport function isPongFrame(frame: unknown): frame is InPongFrame {\n return isObjectRecord(frame) && typeof frame.ts === 'number';\n}\n\nexport function isPresenceJoinFrame(frame: unknown): frame is InPresenceJoinFrame {\n return (\n isObjectRecord(frame) &&\n getStringProp(frame, 'id') !== null &&\n getStringProp(frame, 'room') !== null &&\n 'state' in frame\n );\n}\n\nexport function isPresenceLeaveFrame(frame: unknown): frame is InPresenceLeaveFrame {\n return getStringProp(frame, 'id') !== null && getStringProp(frame, 'room') !== null;\n}\n\nexport function isPresenceStateFrame(frame: unknown): frame is InPresenceStateFrame {\n return isObjectRecord(frame) && getStringProp(frame, 'room') !== null && isObjectRecord(frame.members);\n}\n\nfunction hasChannel(frame: unknown): frame is { channel: string } {\n return getStringProp(frame, 'channel') !== null;\n}\n\n/**\n * Parse and validate known incoming frame shapes.\n * Unknown frame `type` values are passed through so callers can warn/drop them.\n * @internal\n */\nexport function decodeValidated(raw: unknown): DecodedInFrame {\n const frame = decode(raw) as ParsedFrame;\n\n switch (frame.type) {\n case 'error':\n if (isErrorFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'joined':\n if (hasRoom(frame)) return { frame: frame as InJoinedFrame, kind: 'known' };\n\n break;\n case 'left':\n if (hasRoom(frame)) return { frame: frame as InLeftFrame, kind: 'known' };\n\n break;\n case 'message':\n if (isMessageFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'pong':\n if (isPongFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_join':\n if (isPresenceJoinFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_leave':\n if (isPresenceLeaveFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'presence_state':\n if (isPresenceStateFrame(frame)) return { frame, kind: 'known' };\n\n break;\n case 'subscribed':\n if (hasChannel(frame)) return { frame: frame as InSubscribedFrame, kind: 'known' };\n\n break;\n case 'unsubscribed':\n if (hasChannel(frame)) return { frame: frame as InUnsubscribedFrame, kind: 'known' };\n\n break;\n default:\n return { kind: 'unknown', type: frame.type };\n }\n\n throw new PulseProtocolError(`Malformed \"${frame.type}\" frame`, frame);\n}\n"],"mappings":"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"}
package/dist/pulse.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{url;constructor(e,t,n){super(e,n),this.url=t}},n=class extends e{event;constructor(e,t){super(`Timed out waiting for "${e}"`,t),this.event=e}},r=class extends e{room;constructor(e,t){super(`Timed out waiting for room "${e}"`,t),this.room=e}},i=class extends e{constructor(e){super(`Aborted`,e)}},a=class extends e{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},o=class extends e{raw;constructor(e,t,n){super(e,n),this.raw=t}};function s(e){let t=new AbortController;return e.aborted?t.abort(e.reason):e.addEventListener(`abort`,()=>t.abort(e.reason),{once:!0}),t}function c(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=setTimeout(n,e);t?.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})})}function l(e,t=3e4){let n=Math.min(1e3*2**e,t);return Math.random()*n}var u=5;function d(e,n,r,i){let o=r===!0?{}:r||void 0,s=o?o.maxAttempts??u:0,d=o?.delay,f=typeof d==`function`?d:typeof d==`number`?()=>d:l,p=!1,m=!1,h=!1,g,_,v,y=new Set;function b(e){i.onStatus(e)}function x(e){for(let t of y)t.reject(e);y.clear()}function S(){for(let e of y)e.resolve();y.clear()}function C(e,t){v?.socket===e&&(v.reject(t),v=void 0)}function w(){if(_?.readyState===WebSocket.OPEN)return Promise.resolve();if(v)return v.promise;let r=new WebSocket(e,n);_=r;let a,o,s=new Promise((e,t)=>{a=e,o=t});return v={promise:s,reject:o,resolve:a,socket:r},r.onopen=()=>{if(_!==r||!m){r.close(1e3,`stale connection`);return}v?.socket===r&&(v.resolve(),v=void 0),b(`open`),i.onOpen(),S()},r.onmessage=e=>{_===r&&i.onMessage(e)},r.onerror=()=>{if(_!==r)return;let n=new t(`WebSocket error`,e);i.onError(n),C(r,n),x(n)},r.onclose=()=>{if(_!==r)return;_=void 0,i.onClose();let n=new t(`Connection closed`,e);if(C(r,n),!m||p){b(`closed`);return}h||T()},s}async function T(){if(h||p||!m)return;h=!0;let n=new AbortController;g=n,b(`reconnecting`);let r;for(let i=0;i<s&&m&&!p&&(await c(f(i),n.signal),!(n.signal.aborted||!m||p));i++)try{await w(),h=!1;return}catch(n){r=n instanceof t?n:new t(`Reconnect failed`,e)}!n.signal.aborted&&m&&!p&&(m=!1,b(`closed`),i.onError(r??new t(`Reconnect budget exhausted`,e)),x(r??new t(`Reconnect budget exhausted`,e))),g===n&&(g=void 0),h=!1}function E(){g?.abort(),g=void 0,h=!1}return{connect(){if(p)return Promise.reject(new a);if(m=!0,_?.readyState===WebSocket.OPEN)return Promise.resolve();let e=new Promise((e,t)=>{y.add({reject:t,resolve:e})});return!v&&!h&&(b(`connecting`),w().catch(()=>{})),e},disconnect(n=1e3,r=``){m=!1,E(),x(new t(`Connection closed`,e)),b(`closed`),_?.close(n,r)},dispose(){p||(p=!0,m=!1,E(),x(new a),_?.close(1e3,`disposed`),_=void 0,v=void 0,b(`closed`))},forceReconnect(e,t){m&&_?.readyState===WebSocket.OPEN&&_.close(e,t)},get open(){return _?.readyState===WebSocket.OPEN},send(n){if(_?.readyState!==WebSocket.OPEN)throw new t(`Connection is not open`,e);_.send(n)}}}function f(e,t){let n=e,r=new Set;return{getSnapshot:()=>n,set(e){if(!Object.is(n,e)){n=e;for(let e of[...r])try{e()}catch(e){t(e)}}},subscribe(e){return r.add(e),()=>r.delete(e)}}}function p(e,t,r,a){return new Promise((o,s)=>{let c=[t],l;if(r?.signal&&c.push(r.signal),r?.timeout!==void 0){let t=new AbortController;l=setTimeout(()=>{t.abort(new n(e))},r.timeout),c.push(t.signal)}let u=AbortSignal.any(c);if(u.aborted){clearTimeout(l),s(u.reason instanceof n?u.reason:new i);return}let d=()=>{},f=()=>{clearTimeout(l),d(),s(u.reason instanceof n?u.reason:new i)};u.addEventListener(`abort`,f,{once:!0}),d=a(e,e=>{clearTimeout(l),u.removeEventListener(`abort`,f),o(e)})})}function m(e,t,n,r,i){let o=s(r),c=new Set,l=o.signal.aborted;return o.signal.addEventListener(`abort`,()=>{l=!0},{once:!0}),{get disposalSignal(){return o.signal},dispose(){if(!l){l=!0,o.abort();for(let e of c)e();c.clear(),i?.()}},get disposed(){return l},get name(){return e},on(t,r){if(l)throw new a(`Channel "${e}"`);let i=n(e,t,r);return c.add(i),()=>{i(),c.delete(i)}},once(t,r){if(l)throw new a(`Channel "${e}"`);let i=()=>{};return i=n(e,t,e=>{c.delete(i),i(),r(e)}),c.add(i),()=>{c.delete(i),i()}},send(n,r){if(l)throw new a(`Channel "${e}"`);t(e,n,r)},[Symbol.dispose](){this.dispose()},wait(t,r){return p(t,o.signal,r,(t,r)=>n(e,t,r))}}}function h(e){return JSON.stringify(e)}function g(e){if(typeof e!=`string`)throw new o(`Expected string message data`,e);let t;try{t=JSON.parse(e)}catch{throw new o(`JSON parse error`,e)}if(typeof t!=`object`||!t||!(`type`in t)||typeof t.type!=`string`)throw new o(`Missing or invalid "type" field`,t);return t}function _(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function v(e,t){if(!_(e))return null;let n=e[t];return typeof n==`string`?n:null}function y(e){return v(e,`room`)!==null}function b(e){return v(e,`code`)!==null&&v(e,`message`)!==null}function x(e){return!_(e)||v(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function S(e){return _(e)&&typeof e.ts==`number`}function C(e){return _(e)&&v(e,`id`)!==null&&v(e,`room`)!==null&&`state`in e}function w(e){return v(e,`id`)!==null&&v(e,`room`)!==null}function T(e){return _(e)&&v(e,`room`)!==null&&_(e.members)}function E(e){return v(e,`channel`)!==null}function D(e){let t=g(e);switch(t.type){case`error`:if(b(t))return{frame:t,kind:`known`};break;case`joined`:if(y(t))return{frame:t,kind:`known`};break;case`left`:if(y(t))return{frame:t,kind:`known`};break;case`message`:if(x(t))return{frame:t,kind:`known`};break;case`pong`:if(S(t))return{frame:t,kind:`known`};break;case`presence_join`:if(C(t))return{frame:t,kind:`known`};break;case`presence_leave`:if(w(t))return{frame:t,kind:`known`};break;case`presence_state`:if(T(t))return{frame:t,kind:`known`};break;case`subscribed`:if(E(t))return{frame:t,kind:`known`};break;case`unsubscribed`:if(E(t))return{frame:t,kind:`known`};break;default:return{kind:`unknown`,type:t.type}}throw new o(`Malformed "${t.type}" frame`,t)}var O=3e4,k=5e3;function A(e,t,n){if(!e)return{onPong(){},start(){},stop(){}};let r=e===!0?{}:e,i=r.interval??O,a=r.timeout??k,o,s,c=!1;function l(){o=setTimeout(()=>{t(h({ts:Date.now(),type:`ping`})),s=setTimeout(()=>{n()},a)},i)}return{onPong(){clearTimeout(s),s=void 0,c&&l()},start(){c=!0,clearTimeout(o),clearTimeout(s),s=void 0,l()},stop(){c=!1,clearTimeout(o),clearTimeout(s),o=void 0,s=void 0}}}function j(n){let o=f(new Set,n.onStoreError),c=new Map;function l(e){let t=c.get(e);return t||(t={confirmed:!1,joinSent:!1,leaveAfterJoin:!1,localPresence:void 0,refs:0,scopes:new Set},c.set(e,t)),t}function u(e,t){let n=new Set(o.getSnapshot());t?n.add(e):n.delete(e),o.set(n)}function d(e){n.send(h({room:e,type:`join`}))}function p(e){n.send(h({room:e,type:`leave`}))}function m(e,r){let i=c.get(e);if(!i)throw new a(`Room "${e}"`);if(!n.isOpen())throw new t(`Connection is not open`,e);n.send(h({room:e,state:r,type:`presence`})),i.localPresence=r}function g(e,t){let r=c.get(e);r&&r.scopes.has(t)&&(r.scopes.delete(t),r.refs--,r.refs===0&&(r.confirmed?(r.confirmed=!1,r.localPresence=void 0,u(e,!1),n.isOpen()&&p(e),c.delete(e)):r.joinSent?r.leaveAfterJoin=!0:c.delete(e)))}return{createScope(o,c,u){if(n.disposalSignal.aborted)return N(o,c);let p=l(o),h=s(n.disposalSignal),_,v,y=!1,b=new Promise((e,t)=>{_=()=>{y||(y=!0,clearTimeout(x),C.removeEventListener(`abort`,k),e())},v=e=>{y||(y=!0,clearTimeout(x),C.removeEventListener(`abort`,k),t(e))}});b.catch(()=>{});let x,S=[h.signal];if(u?.signal&&S.push(u.signal),u?.timeout!==void 0){let e=new AbortController;x=setTimeout(()=>e.abort(new r(o)),u.timeout),S.push(e.signal)}let C=AbortSignal.any(S),w,T,E=new Set,D=new Set;c&&(w=f(new Map,n.onStoreError),T={onJoin(e,t){let n=new Map(w.getSnapshot());n.set(e,t),w.set(n);for(let n of E)n(e,t)},onLeave(e){let t=new Map(w.getSnapshot());t.delete(e),w.set(t);for(let t of D)t(e)},onState(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,r);w.set(t)},reset(){w.set(new Map)}});let O={isSettled:()=>y,joined:b,name:o,presenceHandlers:T,rejectJoined:v,resolveJoined:_},k=()=>{clearTimeout(x),h.abort();let t=C.reason,n=t instanceof e?t:new i;O.rejectJoined(n),g(o,O)};if(p.refs++,p.scopes.add(O),C.aborted)return k(),M(O,h.signal,w,E,D,o,m);if(C.addEventListener(`abort`,k,{once:!0}),p.confirmed)O.resolveJoined();else if(!p.joinSent&&n.isOpen()){p.joinSent=!0;try{d(o)}catch(n){p.joinSent=!1,O.rejectJoined(n instanceof e?n:new t(`Failed to send join`,o))}}return M(O,h.signal,w,E,D,o,m,()=>{h.signal.aborted||(clearTimeout(x),C.removeEventListener(`abort`,k),h.abort(),y||O.rejectJoined(new a(`Room "${o}"`)),g(o,O))})},dispose(){o.set(new Set),c.clear()},handleJoined(e){let t=c.get(e);if(t){if(t.confirmed=!0,t.joinSent=!1,u(e,!0),t.leaveAfterJoin){t.leaveAfterJoin=!1,t.confirmed=!1,u(e,!1),n.isOpen()&&p(e),c.delete(e);return}for(let e of t.scopes)e.isSettled()||e.resolveJoined()}},handleLeft(e){let t=c.get(e);t&&(t.confirmed=!1,t.joinSent=!1,u(e,!1))},handlePresenceJoin(e,t,n){let r=c.get(e);if(r)for(let e of r.scopes)e.presenceHandlers?.onJoin(t,n)},handlePresenceLeave(e,t){let n=c.get(e);if(n)for(let e of n.scopes)e.presenceHandlers?.onLeave(t)},handlePresenceState(e,t){let n=c.get(e);if(n)for(let e of n.scopes)e.presenceHandlers?.onState(t)},rejectAll(e){for(let t of c.values())for(let n of t.scopes)n.isSettled()||n.rejectJoined(e)},reset(){o.set(new Set);for(let[e,t]of c){if(t.refs===0){c.delete(e);continue}t.confirmed=!1,t.joinSent=!1;for(let e of t.scopes)e.presenceHandlers?.reset()}},restore(){for(let[e,t]of c)if(t.refs!==0){t.confirmed=!1,t.joinSent=!0;try{d(e)}catch{t.joinSent=!1}if(t.localPresence!==void 0)try{n.send(h({room:e,state:t.localPresence,type:`presence`}))}catch{}}},rooms:o}}function M(e,t,n,r,i,o,s,c){let l=c??(()=>{});return n?{get disposalSignal(){return t},dispose:l,get disposed(){return t.aborted},get joined(){return e.joined},get name(){return e.name},onJoin(e){if(t.aborted)throw new a(`Room "${o}"`);return r.add(e),()=>r.delete(e)},onLeave(e){if(t.aborted)throw new a(`Room "${o}"`);return i.add(e),()=>i.delete(e)},get presence(){return n},updatePresence(e){s(o,e)},[Symbol.dispose]:l}:{get disposalSignal(){return t},dispose:l,get disposed(){return t.aborted},get joined(){return e.joined},get name(){return e.name},[Symbol.dispose]:l}}function N(e,t){let n=new AbortController;n.abort(new a);let r=Promise.reject(new a(`Room "${e}"`));return r.catch(()=>{}),t?{disposalSignal:n.signal,dispose(){},disposed:!0,joined:r,name:e,onJoin(){throw new a(`Room "${e}"`)},onLeave(){throw new a(`Room "${e}"`)},presence:f(new Map,()=>{}),updatePresence(){throw new a(`Room "${e}"`)},[Symbol.dispose](){}}:{disposalSignal:n.signal,dispose(){},disposed:!0,joined:r,name:e,[Symbol.dispose](){}}}var P=class{map=new Map;add(e,t,n){let r=this.map.get(e);r||(r=new Map,this.map.set(e,r));let i=r.get(t);return i||(i=new Set,r.set(t,i)),i.add(n),()=>{i.delete(n),i.size===0&&r.delete(t),r.size===0&&this.map.delete(e)}}clear(){this.map.clear()}dispatch(e,t,n){for(let r of this.map.get(e)?.get(t)??[])r(n)}};function F(n,r={}){let i=new AbortController,s=new P,c=new Map,l=new Set,u=!1,g=!0,_=`closed`;function v(e){if(l.size!==0)for(let t of l)try{t(e)}catch{}}function y(e){v({error:e,type:`error`})}let b=t=>y(new e(`External store subscriber threw`,{cause:t})),x=f(`closed`,b);function S(e){O.send(e)}let C=j({disposalSignal:i.signal,isOpen:()=>O.open,onStoreError:b,send:S});function w(){for(let e of c.keys())S(h({channel:e,type:`subscribe`}));C.restore()}function T(){C.reset()}function E(){g||(g=!0,k.stop(),T(),C.rejectAll(new t(`Connection closed before room confirmation`,n)))}let O=d(n,r.protocols,r.reconnect,{onClose(){E()},onError:y,onMessage(e){let t;try{t=D(e.data)}catch(t){y(t instanceof o?t:new o(`Failed to decode frame`,e.data));return}if(t.kind===`unknown`){y(new o(`Unknown frame type "${t.type}"`,e.data));return}try{M(t)}catch(e){y(new o(`Frame handler threw`,t.frame,{cause:e}))}},onOpen(){g=!1;try{w(),k.start()}catch(r){y(r instanceof e?r:new t(`Session restore failed`,n))}},onStatus(e){x.set(e),e!==_&&(_=e,v({status:e,type:`status-change`}))}}),k=A(r.heartbeat,e=>{O.open&&S(e)},()=>O.forceReconnect(4e3,`heartbeat timeout`));function M(e){let t=e.frame;switch(t.type){case`error`:y(new o(`Server error [${t.code}]: ${t.message}`,t));break;case`joined`:C.handleJoined(t.room);break;case`left`:C.handleLeft(t.room);break;case`message`:s.dispatch(t.channel??null,t.event,t.payload);break;case`pong`:k.onPong();break;case`presence_join`:C.handlePresenceJoin(t.room,t.id,t.state);break;case`presence_leave`:C.handlePresenceLeave(t.room,t.id);break;case`presence_state`:C.handlePresenceState(t.room,t.members)}}function N(e){let t=c.get(e)??0;c.set(e,t+1),t===0&&O.open&&S(h({channel:e,type:`subscribe`}))}function F(e){let t=c.get(e);if(t){if(t>1){c.set(e,t-1);return}c.delete(e),O.open&&S(h({channel:e,type:`unsubscribe`}))}}return{channel(e){if(u)throw new a;return N(e),m(e,(e,t,n)=>{let i=r.transform?r.transform({channel:e,event:t,payload:n}):{channel:e,event:t,payload:n};i!==null&&S(h({...i,type:`message`}))},(e,t,n)=>s.add(e,t,n),i.signal,()=>F(e))},connect(){return u?Promise.reject(new a):O.connect()},disconnect(e=1e3,t=``){u||(E(),O.disconnect(e,t))},get disposalSignal(){return i.signal},dispose(){u||(u=!0,k.stop(),g=!0,C.reset(),C.rejectAll(new a),O.dispose(),c.clear(),C.dispose(),s.clear(),v({type:`dispose`}),l.clear(),i.abort())},get disposed(){return u},on(e,t){if(u)throw new a;return s.add(null,e,t)},once(e,t){if(u)throw new a;let n=()=>{};return n=s.add(null,e,e=>{n(),t(e)}),n},room(e,t){if(u)throw new a;return C.createScope(e,!0,t)},get rooms(){return C.rooms},send(e,t){if(u)throw new a;let n=r.transform?r.transform({event:e,payload:t}):{event:e,payload:t};n!==null&&S(h({...n,type:`message`}))},get status(){return x},tap(e,t){if(u)throw new a;if(l.add(e),t?.signal){if(t.signal.aborted)return l.delete(e),()=>{};let n=()=>l.delete(e);return t.signal.addEventListener(`abort`,n,{once:!0}),()=>{l.delete(e),t.signal?.removeEventListener(`abort`,n)}}return()=>l.delete(e)},[Symbol.dispose](){this.dispose()},wait(e,t){return p(e,i.signal,t,(e,t)=>s.add(null,e,t))}}}exports.PulseAbortError=i,exports.PulseConnectionError=t,exports.PulseDisposedError=a,exports.PulseError=e,exports.PulseProtocolError=o,exports.PulseRoomTimeoutError=r,exports.PulseTimeoutError=n,exports.createPulse=F;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");var PulseError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},PulseConnectionError=class extends PulseError{url;constructor(e,t,n){super(e,n),this.url=t}},PulseTimeoutError=class extends PulseError{event;constructor(e,t){super(`Timed out waiting for "${e}"`,t),this.event=e}},PulseRoomTimeoutError=class extends PulseError{room;constructor(e,t){super(`Timed out waiting for room "${e}"`,t),this.room=e}},PulseAbortError=class extends PulseError{constructor(e){super(`Aborted`,e)}},PulseDisposedError=class extends PulseError{constructor(e=`Pulse`,t){super(`${e} instance is disposed`,t)}},PulseProtocolError=class extends PulseError{raw;constructor(e,t,n){super(e,n),this.raw=t}};function deriveAbortController(e){let t=new AbortController;return e.aborted?t.abort(e.reason):e.addEventListener(`abort`,()=>t.abort(e.reason),{once:!0}),t}function sleep(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=setTimeout(n,e);t?.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})})}function defaultReconnectDelay(t,n=3e4){return Math.random()*(0,e.backoff)(t,n)}var t=5;function createConnection(e,n,r,i){let a=r===!0?{}:r||void 0,o=a?a.maxAttempts??t:0,s=a?.delay,c=typeof s==`function`?s:typeof s==`number`?()=>s:defaultReconnectDelay,l=!1,u=!1,d=!1,f,p,m,h=new Set;function setStatus(e){i.onStatus(e)}function rejectWaiters(e){for(let t of h)t.reject(e);h.clear()}function resolveWaiters(){for(let e of h)e.resolve();h.clear()}function rejectOpening(e,t){m?.socket===e&&(m.reject(t),m=void 0)}function openSocket(){if(p?.readyState===WebSocket.OPEN)return Promise.resolve();if(m)return m.promise;let t=new WebSocket(e,n);p=t;let r,a,o=new Promise((e,t)=>{r=e,a=t});return m={promise:o,reject:a,resolve:r,socket:t},t.onopen=()=>{if(p!==t||!u){t.close(1e3,`stale connection`);return}m?.socket===t&&(m.resolve(),m=void 0),setStatus(`open`),i.onOpen(),resolveWaiters()},t.onmessage=e=>{p===t&&i.onMessage(e)},t.onerror=()=>{if(p!==t)return;let n=new PulseConnectionError(`WebSocket error`,e);i.onError(n),rejectOpening(t,n),rejectWaiters(n)},t.onclose=()=>{if(p!==t)return;p=void 0,i.onClose();let n=new PulseConnectionError(`Connection closed`,e);if(rejectOpening(t,n),!u||l){setStatus(`closed`);return}d||reconnectLoop()},o}async function reconnectLoop(){if(d||l||!u)return;d=!0;let t=new AbortController;f=t,setStatus(`reconnecting`);let n;for(let r=0;r<o&&u&&!l&&(await sleep(c(r),t.signal),!(t.signal.aborted||!u||l));r++)try{await openSocket(),d=!1;return}catch(t){n=t instanceof PulseConnectionError?t:new PulseConnectionError(`Reconnect failed`,e)}!t.signal.aborted&&u&&!l&&(u=!1,setStatus(`closed`),i.onError(n??new PulseConnectionError(`Reconnect budget exhausted`,e)),rejectWaiters(n??new PulseConnectionError(`Reconnect budget exhausted`,e))),f===t&&(f=void 0),d=!1}function cancelRetry(){f?.abort(),f=void 0,d=!1}return{connect(){if(l)return Promise.reject(new PulseDisposedError);if(u=!0,p?.readyState===WebSocket.OPEN)return Promise.resolve();let e=new Promise((e,t)=>{h.add({reject:t,resolve:e})});return!m&&!d&&(setStatus(`connecting`),openSocket().catch(()=>{})),e},disconnect(t=1e3,n=``){u=!1,cancelRetry(),rejectWaiters(new PulseConnectionError(`Connection closed`,e)),setStatus(`closed`),p?.close(t,n)},dispose(){l||(l=!0,u=!1,cancelRetry(),rejectWaiters(new PulseDisposedError),p?.close(1e3,`disposed`),p=void 0,m=void 0,setStatus(`closed`))},forceReconnect(e,t){u&&p?.readyState===WebSocket.OPEN&&p.close(e,t)},get open(){return p?.readyState===WebSocket.OPEN},send(t){if(p?.readyState!==WebSocket.OPEN)throw new PulseConnectionError(`Connection is not open`,e);p.send(t)}}}function createStore(e,t){let n=e,r=new Set;return{getSnapshot:()=>n,set(e){if(!Object.is(n,e)){n=e;for(let e of[...r])try{e()}catch(e){t(e)}}},subscribe(e){return r.add(e),()=>r.delete(e)}}}function createWaitPromise(e,t,n,r){return new Promise((i,a)=>{let o=[t],s;if(n?.signal&&o.push(n.signal),n?.timeout!==void 0){let t=new AbortController;s=setTimeout(()=>{t.abort(new PulseTimeoutError(e))},n.timeout),o.push(t.signal)}let c=AbortSignal.any(o);if(c.aborted){clearTimeout(s),a(c.reason instanceof PulseTimeoutError?c.reason:new PulseAbortError);return}let unsub=()=>{},onAbort=()=>{clearTimeout(s),unsub(),a(c.reason instanceof PulseTimeoutError?c.reason:new PulseAbortError)};c.addEventListener(`abort`,onAbort,{once:!0}),unsub=r(e,e=>{clearTimeout(s),c.removeEventListener(`abort`,onAbort),i(e)})})}function createChannel(e,t,n,r,i){let a=deriveAbortController(r),o=new Set,s=a.signal.aborted;return a.signal.addEventListener(`abort`,()=>{s=!0},{once:!0}),{get disposalSignal(){return a.signal},dispose(){if(!s){s=!0,a.abort();for(let e of o)e();o.clear(),i?.()}},get disposed(){return s},get name(){return e},on(t,r){if(s)throw new PulseDisposedError(`Channel "${e}"`);let i=n(e,t,r);return o.add(i),()=>{i(),o.delete(i)}},once(t,r){if(s)throw new PulseDisposedError(`Channel "${e}"`);let unsub=()=>{},wrapped=e=>{o.delete(unsub),unsub(),r(e)};return unsub=n(e,t,wrapped),o.add(unsub),()=>{o.delete(unsub),unsub()}},send(n,r){if(s)throw new PulseDisposedError(`Channel "${e}"`);t(e,n,r)},[Symbol.dispose](){this.dispose()},wait(t,r){return createWaitPromise(t,a.signal,r,(t,r)=>n(e,t,r))}}}function encode(e){return JSON.stringify(e)}function decode(e){if(typeof e!=`string`)throw new PulseProtocolError(`Expected string message data`,e);let t;try{t=JSON.parse(e)}catch{throw new PulseProtocolError(`JSON parse error`,e)}if(typeof t!=`object`||!t||!(`type`in t)||typeof t.type!=`string`)throw new PulseProtocolError(`Missing or invalid "type" field`,t);return t}function isObjectRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function getStringProp(e,t){if(!isObjectRecord(e))return null;let n=e[t];return typeof n==`string`?n:null}function hasRoom(e){return getStringProp(e,`room`)!==null}function isErrorFrame(e){return getStringProp(e,`code`)!==null&&getStringProp(e,`message`)!==null}function isMessageFrame(e){return!isObjectRecord(e)||getStringProp(e,`event`)===null||!(`payload`in e)?!1:e.channel===void 0||typeof e.channel==`string`}function isPongFrame(e){return isObjectRecord(e)&&typeof e.ts==`number`}function isPresenceJoinFrame(e){return isObjectRecord(e)&&getStringProp(e,`id`)!==null&&getStringProp(e,`room`)!==null&&`state`in e}function isPresenceLeaveFrame(e){return getStringProp(e,`id`)!==null&&getStringProp(e,`room`)!==null}function isPresenceStateFrame(e){return isObjectRecord(e)&&getStringProp(e,`room`)!==null&&isObjectRecord(e.members)}function hasChannel(e){return getStringProp(e,`channel`)!==null}function decodeValidated(e){let t=decode(e);switch(t.type){case`error`:if(isErrorFrame(t))return{frame:t,kind:`known`};break;case`joined`:if(hasRoom(t))return{frame:t,kind:`known`};break;case`left`:if(hasRoom(t))return{frame:t,kind:`known`};break;case`message`:if(isMessageFrame(t))return{frame:t,kind:`known`};break;case`pong`:if(isPongFrame(t))return{frame:t,kind:`known`};break;case`presence_join`:if(isPresenceJoinFrame(t))return{frame:t,kind:`known`};break;case`presence_leave`:if(isPresenceLeaveFrame(t))return{frame:t,kind:`known`};break;case`presence_state`:if(isPresenceStateFrame(t))return{frame:t,kind:`known`};break;case`subscribed`:if(hasChannel(t))return{frame:t,kind:`known`};break;case`unsubscribed`:if(hasChannel(t))return{frame:t,kind:`known`};break;default:return{kind:`unknown`,type:t.type}}throw new PulseProtocolError(`Malformed "${t.type}" frame`,t)}var n=3e4,r=5e3;function createHeartbeat(e,t,i){if(!e)return{onPong(){},start(){},stop(){}};let a=e===!0?{}:e,o=a.interval??n,s=a.timeout??r,c,l,u=!1;function schedulePing(){c=setTimeout(()=>{t(encode({ts:Date.now(),type:`ping`})),l=setTimeout(()=>{i()},s)},o)}return{onPong(){clearTimeout(l),l=void 0,u&&schedulePing()},start(){u=!0,clearTimeout(c),clearTimeout(l),l=void 0,schedulePing()},stop(){u=!1,clearTimeout(c),clearTimeout(l),c=void 0,l=void 0}}}function createRoomRegistry(e){let t=createStore(new Set,e.onStoreError),n=new Map;function getOrCreate(e){let t=n.get(e);return t||(t={confirmed:!1,joinSent:!1,leaveAfterJoin:!1,localPresence:void 0,refs:0,scopes:new Set},n.set(e,t)),t}function setRoom(e,n){let r=new Set(t.getSnapshot());n?r.add(e):r.delete(e),t.set(r)}function sendJoin(t){e.send(encode({room:t,type:`join`}))}function sendLeave(t){e.send(encode({room:t,type:`leave`}))}function updatePresence(t,r){let i=n.get(t);if(!i)throw new PulseDisposedError(`Room "${t}"`);if(!e.isOpen())throw new PulseConnectionError(`Connection is not open`,t);e.send(encode({room:t,state:r,type:`presence`})),i.localPresence=r}function releaseScope(t,r){let i=n.get(t);i&&i.scopes.has(r)&&(i.scopes.delete(r),i.refs--,i.refs===0&&(i.confirmed?(i.confirmed=!1,i.localPresence=void 0,setRoom(t,!1),e.isOpen()&&sendLeave(t),n.delete(t)):i.joinSent?i.leaveAfterJoin=!0:n.delete(t)))}return{createScope(t,n,r){if(e.disposalSignal.aborted)return createDeadScope(t,n);let i=getOrCreate(t),a=deriveAbortController(e.disposalSignal),resolveJoined,rejectJoined,o=!1,s=new Promise((e,t)=>{resolveJoined=()=>{o||(o=!0,clearTimeout(c),u.removeEventListener(`abort`,onAbort),e())},rejectJoined=e=>{o||(o=!0,clearTimeout(c),u.removeEventListener(`abort`,onAbort),t(e))}});s.catch(()=>{});let c,l=[a.signal];if(r?.signal&&l.push(r.signal),r?.timeout!==void 0){let e=new AbortController;c=setTimeout(()=>e.abort(new PulseRoomTimeoutError(t)),r.timeout),l.push(e.signal)}let u=AbortSignal.any(l),d,f,p=new Set,m=new Set;n&&(d=createStore(new Map,e.onStoreError),f={onJoin(e,t){let n=new Map(d.getSnapshot());n.set(e,t),d.set(n);for(let n of p)n(e,t)},onLeave(e){let t=new Map(d.getSnapshot());t.delete(e),d.set(t);for(let t of m)t(e)},onState(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,r);d.set(t)},reset(){d.set(new Map)}});let h={isSettled:()=>o,joined:s,name:t,presenceHandlers:f,rejectJoined,resolveJoined},onAbort=()=>{clearTimeout(c),a.abort();let e=u.reason,n=e instanceof PulseError?e:new PulseAbortError;h.rejectJoined(n),releaseScope(t,h)};if(i.refs++,i.scopes.add(h),u.aborted)return onAbort(),buildPublicScope(h,a.signal,d,p,m,t,updatePresence);if(u.addEventListener(`abort`,onAbort,{once:!0}),i.confirmed)h.resolveJoined();else if(!i.joinSent&&e.isOpen()){i.joinSent=!0;try{sendJoin(t)}catch(e){i.joinSent=!1,h.rejectJoined(e instanceof PulseError?e:new PulseConnectionError(`Failed to send join`,t))}}let disposeScope=()=>{a.signal.aborted||(clearTimeout(c),u.removeEventListener(`abort`,onAbort),a.abort(),o||h.rejectJoined(new PulseDisposedError(`Room "${t}"`)),releaseScope(t,h))};return buildPublicScope(h,a.signal,d,p,m,t,updatePresence,disposeScope)},dispose(){t.set(new Set),n.clear()},handleJoined(t){let r=n.get(t);if(r){if(r.confirmed=!0,r.joinSent=!1,setRoom(t,!0),r.leaveAfterJoin){r.leaveAfterJoin=!1,r.confirmed=!1,setRoom(t,!1),e.isOpen()&&sendLeave(t),n.delete(t);return}for(let e of r.scopes)e.isSettled()||e.resolveJoined()}},handleLeft(e){let t=n.get(e);t&&(t.confirmed=!1,t.joinSent=!1,setRoom(e,!1))},handlePresenceJoin(e,t,r){let i=n.get(e);if(i)for(let e of i.scopes)e.presenceHandlers?.onJoin(t,r)},handlePresenceLeave(e,t){let r=n.get(e);if(r)for(let e of r.scopes)e.presenceHandlers?.onLeave(t)},handlePresenceState(e,t){let r=n.get(e);if(r)for(let e of r.scopes)e.presenceHandlers?.onState(t)},rejectAll(e){for(let t of n.values())for(let n of t.scopes)n.isSettled()||n.rejectJoined(e)},reset(){t.set(new Set);for(let[e,t]of n){if(t.refs===0){n.delete(e);continue}t.confirmed=!1,t.joinSent=!1;for(let e of t.scopes)e.presenceHandlers?.reset()}},restore(){for(let[t,r]of n)if(r.refs!==0){r.confirmed=!1,r.joinSent=!0;try{sendJoin(t)}catch{r.joinSent=!1}if(r.localPresence!==void 0)try{e.send(encode({room:t,state:r.localPresence,type:`presence`}))}catch{}}},rooms:t}}function buildPublicScope(e,t,n,r,i,a,o,s){let c=s??(()=>{});return n?{get disposalSignal(){return t},dispose:c,get disposed(){return t.aborted},get joined(){return e.joined},get name(){return e.name},onJoin(e){if(t.aborted)throw new PulseDisposedError(`Room "${a}"`);return r.add(e),()=>r.delete(e)},onLeave(e){if(t.aborted)throw new PulseDisposedError(`Room "${a}"`);return i.add(e),()=>i.delete(e)},get presence(){return n},updatePresence(e){o(a,e)},[Symbol.dispose]:c}:{get disposalSignal(){return t},dispose:c,get disposed(){return t.aborted},get joined(){return e.joined},get name(){return e.name},[Symbol.dispose]:c}}function createDeadScope(e,t){let n=new AbortController;n.abort(new PulseDisposedError);let r=Promise.reject(new PulseDisposedError(`Room "${e}"`));return r.catch(()=>{}),t?{disposalSignal:n.signal,dispose(){},disposed:!0,joined:r,name:e,onJoin(){throw new PulseDisposedError(`Room "${e}"`)},onLeave(){throw new PulseDisposedError(`Room "${e}"`)},presence:createStore(new Map,()=>{}),updatePresence(){throw new PulseDisposedError(`Room "${e}"`)},[Symbol.dispose](){}}:{disposalSignal:n.signal,dispose(){},disposed:!0,joined:r,name:e,[Symbol.dispose](){}}}var ListenerMap=class{map=new Map;add(e,t,n){let r=this.map.get(e);r||(r=new Map,this.map.set(e,r));let i=r.get(t);return i||(i=new Set,r.set(t,i)),i.add(n),()=>{i.delete(n),i.size===0&&r.delete(t),r.size===0&&this.map.delete(e)}}clear(){this.map.clear()}dispatch(e,t,n){for(let r of this.map.get(e)?.get(t)??[])r(n)}};function createPulse(t,n={}){let r=new AbortController,i=new ListenerMap,a=new Map,o=(0,e.tapper)(),s=!1,c=!0,l=`closed`;function emitTap(e){o.emit(e)}function report(e){emitTap({error:e,type:`error`})}let reportStoreError=e=>report(new PulseError(`External store subscriber threw`,{cause:e})),u=createStore(`closed`,reportStoreError);function sendInternal(e){f.send(e)}let d=createRoomRegistry({disposalSignal:r.signal,isOpen:()=>f.open,onStoreError:reportStoreError,send:sendInternal});function restoreSession(){for(let e of a.keys())sendInternal(encode({channel:e,type:`subscribe`}));d.restore()}function resetRemoteSession(){d.reset()}function handleTransportClose(){c||(c=!0,p.stop(),resetRemoteSession(),d.rejectAll(new PulseConnectionError(`Connection closed before room confirmation`,t)))}let f=createConnection(t,n.protocols,n.reconnect,{onClose(){handleTransportClose()},onError:report,onMessage(e){let t;try{t=decodeValidated(e.data)}catch(t){report(t instanceof PulseProtocolError?t:new PulseProtocolError(`Failed to decode frame`,e.data));return}if(t.kind===`unknown`){report(new PulseProtocolError(`Unknown frame type "${t.type}"`,e.data));return}try{handleFrame(t)}catch(e){report(new PulseProtocolError(`Frame handler threw`,t.frame,{cause:e}))}},onOpen(){c=!1;try{restoreSession(),p.start()}catch(e){report(e instanceof PulseError?e:new PulseConnectionError(`Session restore failed`,t))}},onStatus(e){u.set(e),e!==l&&(l=e,emitTap({status:e,type:`status-change`}))}}),p=createHeartbeat(n.heartbeat,e=>{f.open&&sendInternal(e)},()=>f.forceReconnect(4e3,`heartbeat timeout`));function handleFrame(e){let t=e.frame;switch(t.type){case`error`:report(new PulseProtocolError(`Server error [${t.code}]: ${t.message}`,t));break;case`joined`:d.handleJoined(t.room);break;case`left`:d.handleLeft(t.room);break;case`message`:i.dispatch(t.channel??null,t.event,t.payload);break;case`pong`:p.onPong();break;case`presence_join`:d.handlePresenceJoin(t.room,t.id,t.state);break;case`presence_leave`:d.handlePresenceLeave(t.room,t.id);break;case`presence_state`:d.handlePresenceState(t.room,t.members)}}function acquireChannel(e){let t=a.get(e)??0;a.set(e,t+1),t===0&&f.open&&sendInternal(encode({channel:e,type:`subscribe`}))}function releaseChannel(e){let t=a.get(e);if(t){if(t>1){a.set(e,t-1);return}a.delete(e),f.open&&sendInternal(encode({channel:e,type:`unsubscribe`}))}}return{channel(e){if(s)throw new PulseDisposedError;return acquireChannel(e),createChannel(e,(e,t,r)=>{let i=n.transform?n.transform({channel:e,event:t,payload:r}):{channel:e,event:t,payload:r};i!==null&&sendInternal(encode({...i,type:`message`}))},(e,t,n)=>i.add(e,t,n),r.signal,()=>releaseChannel(e))},connect(){return s?Promise.reject(new PulseDisposedError):f.connect()},disconnect(e=1e3,t=``){s||(handleTransportClose(),f.disconnect(e,t))},get disposalSignal(){return r.signal},dispose(){s||(s=!0,p.stop(),c=!0,d.reset(),d.rejectAll(new PulseDisposedError),f.dispose(),a.clear(),d.dispose(),i.clear(),emitTap({type:`dispose`}),o.clear(),r.abort())},get disposed(){return s},on(e,t){if(s)throw new PulseDisposedError;return i.add(null,e,t)},once(e,t){if(s)throw new PulseDisposedError;let unsubscribe=()=>{};return unsubscribe=i.add(null,e,e=>{unsubscribe(),t(e)}),unsubscribe},room(e,t){if(s)throw new PulseDisposedError;return d.createScope(e,!0,t)},get rooms(){return d.rooms},send(e,t){if(s)throw new PulseDisposedError;let r=n.transform?n.transform({event:e,payload:t}):{event:e,payload:t};r!==null&&sendInternal(encode({...r,type:`message`}))},get status(){return u},tap(e,t){if(s)throw new PulseDisposedError;return o.tap(e,t)},[Symbol.dispose](){this.dispose()},wait(e,t){return createWaitPromise(e,r.signal,t,(e,t)=>i.add(null,e,t))}}}exports.PulseAbortError=PulseAbortError,exports.PulseConnectionError=PulseConnectionError,exports.PulseDisposedError=PulseDisposedError,exports.PulseError=PulseError,exports.PulseProtocolError=PulseProtocolError,exports.PulseRoomTimeoutError=PulseRoomTimeoutError,exports.PulseTimeoutError=PulseTimeoutError,exports.createPulse=createPulse;
2
2
  //# sourceMappingURL=pulse.cjs.map