@firtoz/socka 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +120 -0
  2. package/assets/banner.png +0 -0
  3. package/dist/SockaWebSocketSession-Bru8yFcK.d.ts +107 -0
  4. package/dist/bun/index.d.ts +38 -0
  5. package/dist/bun/index.js +121 -0
  6. package/dist/bun/index.js.map +1 -0
  7. package/dist/chunk-45D4T232.js +236 -0
  8. package/dist/chunk-45D4T232.js.map +1 -0
  9. package/dist/chunk-5WQTYLIC.js +46 -0
  10. package/dist/chunk-5WQTYLIC.js.map +1 -0
  11. package/dist/chunk-AM7PB26G.js +421 -0
  12. package/dist/chunk-AM7PB26G.js.map +1 -0
  13. package/dist/chunk-MZCQHJXY.js +158 -0
  14. package/dist/chunk-MZCQHJXY.js.map +1 -0
  15. package/dist/chunk-YMT4HAH7.js +20 -0
  16. package/dist/chunk-YMT4HAH7.js.map +1 -0
  17. package/dist/client/index.d.ts +119 -0
  18. package/dist/client/index.js +5 -0
  19. package/dist/client/index.js.map +1 -0
  20. package/dist/core/index.d.ts +29 -0
  21. package/dist/core/index.js +14 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/do/index.d.ts +80 -0
  24. package/dist/do/index.js +110 -0
  25. package/dist/do/index.js.map +1 -0
  26. package/dist/hono/cloudflare-workers.d.ts +21 -0
  27. package/dist/hono/cloudflare-workers.js +68 -0
  28. package/dist/hono/cloudflare-workers.js.map +1 -0
  29. package/dist/hono/index.d.ts +30 -0
  30. package/dist/hono/index.js +74 -0
  31. package/dist/hono/index.js.map +1 -0
  32. package/dist/react/index.d.ts +72 -0
  33. package/dist/react/index.js +126 -0
  34. package/dist/react/index.js.map +1 -0
  35. package/dist/server/index.d.ts +27 -0
  36. package/dist/server/index.js +63 -0
  37. package/dist/server/index.js.map +1 -0
  38. package/dist/socka-report-error-DzFI2Tr7.d.ts +206 -0
  39. package/docs/README.md +18 -0
  40. package/docs/client.md +85 -0
  41. package/docs/comparison.md +36 -0
  42. package/docs/durable-objects.md +74 -0
  43. package/docs/events.md +48 -0
  44. package/docs/getting-started.md +138 -0
  45. package/docs/lifecycle.md +31 -0
  46. package/docs/multi-room.md +31 -0
  47. package/docs/peers.md +85 -0
  48. package/docs/reference.md +123 -0
  49. package/docs/server.md +124 -0
  50. package/examples/minimal-socka.ts +31 -0
  51. package/package.json +148 -0
  52. package/roadmap.md +8 -0
  53. package/skills/socka/core-rpc/SKILL.md +36 -0
  54. package/skills/socka/do-session/SKILL.md +33 -0
  55. package/skills/socka/standard-schema/SKILL.md +26 -0
@@ -0,0 +1,21 @@
1
+ import { Context } from 'hono';
2
+ import { WSEvents } from 'hono/ws';
3
+ import { S as SockaContract, a as SockaContractConfig } from '../socka-report-error-DzFI2Tr7.js';
4
+ import { S as SockaWebSocketSession, b as SockaWebSocketInit, a as SockaWebSocketSessionConfig } from '../SockaWebSocketSession-Bru8yFcK.js';
5
+ import '@standard-schema/spec';
6
+
7
+ type SockaHonoCloudflareOptions<TContract extends SockaContract<SockaContractConfig>, TData> = {
8
+ sessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
9
+ sockaInit?: (c: Context) => SockaWebSocketInit | undefined;
10
+ resolveScope?: (c: Context) => {
11
+ sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
12
+ config: SockaWebSocketSessionConfig<TContract, TData>;
13
+ };
14
+ };
15
+ /**
16
+ * Callback for `upgradeWebSocket` from `hono/cloudflare-workers` (no `onOpen`;
17
+ * the session is created on first `onMessage`).
18
+ */
19
+ declare function sockaHonoCloudflare<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfig<TContract, TData>, options?: SockaHonoCloudflareOptions<TContract, TData>): (c: Context) => Omit<WSEvents<WebSocket>, "onOpen">;
20
+
21
+ export { type SockaHonoCloudflareOptions, sockaHonoCloudflare };
@@ -0,0 +1,68 @@
1
+ import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
2
+ import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-45D4T232.js';
3
+ import { reportSockaError } from '../chunk-MZCQHJXY.js';
4
+
5
+ // src/hono/cloudflare-workers.ts
6
+ function sockaHonoCloudflare(config, options) {
7
+ const staticSessions = options?.sessions ?? /* @__PURE__ */ new Map();
8
+ const staticConfig = config;
9
+ const resolveScope = options?.resolveScope;
10
+ return (c) => ({
11
+ onMessage(evt, wsCtx) {
12
+ const raw = wsCtx.raw;
13
+ if (!raw) return;
14
+ const domWs = raw;
15
+ const { sessions, config: scopeConfig } = resolveScope ? resolveScope(c) : { sessions: staticSessions, config: staticConfig };
16
+ let session = sessions.get(domWs);
17
+ const cfg = scopeConfig;
18
+ if (!session) {
19
+ const init = options?.sockaInit?.(c);
20
+ session = new SockaWebSocketSession(domWs, sessions, cfg, init);
21
+ sessions.set(domWs, session);
22
+ runSockaSessionOnAttached(cfg, session);
23
+ }
24
+ const wireFormat = cfg.wireFormat ?? "json";
25
+ void dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(
26
+ (error) => {
27
+ reportSockaError(cfg.reportError, {
28
+ kind: "serverInboundMessage",
29
+ adapter: "hono",
30
+ error
31
+ });
32
+ }
33
+ );
34
+ },
35
+ onClose(_evt, wsCtx) {
36
+ const raw = wsCtx.raw;
37
+ if (!raw) return;
38
+ const domWs = raw;
39
+ void (async () => {
40
+ const { sessions, config: scopeConfig } = resolveScope ? resolveScope(c) : { sessions: staticSessions, config: staticConfig };
41
+ const cfg = scopeConfig;
42
+ const session = sessions.get(domWs);
43
+ try {
44
+ if (session) {
45
+ await session.invokeHandleClose();
46
+ }
47
+ } catch (error) {
48
+ reportSockaError(cfg.reportError, {
49
+ kind: "serverHandleClose",
50
+ error
51
+ });
52
+ } finally {
53
+ sessions.delete(domWs);
54
+ }
55
+ })().catch((error) => {
56
+ reportSockaError(staticConfig.reportError, {
57
+ kind: "serverShutdown",
58
+ adapter: "hono",
59
+ error
60
+ });
61
+ });
62
+ }
63
+ });
64
+ }
65
+
66
+ export { sockaHonoCloudflare };
67
+ //# sourceMappingURL=cloudflare-workers.js.map
68
+ //# sourceMappingURL=cloudflare-workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hono/cloudflare-workers.ts"],"names":[],"mappings":";;;;;AA6BO,SAAS,mBAAA,CAIf,QACA,OAAA,EACsD;AACtD,EAAA,MAAM,cAAA,GACL,OAAA,EAAS,QAAA,oBACT,IAAI,GAAA,EAAwD;AAC7D,EAAA,MAAM,YAAA,GAAe,MAAA;AACrB,EAAA,MAAM,eAAe,OAAA,EAAS,YAAA;AAE9B,EAAA,OAAO,CAAC,CAAA,MAAgB;AAAA,IACvB,SAAA,CAAU,KAAK,KAAA,EAAO;AACrB,MAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,WAAA,EAAY,GAAI,YAAA,GACvC,YAAA,CAAa,CAAC,CAAA,GACd,EAAE,QAAA,EAAU,cAAA,EAAgB,QAAQ,YAAA,EAAa;AACpD,MAAA,IAAI,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAChC,MAAA,MAAM,GAAA,GAAM,WAAA;AACZ,MAAA,IAAI,CAAC,OAAA,EAAS;AACb,QAAA,MAAM,IAAA,GAAO,OAAA,EAAS,SAAA,GAAY,CAAC,CAAA;AACnC,QAAA,OAAA,GAAU,IAAI,qBAAA,CAAsB,KAAA,EAAO,QAAA,EAAU,KAAK,IAAI,CAAA;AAC9D,QAAA,QAAA,CAAS,GAAA,CAAI,OAAO,OAAO,CAAA;AAC3B,QAAA,yBAAA,CAA0B,KAAK,OAAO,CAAA;AAAA,MACvC;AACA,MAAA,MAAM,UAAA,GAA8B,IAAI,UAAA,IAAc,MAAA;AACtD,MAAA,KAAK,2BAAA,CAA4B,OAAA,EAAS,UAAA,EAAY,GAAA,CAAI,IAAI,CAAA,CAAE,KAAA;AAAA,QAC/D,CAAC,KAAA,KAAmB;AACnB,UAAA,gBAAA,CAAiB,IAAI,WAAA,EAAa;AAAA,YACjC,IAAA,EAAM,sBAAA;AAAA,YACN,OAAA,EAAS,MAAA;AAAA,YACT;AAAA,WACA,CAAA;AAAA,QACF;AAAA,OACD;AAAA,IACD,CAAA;AAAA,IACA,OAAA,CAAQ,MAAM,KAAA,EAAO;AACpB,MAAA,MAAM,MAAM,KAAA,CAAM,GAAA;AAClB,MAAA,IAAI,CAAC,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,GAAA;AACd,MAAA,KAAA,CAAM,YAA2B;AAChC,QAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,WAAA,EAAY,GAAI,YAAA,GACvC,YAAA,CAAa,CAAC,CAAA,GACd,EAAE,QAAA,EAAU,cAAA,EAAgB,QAAQ,YAAA,EAAa;AACpD,QAAA,MAAM,GAAA,GAAM,WAAA;AAIZ,QAAA,MAAM,OAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAA;AAClC,QAAA,IAAI;AACH,UAAA,IAAI,OAAA,EAAS;AACZ,YAAA,MAAM,QAAQ,iBAAA,EAAkB;AAAA,UACjC;AAAA,QACD,SAAS,KAAA,EAAO;AACf,UAAA,gBAAA,CAAiB,IAAI,WAAA,EAAa;AAAA,YACjC,IAAA,EAAM,mBAAA;AAAA,YACN;AAAA,WACA,CAAA;AAAA,QACF,CAAA,SAAE;AACD,UAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA,QACtB;AAAA,MACD,CAAA,GAAG,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AAC9B,QAAA,gBAAA,CAAiB,aAAa,WAAA,EAAa;AAAA,UAC1C,IAAA,EAAM,gBAAA;AAAA,UACN,OAAA,EAAS,MAAA;AAAA,UACT;AAAA,SACA,CAAA;AAAA,MACF,CAAC,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACD","file":"cloudflare-workers.js","sourcesContent":["import type { Context } from \"hono\";\nimport type { WSEvents } from \"hono/ws\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\n\nexport type SockaHonoCloudflareOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\tsessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\tsockaInit?: (c: Context) => SockaWebSocketInit | undefined;\n\tresolveScope?: (c: Context) => {\n\t\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n\t};\n};\n\n/**\n * Callback for `upgradeWebSocket` from `hono/cloudflare-workers` (no `onOpen`;\n * the session is created on first `onMessage`).\n */\nexport function sockaHonoCloudflare<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\toptions?: SockaHonoCloudflareOptions<TContract, TData>,\n): (c: Context) => Omit<WSEvents<WebSocket>, \"onOpen\"> {\n\tconst staticSessions =\n\t\toptions?.sessions ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst staticConfig = config;\n\tconst resolveScope = options?.resolveScope;\n\n\treturn (c: Context) => ({\n\t\tonMessage(evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tlet session = sessions.get(domWs);\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<TContract, TData>;\n\t\t\tif (!session) {\n\t\t\t\tconst init = options?.sockaInit?.(c);\n\t\t\t\tsession = new SockaWebSocketSession(domWs, sessions, cfg, init);\n\t\t\t\tsessions.set(domWs, session);\n\t\t\t\trunSockaSessionOnAttached(cfg, session);\n\t\t\t}\n\t\t\tconst wireFormat: SockaWireFormat = cfg.wireFormat ?? \"json\";\n\t\t\tvoid dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tonClose(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as WebSocket;\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t\t? resolveScope(c)\n\t\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<\n\t\t\t\t\tTContract,\n\t\t\t\t\tTData\n\t\t\t\t>;\n\t\t\t\tconst session = sessions.get(domWs);\n\t\t\t\ttry {\n\t\t\t\t\tif (session) {\n\t\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tsessions.delete(domWs);\n\t\t\t\t}\n\t\t\t})().catch((error: unknown) => {\n\t\t\t\treportSockaError(staticConfig.reportError, {\n\t\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { Context } from 'hono';
2
+ import { WSEvents } from 'hono/ws';
3
+ import { WebSocket as WebSocket$1 } from 'ws';
4
+ import { S as SockaContract, a as SockaContractConfig } from '../socka-report-error-DzFI2Tr7.js';
5
+ import { a as SockaWebSocketSessionConfig, S as SockaWebSocketSession, b as SockaWebSocketInit } from '../SockaWebSocketSession-Bru8yFcK.js';
6
+ import '@standard-schema/spec';
7
+
8
+ type SockaHonoNodeWsOptions<TContract extends SockaContract<SockaContractConfig>, TData> = {
9
+ /** Shared map; default is a new `Map`. */
10
+ sessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
11
+ /** Per-upgrade init for `createData` (e.g. `{ request: c.req.raw }` when it is a `Request`). */
12
+ sockaInit?: (c: Context) => SockaWebSocketInit | undefined;
13
+ /**
14
+ * Resolve the session map and config from this upgrade’s Hono context (e.g. multi-room
15
+ * from `c.req.param("roomId")`). When set, overrides the outer `config` / static `sessions`
16
+ * for each connection.
17
+ */
18
+ resolveScope?: (c: Context) => {
19
+ sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;
20
+ config: SockaWebSocketSessionConfig<TContract, TData>;
21
+ };
22
+ };
23
+ /**
24
+ * Returns the callback passed to `upgradeWebSocket` from
25
+ * {@link https://github.com/honojs/middleware/tree/main/packages/node-ws @hono/node-ws}
26
+ * `createNodeWebSocket({ app }).upgradeWebSocket`.
27
+ */
28
+ declare function sockaHonoNodeWs<TContract extends SockaContract<SockaContractConfig>, TData>(config: SockaWebSocketSessionConfig<TContract, TData>, options?: SockaHonoNodeWsOptions<TContract, TData>): (c: Context) => WSEvents<WebSocket$1>;
29
+
30
+ export { type SockaHonoNodeWsOptions, sockaHonoNodeWs };
@@ -0,0 +1,74 @@
1
+ import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
2
+ import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-45D4T232.js';
3
+ import { reportSockaError } from '../chunk-MZCQHJXY.js';
4
+
5
+ // src/hono/node-ws.ts
6
+ function sockaHonoNodeWs(config, options) {
7
+ const staticSessions = options?.sessions ?? /* @__PURE__ */ new Map();
8
+ const staticConfig = config;
9
+ const resolveScope = options?.resolveScope;
10
+ return (c) => ({
11
+ onOpen(_evt, wsCtx) {
12
+ const raw = wsCtx.raw;
13
+ if (!raw) return;
14
+ const domWs = raw;
15
+ const { sessions, config: scopeConfig } = resolveScope ? resolveScope(c) : { sessions: staticSessions, config: staticConfig };
16
+ const init = options?.sockaInit?.(c);
17
+ const cfg = scopeConfig;
18
+ const session = new SockaWebSocketSession(domWs, sessions, cfg, init);
19
+ sessions.set(domWs, session);
20
+ runSockaSessionOnAttached(cfg, session);
21
+ },
22
+ onMessage(evt, wsCtx) {
23
+ const raw = wsCtx.raw;
24
+ if (!raw) return;
25
+ const domWs = raw;
26
+ const { sessions, config: scopeConfig } = resolveScope ? resolveScope(c) : { sessions: staticSessions, config: staticConfig };
27
+ const session = sessions.get(domWs);
28
+ if (!session) return;
29
+ const cfg = scopeConfig;
30
+ const wireFormat = cfg.wireFormat ?? "json";
31
+ void dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(
32
+ (error) => {
33
+ reportSockaError(cfg.reportError, {
34
+ kind: "serverInboundMessage",
35
+ adapter: "hono",
36
+ error
37
+ });
38
+ }
39
+ );
40
+ },
41
+ onClose(_evt, wsCtx) {
42
+ const raw = wsCtx.raw;
43
+ if (!raw) return;
44
+ const domWs = raw;
45
+ void (async () => {
46
+ const { sessions, config: scopeConfig } = resolveScope ? resolveScope(c) : { sessions: staticSessions, config: staticConfig };
47
+ const cfg = scopeConfig;
48
+ const session = sessions.get(domWs);
49
+ try {
50
+ if (session) {
51
+ await session.invokeHandleClose();
52
+ }
53
+ } catch (error) {
54
+ reportSockaError(cfg.reportError, {
55
+ kind: "serverHandleClose",
56
+ error
57
+ });
58
+ } finally {
59
+ sessions.delete(domWs);
60
+ }
61
+ })().catch((error) => {
62
+ reportSockaError(staticConfig.reportError, {
63
+ kind: "serverShutdown",
64
+ adapter: "hono",
65
+ error
66
+ });
67
+ });
68
+ }
69
+ });
70
+ }
71
+
72
+ export { sockaHonoNodeWs };
73
+ //# sourceMappingURL=index.js.map
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hono/node-ws.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Context } from \"hono\";\nimport type { WSEvents } from \"hono/ws\";\nimport type { WebSocket as NodeWebSocket } from \"ws\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport type { SockaWireFormat } from \"../core/wire-codec\";\nimport { dispatchSockaInboundMessage } from \"../server/dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfig,\n} from \"../server/SockaWebSocketSession\";\n\nexport type SockaHonoNodeWsOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\t/** Shared map; default is a new `Map`. */\n\tsessions?: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t/** Per-upgrade init for `createData` (e.g. `{ request: c.req.raw }` when it is a `Request`). */\n\tsockaInit?: (c: Context) => SockaWebSocketInit | undefined;\n\t/**\n\t * Resolve the session map and config from this upgrade’s Hono context (e.g. multi-room\n\t * from `c.req.param(\"roomId\")`). When set, overrides the outer `config` / static `sessions`\n\t * for each connection.\n\t */\n\tresolveScope?: (c: Context) => {\n\t\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>;\n\t\tconfig: SockaWebSocketSessionConfig<TContract, TData>;\n\t};\n};\n\n/**\n * Returns the callback passed to `upgradeWebSocket` from\n * {@link https://github.com/honojs/middleware/tree/main/packages/node-ws @hono/node-ws}\n * `createNodeWebSocket({ app }).upgradeWebSocket`.\n */\nexport function sockaHonoNodeWs<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\toptions?: SockaHonoNodeWsOptions<TContract, TData>,\n): (c: Context) => WSEvents<NodeWebSocket> {\n\tconst staticSessions =\n\t\toptions?.sessions ??\n\t\tnew Map<WebSocket, SockaWebSocketSession<TContract, TData>>();\n\tconst staticConfig = config;\n\tconst resolveScope = options?.resolveScope;\n\n\treturn (c: Context) => ({\n\t\tonOpen(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tconst init = options?.sockaInit?.(c);\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<TContract, TData>;\n\t\t\tconst session = new SockaWebSocketSession(domWs, sessions, cfg, init);\n\t\t\tsessions.set(domWs, session);\n\t\t\trunSockaSessionOnAttached(cfg, session);\n\t\t},\n\t\tonMessage(evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t? resolveScope(c)\n\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\tconst session = sessions.get(domWs);\n\t\t\tif (!session) return;\n\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<TContract, TData>;\n\t\t\tconst wireFormat: SockaWireFormat = cfg.wireFormat ?? \"json\";\n\t\t\tvoid dispatchSockaInboundMessage(session, wireFormat, evt.data).catch(\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tonClose(_evt, wsCtx) {\n\t\t\tconst raw = wsCtx.raw;\n\t\t\tif (!raw) return;\n\t\t\tconst domWs = raw as unknown as WebSocket;\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\tconst { sessions, config: scopeConfig } = resolveScope\n\t\t\t\t\t? resolveScope(c)\n\t\t\t\t\t: { sessions: staticSessions, config: staticConfig };\n\t\t\t\tconst cfg = scopeConfig as SockaWebSocketSessionConfig<\n\t\t\t\t\tTContract,\n\t\t\t\t\tTData\n\t\t\t\t>;\n\t\t\t\tconst session = sessions.get(domWs);\n\t\t\t\ttry {\n\t\t\t\t\tif (session) {\n\t\t\t\t\t\tawait session.invokeHandleClose();\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\treportSockaError(cfg.reportError, {\n\t\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tsessions.delete(domWs);\n\t\t\t\t}\n\t\t\t})().catch((error: unknown) => {\n\t\t\t\treportSockaError(staticConfig.reportError, {\n\t\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\t\tadapter: \"hono\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,72 @@
1
+ import { DependencyList, RefObject, ReactNode, ReactElement } from 'react';
2
+ import { S as SockaContract, a as SockaContractConfig, f as InferSockaPushHandlers, I as InferSockaSend } from '../socka-report-error-DzFI2Tr7.js';
3
+ import { SockaSessionOptions, SockaSession } from '../client/index.js';
4
+ import '@standard-schema/spec';
5
+
6
+ /** Options for {@link useSocka}. */
7
+ type UseSockaOptions<TContract extends SockaContract<SockaContractConfig>> = SockaSessionOptions<TContract>;
8
+ /**
9
+ * Connects a {@link SockaSession} in an effect: rejects all pending calls and closes
10
+ * the socket on cleanup or when `deps` change.
11
+ */
12
+ declare function useSocka<TContract extends SockaContract<SockaContractConfig>>(options: UseSockaOptions<TContract>, deps: DependencyList): {
13
+ ready: boolean;
14
+ sessionRef: RefObject<SockaSession<TContract> | null>;
15
+ };
16
+
17
+ type UseSockaSessionOptions<TContract extends SockaContract<SockaContractConfig>> = Omit<UseSockaOptions<TContract>, "contract" | "pushHandlers"> & {
18
+ pushHandlers?: Partial<InferSockaPushHandlers<TContract>>;
19
+ };
20
+ /**
21
+ * Builds the same typed `send` object as {@link useSockaSession} from a live session ref.
22
+ * Used by {@link useSockaSessionContext} so consumers do not open extra connections.
23
+ */
24
+ declare function createSockaSendProxyFromSession<TContract extends SockaContract<SockaContractConfig>>(contract: TContract, sessionRef: RefObject<SockaSession<TContract> | null>): InferSockaSend<TContract>;
25
+ /**
26
+ * ```tsx
27
+ * const { ready, send } = useSockaSession(myContract, { url }, deps);
28
+ * await send.echo({ text: "hi" });
29
+ * ```
30
+ */
31
+ declare function useSockaSession<TContract extends SockaContract<SockaContractConfig>>(contract: TContract, options: UseSockaSessionOptions<TContract>, deps: DependencyList): {
32
+ ready: boolean;
33
+ send: InferSockaSend<TContract>;
34
+ sessionRef: RefObject<SockaSession<TContract> | null>;
35
+ };
36
+
37
+ type AnySockaContract = SockaContract<SockaContractConfig>;
38
+ /**
39
+ * Session slice stored on React context by {@link SockaSessionProvider}. The typed
40
+ * `send` object is built in {@link useSockaSessionContext} (same as {@link useSockaSession})
41
+ * so children do not open duplicate WebSockets.
42
+ */
43
+ type SockaSessionContextValue<TContract extends SockaContract<SockaContractConfig> = AnySockaContract> = {
44
+ readonly contract: TContract;
45
+ readonly ready: boolean;
46
+ readonly sessionRef: RefObject<SockaSession<TContract> | null>;
47
+ };
48
+ type SockaSessionProviderProps<TContract extends SockaContract<SockaContractConfig>> = {
49
+ readonly contract: TContract;
50
+ readonly deps: DependencyList;
51
+ readonly children: ReactNode;
52
+ } & UseSockaSessionOptions<TContract>;
53
+ /**
54
+ * Owns a single {@link SockaSession} / WebSocket and exposes it to descendants via
55
+ * {@link useSockaSessionContext}. Mount once per connection (e.g. layout); avoid
56
+ * calling {@link useSockaSession} in every leaf—use the context hook instead.
57
+ */
58
+ declare function SockaSessionProvider<TContract extends SockaContract<SockaContractConfig>>(props: SockaSessionProviderProps<TContract>): ReactElement;
59
+ declare namespace SockaSessionProvider {
60
+ var displayName: string;
61
+ }
62
+ /**
63
+ * Reads the socka session from the nearest {@link SockaSessionProvider}.
64
+ * Pass the **same** `contract` reference as the provider for typing and validation.
65
+ */
66
+ declare function useSockaSessionContext<TContract extends SockaContract<SockaContractConfig>>(contract: TContract): {
67
+ ready: boolean;
68
+ send: InferSockaSend<TContract>;
69
+ sessionRef: RefObject<SockaSession<TContract> | null>;
70
+ };
71
+
72
+ export { type SockaSessionContextValue, SockaSessionProvider, type SockaSessionProviderProps, type UseSockaOptions, type UseSockaSessionOptions, createSockaSendProxyFromSession, useSocka, useSockaSession, useSockaSessionContext };
@@ -0,0 +1,126 @@
1
+ import { SockaSession } from '../chunk-AM7PB26G.js';
2
+ import '../chunk-YMT4HAH7.js';
3
+ import '../chunk-MZCQHJXY.js';
4
+ import { createContext, useRef, useState, useEffect, useMemo, useContext } from 'react';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ function useSocka(options, deps) {
8
+ const { onOpen, onClose, ...restOptions } = options;
9
+ const onOpenRef = useRef(onOpen);
10
+ onOpenRef.current = onOpen;
11
+ const onCloseRef = useRef(onClose);
12
+ onCloseRef.current = onClose;
13
+ const [ready, setReady] = useState(false);
14
+ const sessionRef = useRef(null);
15
+ useEffect(() => {
16
+ let cancelled = false;
17
+ setReady(false);
18
+ const session = new SockaSession({
19
+ ...restOptions,
20
+ onOpen: (event) => {
21
+ if (!cancelled) {
22
+ setReady(true);
23
+ }
24
+ onOpenRef.current?.(event);
25
+ },
26
+ onClose: (event) => {
27
+ if (!cancelled) {
28
+ setReady(false);
29
+ }
30
+ onCloseRef.current?.(event);
31
+ }
32
+ });
33
+ sessionRef.current = session;
34
+ void session.client.connect().then(
35
+ () => {
36
+ if (!cancelled) {
37
+ setReady(true);
38
+ }
39
+ },
40
+ () => {
41
+ }
42
+ );
43
+ return () => {
44
+ cancelled = true;
45
+ sessionRef.current = null;
46
+ session.rejectAllPending(new Error("WebSocket closed"));
47
+ session.close();
48
+ };
49
+ }, deps);
50
+ return { ready, sessionRef };
51
+ }
52
+
53
+ // src/react/useSockaSession.ts
54
+ function createSockaSendProxyFromSession(contract, sessionRef) {
55
+ const proxy = {};
56
+ for (const name of Object.keys(contract.calls)) {
57
+ proxy[name] = (...args) => {
58
+ const session = sessionRef.current;
59
+ if (!session) {
60
+ return Promise.reject(
61
+ new Error("socka: session ref is null; cannot send")
62
+ );
63
+ }
64
+ const fn = session.send[name];
65
+ return fn.apply(session.send, args);
66
+ };
67
+ }
68
+ return proxy;
69
+ }
70
+ function useSockaSession(contract, options, deps) {
71
+ const { pushHandlers, ...sockaOpts } = options;
72
+ const { ready, sessionRef } = useSocka(
73
+ {
74
+ ...sockaOpts,
75
+ contract,
76
+ pushHandlers
77
+ },
78
+ deps
79
+ );
80
+ const send = useMemo(
81
+ () => createSockaSendProxyFromSession(contract, sessionRef),
82
+ [contract, sessionRef]
83
+ );
84
+ return { ready, send, sessionRef };
85
+ }
86
+ var SockaSessionContext = createContext(null);
87
+ function contextMatchesContract(ctx, contract) {
88
+ return ctx.contract === contract;
89
+ }
90
+ function SockaSessionProvider(props) {
91
+ const { contract, deps, children, ...sessionOptions } = props;
92
+ const value = useSockaSession(contract, sessionOptions, deps);
93
+ const merged = {
94
+ contract,
95
+ ready: value.ready,
96
+ sessionRef: value.sessionRef
97
+ };
98
+ return /* @__PURE__ */ jsx(SockaSessionContext.Provider, { value: merged, children });
99
+ }
100
+ SockaSessionProvider.displayName = "SockaSessionProvider";
101
+ function useSockaSessionContext(contract) {
102
+ const ctx = useContext(SockaSessionContext);
103
+ if (ctx === null) {
104
+ throw new Error(
105
+ "useSockaSessionContext must be used within a SockaSessionProvider"
106
+ );
107
+ }
108
+ if (!contextMatchesContract(ctx, contract)) {
109
+ throw new Error(
110
+ "useSockaSessionContext: `contract` must be the same reference as SockaSessionProvider's `contract`"
111
+ );
112
+ }
113
+ const send = useMemo(
114
+ () => createSockaSendProxyFromSession(contract, ctx.sessionRef),
115
+ [contract, ctx.sessionRef]
116
+ );
117
+ return {
118
+ ready: ctx.ready,
119
+ send,
120
+ sessionRef: ctx.sessionRef
121
+ };
122
+ }
123
+
124
+ export { SockaSessionProvider, createSockaSendProxyFromSession, useSocka, useSockaSession, useSockaSessionContext };
125
+ //# sourceMappingURL=index.js.map
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/useSocka.ts","../../src/react/useSockaSession.ts","../../src/react/SockaSessionProvider.tsx"],"names":["useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import type { DependencyList, RefObject } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { SockaSession, type SockaSessionOptions } from \"../client/SockaSession\";\n\n/** Options for {@link useSocka}. */\nexport type UseSockaOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = SockaSessionOptions<TContract>;\n\n/**\n * Connects a {@link SockaSession} in an effect: rejects all pending calls and closes\n * the socket on cleanup or when `deps` change.\n */\nexport function useSocka<TContract extends SockaContract<SockaContractConfig>>(\n\toptions: UseSockaOptions<TContract>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n} {\n\tconst { onOpen, onClose, ...restOptions } = options;\n\n\tconst onOpenRef = useRef(onOpen);\n\tonOpenRef.current = onOpen;\n\tconst onCloseRef = useRef(onClose);\n\tonCloseRef.current = onClose;\n\n\tconst [ready, setReady] = useState(false);\n\tconst sessionRef = useRef<SockaSession<TContract> | null>(null);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tsetReady(false);\n\n\t\tconst session = new SockaSession({\n\t\t\t...restOptions,\n\t\t\tonOpen: (event) => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(true);\n\t\t\t\t}\n\t\t\t\tonOpenRef.current?.(event);\n\t\t\t},\n\t\t\tonClose: (event) => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(false);\n\t\t\t\t}\n\t\t\t\tonCloseRef.current?.(event);\n\t\t\t},\n\t\t});\n\n\t\tsessionRef.current = session;\n\t\tvoid session.client.connect().then(\n\t\t\t() => {\n\t\t\t\tif (!cancelled) {\n\t\t\t\t\tsetReady(true);\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\t/* connect failure: onError / onClose handle UX */\n\t\t\t},\n\t\t);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tsessionRef.current = null;\n\t\t\tsession.rejectAllPending(new Error(\"WebSocket closed\"));\n\t\t\tsession.close();\n\t\t};\n\t}, deps); // deps: explicit reconnect contract for useSocka (see hook docs)\n\n\treturn { ready, sessionRef };\n}\n","import { useMemo, type DependencyList, type RefObject } from \"react\";\nimport type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport type { InferSockaSend, InferSockaPushHandlers } from \"../core/contract\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport { useSocka, type UseSockaOptions } from \"./useSocka\";\n\nexport type UseSockaSessionOptions<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = Omit<UseSockaOptions<TContract>, \"contract\" | \"pushHandlers\"> & {\n\tpushHandlers?: Partial<InferSockaPushHandlers<TContract>>;\n};\n\n/**\n * Builds the same typed `send` object as {@link useSockaSession} from a live session ref.\n * Used by {@link useSockaSessionContext} so consumers do not open extra connections.\n */\nexport function createSockaSendProxyFromSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tcontract: TContract,\n\tsessionRef: RefObject<SockaSession<TContract> | null>,\n): InferSockaSend<TContract> {\n\tconst proxy: Record<string, unknown> = {};\n\tfor (const name of Object.keys(contract.calls)) {\n\t\tproxy[name] = (...args: unknown[]) => {\n\t\t\tconst session = sessionRef.current;\n\t\t\tif (!session) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\"socka: session ref is null; cannot send\"),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst fn = session.send[name as keyof typeof session.send] as (\n\t\t\t\t...a: unknown[]\n\t\t\t) => Promise<unknown>;\n\t\t\treturn fn.apply(session.send, args);\n\t\t};\n\t}\n\treturn proxy as InferSockaSend<TContract>;\n}\n\n/**\n * ```tsx\n * const { ready, send } = useSockaSession(myContract, { url }, deps);\n * await send.echo({ text: \"hi\" });\n * ```\n */\nexport function useSockaSession<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tcontract: TContract,\n\toptions: UseSockaSessionOptions<TContract>,\n\tdeps: DependencyList,\n): {\n\tready: boolean;\n\tsend: InferSockaSend<TContract>;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n} {\n\tconst { pushHandlers, ...sockaOpts } = options;\n\tconst { ready, sessionRef } = useSocka(\n\t\t{\n\t\t\t...sockaOpts,\n\t\t\tcontract,\n\t\t\tpushHandlers,\n\t\t},\n\t\tdeps,\n\t);\n\n\tconst send = useMemo(\n\t\t() => createSockaSendProxyFromSession(contract, sessionRef),\n\t\t[contract, sessionRef],\n\t);\n\n\treturn { ready, send, sessionRef };\n}\n","import type { DependencyList, ReactElement, ReactNode, RefObject } from \"react\";\nimport { createContext, useContext, useMemo } from \"react\";\nimport type { SockaSession } from \"../client/SockaSession\";\nimport type {\n\tSockaContract,\n\tSockaContractConfig,\n\tInferSockaSend,\n} from \"../core/contract\";\nimport {\n\tcreateSockaSendProxyFromSession,\n\tuseSockaSession,\n\ttype UseSockaSessionOptions,\n} from \"./useSockaSession\";\n\ntype AnySockaContract = SockaContract<SockaContractConfig>;\n\n/**\n * Session slice stored on React context by {@link SockaSessionProvider}. The typed\n * `send` object is built in {@link useSockaSessionContext} (same as {@link useSockaSession})\n * so children do not open duplicate WebSockets.\n */\nexport type SockaSessionContextValue<\n\tTContract extends SockaContract<SockaContractConfig> = AnySockaContract,\n> = {\n\treadonly contract: TContract;\n\treadonly ready: boolean;\n\treadonly sessionRef: RefObject<SockaSession<TContract> | null>;\n};\n\nconst SockaSessionContext =\n\tcreateContext<SockaSessionContextValue<AnySockaContract> | null>(null);\n\nfunction contextMatchesContract<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tctx: SockaSessionContextValue<AnySockaContract>,\n\tcontract: TContract,\n): ctx is SockaSessionContextValue<TContract> {\n\treturn ctx.contract === contract;\n}\n\nexport type SockaSessionProviderProps<\n\tTContract extends SockaContract<SockaContractConfig>,\n> = {\n\treadonly contract: TContract;\n\treadonly deps: DependencyList;\n\treadonly children: ReactNode;\n} & UseSockaSessionOptions<TContract>;\n\n/**\n * Owns a single {@link SockaSession} / WebSocket and exposes it to descendants via\n * {@link useSockaSessionContext}. Mount once per connection (e.g. layout); avoid\n * calling {@link useSockaSession} in every leaf—use the context hook instead.\n */\nexport function SockaSessionProvider<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(props: SockaSessionProviderProps<TContract>): ReactElement {\n\tconst { contract, deps, children, ...sessionOptions } = props;\n\tconst value = useSockaSession(contract, sessionOptions, deps);\n\tconst merged: SockaSessionContextValue<TContract> = {\n\t\tcontract,\n\t\tready: value.ready,\n\t\tsessionRef: value.sessionRef,\n\t};\n\treturn (\n\t\t<SockaSessionContext.Provider value={merged}>\n\t\t\t{children}\n\t\t</SockaSessionContext.Provider>\n\t);\n}\n\nSockaSessionProvider.displayName = \"SockaSessionProvider\";\n\n/**\n * Reads the socka session from the nearest {@link SockaSessionProvider}.\n * Pass the **same** `contract` reference as the provider for typing and validation.\n */\nexport function useSockaSessionContext<\n\tTContract extends SockaContract<SockaContractConfig>,\n>(\n\tcontract: TContract,\n): {\n\tready: boolean;\n\tsend: InferSockaSend<TContract>;\n\tsessionRef: RefObject<SockaSession<TContract> | null>;\n} {\n\tconst ctx = useContext(SockaSessionContext);\n\tif (ctx === null) {\n\t\tthrow new Error(\n\t\t\t\"useSockaSessionContext must be used within a SockaSessionProvider\",\n\t\t);\n\t}\n\tif (!contextMatchesContract(ctx, contract)) {\n\t\tthrow new Error(\n\t\t\t\"useSockaSessionContext: `contract` must be the same reference as SockaSessionProvider's `contract`\",\n\t\t);\n\t}\n\tconst send = useMemo(\n\t\t() => createSockaSendProxyFromSession(contract, ctx.sessionRef),\n\t\t[contract, ctx.sessionRef],\n\t);\n\treturn {\n\t\tready: ctx.ready,\n\t\tsend,\n\t\tsessionRef: ctx.sessionRef,\n\t};\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { S as SockaWebSocketSession, a as SockaWebSocketSessionConfig, b as SockaWebSocketInit } from '../SockaWebSocketSession-Bru8yFcK.js';
2
+ export { d as SockaEmitCapable, e as SockaPushSession, c as broadcastSockaEventToPeers, r as runSockaSessionOnAttached } from '../SockaWebSocketSession-Bru8yFcK.js';
3
+ import { S as SockaContract, a as SockaContractConfig, b as SockaWireFormat } from '../socka-report-error-DzFI2Tr7.js';
4
+ import '@standard-schema/spec';
5
+
6
+ type AttachedSockaWebSocket<TContract extends SockaContract<SockaContractConfig>, TData> = {
7
+ session: SockaWebSocketSession<TContract, TData>;
8
+ /** Remove listeners and delete this session from the map (idempotent). */
9
+ dispose: () => void;
10
+ };
11
+ /**
12
+ * Register WebSocket `message` / `close` handlers, insert the session into
13
+ * `sessions`, and return `{ session, dispose }`. `dispose` runs
14
+ * {@link SockaWebSocketSession.invokeHandleClose} once, then removes listeners
15
+ * (also triggered by `close`).
16
+ */
17
+ declare function attachSockaWebSocket<TContract extends SockaContract<SockaContractConfig>, TData>(websocket: WebSocket, sessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>, config: SockaWebSocketSessionConfig<TContract, TData>, init?: SockaWebSocketInit): AttachedSockaWebSocket<TContract, TData>;
18
+
19
+ /**
20
+ * Decode a WebSocket `message` payload and dispatch it to the session (same
21
+ * behavior as the `message` handler installed by {@link attachSockaWebSocket}).
22
+ * Use this when the runtime does not support `addEventListener` on the socket
23
+ * (e.g. Bun {@link ServerWebSocket}) or when handling messages manually.
24
+ */
25
+ declare function dispatchSockaInboundMessage<TContract extends SockaContract<SockaContractConfig>, TData>(session: SockaWebSocketSession<TContract, TData>, wireFormat: SockaWireFormat, data: MessageEvent["data"]): Promise<void>;
26
+
27
+ export { type AttachedSockaWebSocket, SockaWebSocketInit, SockaWebSocketSession, SockaWebSocketSessionConfig, attachSockaWebSocket, dispatchSockaInboundMessage };
@@ -0,0 +1,63 @@
1
+ import { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
2
+ export { dispatchSockaInboundMessage } from '../chunk-5WQTYLIC.js';
3
+ import { SockaWebSocketSession, runSockaSessionOnAttached } from '../chunk-45D4T232.js';
4
+ export { SockaWebSocketSession, broadcastSockaEventToPeers, runSockaSessionOnAttached } from '../chunk-45D4T232.js';
5
+ import { reportSockaError } from '../chunk-MZCQHJXY.js';
6
+
7
+ // src/server/attachSockaWebSocket.ts
8
+ function attachSockaWebSocket(websocket, sessions, config, init) {
9
+ const session = new SockaWebSocketSession(websocket, sessions, config, init);
10
+ sessions.set(websocket, session);
11
+ runSockaSessionOnAttached(config, session);
12
+ let shuttingDown = false;
13
+ const finalize = () => {
14
+ websocket.removeEventListener("message", onMessage);
15
+ websocket.removeEventListener("close", onClose);
16
+ sessions.delete(websocket);
17
+ };
18
+ const shutdown = () => {
19
+ if (shuttingDown) return;
20
+ shuttingDown = true;
21
+ void (async () => {
22
+ try {
23
+ await session.invokeHandleClose();
24
+ } catch (error) {
25
+ reportSockaError(config.reportError, {
26
+ kind: "serverHandleClose",
27
+ error
28
+ });
29
+ } finally {
30
+ finalize();
31
+ }
32
+ })().catch((error) => {
33
+ reportSockaError(config.reportError, {
34
+ kind: "serverShutdown",
35
+ adapter: "attach",
36
+ error
37
+ });
38
+ finalize();
39
+ });
40
+ };
41
+ const onMessage = (ev) => {
42
+ const wf = config.wireFormat ?? "json";
43
+ void dispatchSockaInboundMessage(session, wf, ev.data).catch(
44
+ (error) => {
45
+ reportSockaError(config.reportError, {
46
+ kind: "serverInboundMessage",
47
+ adapter: "attach",
48
+ error
49
+ });
50
+ }
51
+ );
52
+ };
53
+ const onClose = () => {
54
+ shutdown();
55
+ };
56
+ websocket.addEventListener("message", onMessage);
57
+ websocket.addEventListener("close", onClose);
58
+ return { session, dispose: shutdown };
59
+ }
60
+
61
+ export { attachSockaWebSocket };
62
+ //# sourceMappingURL=index.js.map
63
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/attachSockaWebSocket.ts"],"names":[],"mappings":";;;;;;;AAyBO,SAAS,oBAAA,CAIf,SAAA,EACA,QAAA,EACA,MAAA,EACA,IAAA,EAC2C;AAC3C,EAAA,MAAM,UAAU,IAAI,qBAAA,CAAsB,SAAA,EAAW,QAAA,EAAU,QAAQ,IAAI,CAAA;AAC3E,EAAA,QAAA,CAAS,GAAA,CAAI,WAAW,OAAO,CAAA;AAC/B,EAAA,yBAAA,CAA0B,QAAQ,OAAO,CAAA;AAEzC,EAAA,IAAI,YAAA,GAAe,KAAA;AAEnB,EAAA,MAAM,WAAW,MAAY;AAC5B,IAAA,SAAA,CAAU,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAClD,IAAA,SAAA,CAAU,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAC9C,IAAA,QAAA,CAAS,OAAO,SAAS,CAAA;AAAA,EAC1B,CAAA;AAEA,EAAA,MAAM,WAAW,MAAY;AAC5B,IAAA,IAAI,YAAA,EAAc;AAClB,IAAA,YAAA,GAAe,IAAA;AACf,IAAA,KAAA,CAAM,YAA2B;AAChC,MAAA,IAAI;AACH,QAAA,MAAM,QAAQ,iBAAA,EAAkB;AAAA,MACjC,SAAS,KAAA,EAAO;AACf,QAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,UACpC,IAAA,EAAM,mBAAA;AAAA,UACN;AAAA,SACA,CAAA;AAAA,MACF,CAAA,SAAE;AACD,QAAA,QAAA,EAAS;AAAA,MACV;AAAA,IACD,CAAA,GAAG,CAAE,KAAA,CAAM,CAAC,KAAA,KAAmB;AAC9B,MAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,QACpC,IAAA,EAAM,gBAAA;AAAA,QACN,OAAA,EAAS,QAAA;AAAA,QACT;AAAA,OACA,CAAA;AACD,MAAA,QAAA,EAAS;AAAA,IACV,CAAC,CAAA;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,EAAA,KAA2B;AAC7C,IAAA,MAAM,EAAA,GAAK,OAAO,UAAA,IAAc,MAAA;AAChC,IAAA,KAAK,2BAAA,CAA4B,OAAA,EAAS,EAAA,EAAI,EAAA,CAAG,IAAI,CAAA,CAAE,KAAA;AAAA,MACtD,CAAC,KAAA,KAAmB;AACnB,QAAA,gBAAA,CAAiB,OAAO,WAAA,EAAa;AAAA,UACpC,IAAA,EAAM,sBAAA;AAAA,UACN,OAAA,EAAS,QAAA;AAAA,UACT;AAAA,SACA,CAAA;AAAA,MACF;AAAA,KACD;AAAA,EACD,CAAA;AAEA,EAAA,MAAM,UAAU,MAAY;AAC3B,IAAA,QAAA,EAAS;AAAA,EACV,CAAA;AAEA,EAAA,SAAA,CAAU,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC/C,EAAA,SAAA,CAAU,gBAAA,CAAiB,SAAS,OAAO,CAAA;AAE3C,EAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,QAAA,EAAS;AACrC","file":"index.js","sourcesContent":["import type { SockaContract, SockaContractConfig } from \"../core/contract\";\nimport { reportSockaError } from \"../core/socka-report-error\";\nimport { dispatchSockaInboundMessage } from \"./dispatchSockaInboundMessage\";\nimport {\n\tSockaWebSocketSession,\n\trunSockaSessionOnAttached,\n\ttype SockaWebSocketInit,\n\ttype SockaWebSocketSessionConfig,\n} from \"./SockaWebSocketSession\";\n\nexport type AttachedSockaWebSocket<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n> = {\n\tsession: SockaWebSocketSession<TContract, TData>;\n\t/** Remove listeners and delete this session from the map (idempotent). */\n\tdispose: () => void;\n};\n\n/**\n * Register WebSocket `message` / `close` handlers, insert the session into\n * `sessions`, and return `{ session, dispose }`. `dispose` runs\n * {@link SockaWebSocketSession.invokeHandleClose} once, then removes listeners\n * (also triggered by `close`).\n */\nexport function attachSockaWebSocket<\n\tTContract extends SockaContract<SockaContractConfig>,\n\tTData,\n>(\n\twebsocket: WebSocket,\n\tsessions: Map<WebSocket, SockaWebSocketSession<TContract, TData>>,\n\tconfig: SockaWebSocketSessionConfig<TContract, TData>,\n\tinit?: SockaWebSocketInit,\n): AttachedSockaWebSocket<TContract, TData> {\n\tconst session = new SockaWebSocketSession(websocket, sessions, config, init);\n\tsessions.set(websocket, session);\n\trunSockaSessionOnAttached(config, session);\n\n\tlet shuttingDown = false;\n\n\tconst finalize = (): void => {\n\t\twebsocket.removeEventListener(\"message\", onMessage);\n\t\twebsocket.removeEventListener(\"close\", onClose);\n\t\tsessions.delete(websocket);\n\t};\n\n\tconst shutdown = (): void => {\n\t\tif (shuttingDown) return;\n\t\tshuttingDown = true;\n\t\tvoid (async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tawait session.invokeHandleClose();\n\t\t\t} catch (error) {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverHandleClose\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tfinalize();\n\t\t\t}\n\t\t})().catch((error: unknown) => {\n\t\t\treportSockaError(config.reportError, {\n\t\t\t\tkind: \"serverShutdown\",\n\t\t\t\tadapter: \"attach\",\n\t\t\t\terror,\n\t\t\t});\n\t\t\tfinalize();\n\t\t});\n\t};\n\n\tconst onMessage = (ev: MessageEvent): void => {\n\t\tconst wf = config.wireFormat ?? \"json\";\n\t\tvoid dispatchSockaInboundMessage(session, wf, ev.data).catch(\n\t\t\t(error: unknown) => {\n\t\t\t\treportSockaError(config.reportError, {\n\t\t\t\t\tkind: \"serverInboundMessage\",\n\t\t\t\t\tadapter: \"attach\",\n\t\t\t\t\terror,\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t};\n\n\tconst onClose = (): void => {\n\t\tshutdown();\n\t};\n\n\twebsocket.addEventListener(\"message\", onMessage);\n\twebsocket.addEventListener(\"close\", onClose);\n\n\treturn { session, dispose: shutdown };\n}\n"]}