@essentialai/cogent-server 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 (166) hide show
  1. package/.env.example +68 -0
  2. package/CHANGELOG.md +16 -0
  3. package/Caddyfile +8 -0
  4. package/Dockerfile +46 -0
  5. package/LICENSE +190 -0
  6. package/README.md +89 -0
  7. package/config.json.example +16 -0
  8. package/dist/__tests__/helpers.d.ts +56 -0
  9. package/dist/__tests__/helpers.d.ts.map +1 -0
  10. package/dist/__tests__/helpers.js +138 -0
  11. package/dist/__tests__/helpers.js.map +1 -0
  12. package/dist/app.d.ts +38 -0
  13. package/dist/app.d.ts.map +1 -0
  14. package/dist/app.js +60 -0
  15. package/dist/app.js.map +1 -0
  16. package/dist/config.d.ts +88 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +148 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +102 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/middleware/auth.d.ts +15 -0
  25. package/dist/middleware/auth.d.ts.map +1 -0
  26. package/dist/middleware/auth.js +47 -0
  27. package/dist/middleware/auth.js.map +1 -0
  28. package/dist/middleware/error-handler.d.ts +14 -0
  29. package/dist/middleware/error-handler.d.ts.map +1 -0
  30. package/dist/middleware/error-handler.js +26 -0
  31. package/dist/middleware/error-handler.js.map +1 -0
  32. package/dist/middleware/not-found.d.ts +8 -0
  33. package/dist/middleware/not-found.d.ts.map +1 -0
  34. package/dist/middleware/not-found.js +12 -0
  35. package/dist/middleware/not-found.js.map +1 -0
  36. package/dist/middleware/request-logger.d.ts +17 -0
  37. package/dist/middleware/request-logger.d.ts.map +1 -0
  38. package/dist/middleware/request-logger.js +65 -0
  39. package/dist/middleware/request-logger.js.map +1 -0
  40. package/dist/middleware/ws-auth.d.ts +21 -0
  41. package/dist/middleware/ws-auth.d.ts.map +1 -0
  42. package/dist/middleware/ws-auth.js +59 -0
  43. package/dist/middleware/ws-auth.js.map +1 -0
  44. package/dist/routes/health.d.ts +11 -0
  45. package/dist/routes/health.d.ts.map +1 -0
  46. package/dist/routes/health.js +34 -0
  47. package/dist/routes/health.js.map +1 -0
  48. package/dist/routes/messages.d.ts +19 -0
  49. package/dist/routes/messages.d.ts.map +1 -0
  50. package/dist/routes/messages.js +154 -0
  51. package/dist/routes/messages.js.map +1 -0
  52. package/dist/routes/peers.d.ts +17 -0
  53. package/dist/routes/peers.d.ts.map +1 -0
  54. package/dist/routes/peers.js +169 -0
  55. package/dist/routes/peers.js.map +1 -0
  56. package/dist/routes/poll.d.ts +15 -0
  57. package/dist/routes/poll.d.ts.map +1 -0
  58. package/dist/routes/poll.js +97 -0
  59. package/dist/routes/poll.js.map +1 -0
  60. package/dist/routes/sessions.d.ts +14 -0
  61. package/dist/routes/sessions.d.ts.map +1 -0
  62. package/dist/routes/sessions.js +113 -0
  63. package/dist/routes/sessions.js.map +1 -0
  64. package/dist/routes/ui.d.ts +21 -0
  65. package/dist/routes/ui.d.ts.map +1 -0
  66. package/dist/routes/ui.js +173 -0
  67. package/dist/routes/ui.js.map +1 -0
  68. package/dist/routes/validation-hook.d.ts +18 -0
  69. package/dist/routes/validation-hook.d.ts.map +1 -0
  70. package/dist/routes/validation-hook.js +24 -0
  71. package/dist/routes/validation-hook.js.map +1 -0
  72. package/dist/services/auth-service.d.ts +48 -0
  73. package/dist/services/auth-service.d.ts.map +1 -0
  74. package/dist/services/auth-service.js +63 -0
  75. package/dist/services/auth-service.js.map +1 -0
  76. package/dist/services/connection-manager.d.ts +108 -0
  77. package/dist/services/connection-manager.d.ts.map +1 -0
  78. package/dist/services/connection-manager.js +216 -0
  79. package/dist/services/connection-manager.js.map +1 -0
  80. package/dist/services/message-queue.d.ts +56 -0
  81. package/dist/services/message-queue.d.ts.map +1 -0
  82. package/dist/services/message-queue.js +164 -0
  83. package/dist/services/message-queue.js.map +1 -0
  84. package/dist/services/peer-cleanup.d.ts +39 -0
  85. package/dist/services/peer-cleanup.d.ts.map +1 -0
  86. package/dist/services/peer-cleanup.js +96 -0
  87. package/dist/services/peer-cleanup.js.map +1 -0
  88. package/dist/services/session-cleanup.d.ts +44 -0
  89. package/dist/services/session-cleanup.d.ts.map +1 -0
  90. package/dist/services/session-cleanup.js +100 -0
  91. package/dist/services/session-cleanup.js.map +1 -0
  92. package/dist/services/session-store.d.ts +103 -0
  93. package/dist/services/session-store.d.ts.map +1 -0
  94. package/dist/services/session-store.js +292 -0
  95. package/dist/services/session-store.js.map +1 -0
  96. package/dist/services/stats-service.d.ts +48 -0
  97. package/dist/services/stats-service.d.ts.map +1 -0
  98. package/dist/services/stats-service.js +77 -0
  99. package/dist/services/stats-service.js.map +1 -0
  100. package/dist/types.d.ts +60 -0
  101. package/dist/types.d.ts.map +1 -0
  102. package/dist/types.js +2 -0
  103. package/dist/types.js.map +1 -0
  104. package/dist/ui/components/Footer.d.ts +7 -0
  105. package/dist/ui/components/Footer.d.ts.map +1 -0
  106. package/dist/ui/components/Footer.js +17 -0
  107. package/dist/ui/components/Footer.js.map +1 -0
  108. package/dist/ui/components/Layout.d.ts +13 -0
  109. package/dist/ui/components/Layout.d.ts.map +1 -0
  110. package/dist/ui/components/Layout.js +11 -0
  111. package/dist/ui/components/Layout.js.map +1 -0
  112. package/dist/ui/components/NavBar.d.ts +12 -0
  113. package/dist/ui/components/NavBar.d.ts.map +1 -0
  114. package/dist/ui/components/NavBar.js +60 -0
  115. package/dist/ui/components/NavBar.js.map +1 -0
  116. package/dist/ui/components/StatCard.d.ts +14 -0
  117. package/dist/ui/components/StatCard.d.ts.map +1 -0
  118. package/dist/ui/components/StatCard.js +32 -0
  119. package/dist/ui/components/StatCard.js.map +1 -0
  120. package/dist/ui/components/Terminal.d.ts +13 -0
  121. package/dist/ui/components/Terminal.d.ts.map +1 -0
  122. package/dist/ui/components/Terminal.js +37 -0
  123. package/dist/ui/components/Terminal.js.map +1 -0
  124. package/dist/ui/pages/AdminDashboard.d.ts +13 -0
  125. package/dist/ui/pages/AdminDashboard.d.ts.map +1 -0
  126. package/dist/ui/pages/AdminDashboard.js +59 -0
  127. package/dist/ui/pages/AdminDashboard.js.map +1 -0
  128. package/dist/ui/pages/HowToPage.d.ts +8 -0
  129. package/dist/ui/pages/HowToPage.d.ts.map +1 -0
  130. package/dist/ui/pages/HowToPage.js +312 -0
  131. package/dist/ui/pages/HowToPage.js.map +1 -0
  132. package/dist/ui/pages/LandingPage.d.ts +13 -0
  133. package/dist/ui/pages/LandingPage.d.ts.map +1 -0
  134. package/dist/ui/pages/LandingPage.js +160 -0
  135. package/dist/ui/pages/LandingPage.js.map +1 -0
  136. package/dist/ui/pages/MessageLog.d.ts +25 -0
  137. package/dist/ui/pages/MessageLog.d.ts.map +1 -0
  138. package/dist/ui/pages/MessageLog.js +146 -0
  139. package/dist/ui/pages/MessageLog.js.map +1 -0
  140. package/dist/ui/pages/SessionDetail.d.ts +14 -0
  141. package/dist/ui/pages/SessionDetail.d.ts.map +1 -0
  142. package/dist/ui/pages/SessionDetail.js +165 -0
  143. package/dist/ui/pages/SessionDetail.js.map +1 -0
  144. package/dist/ui/pages/SessionList.d.ts +22 -0
  145. package/dist/ui/pages/SessionList.d.ts.map +1 -0
  146. package/dist/ui/pages/SessionList.js +88 -0
  147. package/dist/ui/pages/SessionList.js.map +1 -0
  148. package/dist/ui/styles/theme.d.ts +35 -0
  149. package/dist/ui/styles/theme.d.ts.map +1 -0
  150. package/dist/ui/styles/theme.js +65 -0
  151. package/dist/ui/styles/theme.js.map +1 -0
  152. package/dist/ws/frames.d.ts +82 -0
  153. package/dist/ws/frames.d.ts.map +1 -0
  154. package/dist/ws/frames.js +68 -0
  155. package/dist/ws/frames.js.map +1 -0
  156. package/dist/ws/handler.d.ts +26 -0
  157. package/dist/ws/handler.d.ts.map +1 -0
  158. package/dist/ws/handler.js +72 -0
  159. package/dist/ws/handler.js.map +1 -0
  160. package/dist/ws/heartbeat.d.ts +18 -0
  161. package/dist/ws/heartbeat.d.ts.map +1 -0
  162. package/dist/ws/heartbeat.js +39 -0
  163. package/dist/ws/heartbeat.js.map +1 -0
  164. package/docker-compose.yml +38 -0
  165. package/nginx.conf.example +63 -0
  166. package/package.json +61 -0
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ import { css } from "hono/css";
3
+ import { Layout } from "../components/Layout.js";
4
+ import { NavBar } from "../components/NavBar.js";
5
+ import { Footer } from "../components/Footer.js";
6
+ import { colors, fonts } from "../styles/theme.js";
7
+ const container = css `
8
+ max-width: 960px;
9
+ margin: 0 auto;
10
+ padding: 0 1.5rem 2rem;
11
+ `;
12
+ const pageTitle = css `
13
+ font-size: 1.5rem;
14
+ font-weight: 700;
15
+ color: ${colors.text};
16
+ margin: 0 0 2rem 0;
17
+ `;
18
+ const tableWrapper = css `
19
+ overflow-x: auto;
20
+ `;
21
+ const tableStyle = css `
22
+ width: 100%;
23
+ border-collapse: collapse;
24
+ font-family: ${fonts.mono};
25
+ font-size: 0.8rem;
26
+
27
+ th {
28
+ text-align: left;
29
+ padding: 0.6rem 0.75rem;
30
+ color: ${colors.textMuted};
31
+ font-weight: 700;
32
+ text-transform: uppercase;
33
+ letter-spacing: 0.05em;
34
+ font-size: 0.7rem;
35
+ border-bottom: 1px solid ${colors.border};
36
+ }
37
+
38
+ td {
39
+ padding: 0.6rem 0.75rem;
40
+ border-bottom: 1px solid ${colors.border};
41
+ color: ${colors.text};
42
+ vertical-align: middle;
43
+ }
44
+
45
+ tr:hover td {
46
+ background: ${colors.bgCard};
47
+ }
48
+ `;
49
+ const sessionLink = css `
50
+ color: ${colors.cyan};
51
+ text-decoration: none;
52
+ &:hover {
53
+ text-decoration: underline;
54
+ }
55
+ `;
56
+ const connectedCount = css `
57
+ color: ${colors.green};
58
+ font-weight: 700;
59
+ `;
60
+ const deleteBtn = css `
61
+ background: transparent;
62
+ border: 1px solid ${colors.red};
63
+ color: ${colors.red};
64
+ padding: 0.25rem 0.6rem;
65
+ border-radius: 3px;
66
+ cursor: pointer;
67
+ font-family: ${fonts.mono};
68
+ font-size: 0.75rem;
69
+
70
+ &:hover {
71
+ background: ${colors.red};
72
+ color: ${colors.bg};
73
+ }
74
+ `;
75
+ const emptyState = css `
76
+ text-align: center;
77
+ padding: 3rem 1rem;
78
+ color: ${colors.textMuted};
79
+ font-size: 0.9rem;
80
+ `;
81
+ /**
82
+ * Session list page with table of all sessions.
83
+ * Each row links to the session detail page and has a delete button.
84
+ */
85
+ export const SessionList = ({ sessions }) => (_jsxs(Layout, { title: "Sessions", children: [_jsx(NavBar, { active: "sessions" }), _jsxs("div", { class: container, children: [_jsx("h1", { class: pageTitle, children: "Sessions" }), sessions.length === 0 ? (_jsx("div", { class: emptyState, children: "No sessions found." })) : (_jsx("div", { class: tableWrapper, children: _jsxs("table", { class: tableStyle, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Session ID" }), _jsx("th", { children: "Label" }), _jsx("th", { children: "Created" }), _jsx("th", { children: "IP" }), _jsx("th", { children: "Peers" }), _jsx("th", { children: "Messages" }), _jsx("th", { children: "Actions" })] }) }), _jsx("tbody", { children: sessions.map((s) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx("a", { href: "/admin/sessions/" + s.sessionId, class: sessionLink, children: s.sessionId.length > 20
86
+ ? s.sessionId.slice(0, 20) + "..."
87
+ : s.sessionId }) }), _jsx("td", { children: s.label ?? "-" }), _jsx("td", { children: new Date(s.createdAt).toLocaleString() }), _jsx("td", { children: s.creatorIp ?? "-" }), _jsxs("td", { children: [s.peerCount, " registered /", " ", _jsx("span", { class: connectedCount, children: s.connectedPeerIds.length }), " ", "connected"] }), _jsx("td", { children: s.messageCount }), _jsx("td", { children: _jsx("form", { method: "post", action: "/admin/sessions/" + s.sessionId + "/delete", style: "display:inline", children: _jsx("button", { type: "submit", class: deleteBtn, onclick: "return confirm('Delete this session and all its data?')", children: "Delete" }) }) })] }))) })] }) })), _jsx(Footer, {})] })] }));
88
+ //# sourceMappingURL=SessionList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionList.js","sourceRoot":"","sources":["../../../src/ui/pages/SessionList.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAkBnD,MAAM,SAAS,GAA2B,GAAG,CAAA;;;;CAI5C,CAAC;AAEF,MAAM,SAAS,GAA2B,GAAG,CAAA;;;WAGlC,MAAM,CAAC,IAAI;;CAErB,CAAC;AAEF,MAAM,YAAY,GAA2B,GAAG,CAAA;;CAE/C,CAAC;AAEF,MAAM,UAAU,GAA2B,GAAG,CAAA;;;iBAG7B,KAAK,CAAC,IAAI;;;;;;aAMd,MAAM,CAAC,SAAS;;;;;+BAKE,MAAM,CAAC,MAAM;;;;;+BAKb,MAAM,CAAC,MAAM;aAC/B,MAAM,CAAC,IAAI;;;;;kBAKN,MAAM,CAAC,MAAM;;CAE9B,CAAC;AAEF,MAAM,WAAW,GAA2B,GAAG,CAAA;WACpC,MAAM,CAAC,IAAI;;;;;CAKrB,CAAC;AAEF,MAAM,cAAc,GAA2B,GAAG,CAAA;WACvC,MAAM,CAAC,KAAK;;CAEtB,CAAC;AAEF,MAAM,SAAS,GAA2B,GAAG,CAAA;;sBAEvB,MAAM,CAAC,GAAG;WACrB,MAAM,CAAC,GAAG;;;;iBAIJ,KAAK,CAAC,IAAI;;;;kBAIT,MAAM,CAAC,GAAG;aACf,MAAM,CAAC,EAAE;;CAErB,CAAC;AAEF,MAAM,UAAU,GAA2B,GAAG,CAAA;;;WAGnC,MAAM,CAAC,SAAS;;CAE1B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAyB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CACjE,MAAC,MAAM,IAAC,KAAK,EAAC,UAAU,aACtB,KAAC,MAAM,IAAC,MAAM,EAAC,UAAU,GAAG,EAC5B,eAAK,KAAK,EAAE,SAAS,aACnB,aAAI,KAAK,EAAE,SAAS,yBAAe,EAElC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,cAAK,KAAK,EAAE,UAAU,mCAA0B,CACjD,CAAC,CAAC,CAAC,CACF,cAAK,KAAK,EAAE,YAAY,YACtB,iBAAO,KAAK,EAAE,UAAU,aACtB,0BACE,yBACE,sCAAmB,EACnB,iCAAc,EACd,mCAAgB,EAChB,8BAAW,EACX,iCAAc,EACd,oCAAiB,EACjB,mCAAgB,IACb,GACC,EACR,0BACG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACnB,yBACE,uBACE,YACE,IAAI,EAAE,kBAAkB,GAAG,CAAC,CAAC,SAAS,EACtC,KAAK,EAAE,WAAW,YAEjB,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE;oDACtB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK;oDAClC,CAAC,CAAC,CAAC,CAAC,SAAS,GACb,GACD,EACL,uBAAK,CAAC,CAAC,KAAK,IAAI,GAAG,GAAM,EACzB,uBAAK,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,cAAc,EAAE,GAAM,EACjD,uBAAK,CAAC,CAAC,SAAS,IAAI,GAAG,GAAM,EAC7B,yBACG,CAAC,CAAC,SAAS,mBAAe,GAAG,EAC9B,eAAM,KAAK,EAAE,cAAc,YACxB,CAAC,CAAC,gBAAgB,CAAC,MAAM,GACrB,EAAC,GAAG,iBAER,EACL,uBAAK,CAAC,CAAC,YAAY,GAAM,EACzB,uBACE,eACE,MAAM,EAAC,MAAM,EACb,MAAM,EAAE,kBAAkB,GAAG,CAAC,CAAC,SAAS,GAAG,SAAS,EACpD,KAAK,EAAC,gBAAgB,YAEtB,iBACE,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,SAAS,EAChB,OAAO,EAAC,yDAAyD,uBAG1D,GACJ,GACJ,IACF,CACN,CAAC,GACI,IACF,GACJ,CACP,EAED,KAAC,MAAM,KAAG,IACN,IACC,CACV,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { css, keyframes } from "hono/css";
2
+ /**
3
+ * Terminal-aesthetic color palette.
4
+ * Dark background with Matrix green accent, muted text, and subtle borders.
5
+ */
6
+ export declare const colors: {
7
+ readonly bg: "#0d1117";
8
+ readonly bgCard: "#161b22";
9
+ readonly bgTerminal: "#0a0a0a";
10
+ readonly text: "#c9d1d9";
11
+ readonly textMuted: "#8b949e";
12
+ readonly green: "#00ff41";
13
+ readonly cyan: "#58a6ff";
14
+ readonly amber: "#e3b341";
15
+ readonly red: "#f85149";
16
+ readonly border: "#30363d";
17
+ readonly borderGlow: "#00ff4133";
18
+ };
19
+ /**
20
+ * Monospace font stack for the terminal aesthetic.
21
+ */
22
+ export declare const fonts: {
23
+ readonly mono: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', monospace";
24
+ };
25
+ /**
26
+ * Cursor blink keyframe animation -- opacity toggle at 50%.
27
+ * Typed as ReturnType<typeof keyframes> to avoid exposing internal CssClassName.
28
+ */
29
+ export declare const cursorBlink: ReturnType<typeof keyframes>;
30
+ /**
31
+ * Global styles using Hono's css helper with :-hono-global selector.
32
+ * Sets box-sizing, font, background, text color, link color, and selection.
33
+ */
34
+ export declare const globalStyles: ReturnType<typeof css>;
35
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/ui/styles/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;CAYT,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,KAAK;;CAER,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,UAAU,CAAC,OAAO,SAAS,CAGpD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,UAAU,CAAC,OAAO,GAAG,CA2B/C,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { css, keyframes } from "hono/css";
2
+ /**
3
+ * Terminal-aesthetic color palette.
4
+ * Dark background with Matrix green accent, muted text, and subtle borders.
5
+ */
6
+ export const colors = {
7
+ bg: "#0d1117",
8
+ bgCard: "#161b22",
9
+ bgTerminal: "#0a0a0a",
10
+ text: "#c9d1d9",
11
+ textMuted: "#8b949e",
12
+ green: "#00ff41",
13
+ cyan: "#58a6ff",
14
+ amber: "#e3b341",
15
+ red: "#f85149",
16
+ border: "#30363d",
17
+ borderGlow: "#00ff4133",
18
+ };
19
+ /**
20
+ * Monospace font stack for the terminal aesthetic.
21
+ */
22
+ export const fonts = {
23
+ mono: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', monospace",
24
+ };
25
+ /**
26
+ * Cursor blink keyframe animation -- opacity toggle at 50%.
27
+ * Typed as ReturnType<typeof keyframes> to avoid exposing internal CssClassName.
28
+ */
29
+ export const cursorBlink = keyframes `
30
+ 0%, 100% { opacity: 1; }
31
+ 50% { opacity: 0; }
32
+ `;
33
+ /**
34
+ * Global styles using Hono's css helper with :-hono-global selector.
35
+ * Sets box-sizing, font, background, text color, link color, and selection.
36
+ */
37
+ export const globalStyles = css `
38
+ :-hono-global {
39
+ * {
40
+ box-sizing: border-box;
41
+ }
42
+ html {
43
+ font-family: ${fonts.mono};
44
+ font-size: 14px;
45
+ background: ${colors.bg};
46
+ color: ${colors.text};
47
+ }
48
+ body {
49
+ margin: 0;
50
+ padding: 0;
51
+ }
52
+ a {
53
+ color: ${colors.cyan};
54
+ text-decoration: none;
55
+ }
56
+ a:hover {
57
+ text-decoration: underline;
58
+ }
59
+ ::selection {
60
+ background: ${colors.green};
61
+ color: ${colors.bgTerminal};
62
+ }
63
+ }
64
+ `;
65
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../../src/ui/styles/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,EAAE,EAAE,SAAS;IACb,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,SAAS;IACrB,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,WAAW;CACf,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,sEAAsE;CACpE,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiC,SAAS,CAAA;;;CAGjE,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAA2B,GAAG,CAAA;;;;;;qBAMlC,KAAK,CAAC,IAAI;;oBAEX,MAAM,CAAC,EAAE;eACd,MAAM,CAAC,IAAI;;;;;;;eAOX,MAAM,CAAC,IAAI;;;;;;;oBAON,MAAM,CAAC,KAAK;eACjB,MAAM,CAAC,UAAU;;;CAG/B,CAAC"}
@@ -0,0 +1,82 @@
1
+ import type { PeerInfo, MessageRecord } from "@essentialai/cogent";
2
+ /**
3
+ * WebSocket close codes for different disconnect scenarios.
4
+ * Uses standard RFC 6455 / IANA-registered codes.
5
+ */
6
+ export declare const WsCloseCode: {
7
+ /** Clean client-initiated close. */
8
+ readonly NORMAL_CLOSURE: 1000;
9
+ /** Heartbeat timeout -- 3 missed pings (90s). */
10
+ readonly HEARTBEAT_TIMEOUT: 1001;
11
+ /** Invalid frame data from client. */
12
+ readonly UNSUPPORTED_DATA: 1003;
13
+ /** Unexpected server error. */
14
+ readonly INTERNAL_ERROR: 1011;
15
+ /** Server shutting down via SIGTERM. */
16
+ readonly SERVICE_RESTART: 1012;
17
+ };
18
+ export interface MessageFrame {
19
+ type: "message";
20
+ payload: MessageRecord;
21
+ timestamp: string;
22
+ }
23
+ export interface PeerConnectedFrame {
24
+ type: "peer_connected";
25
+ payload: {
26
+ peerId: string;
27
+ name: string;
28
+ project: string;
29
+ };
30
+ timestamp: string;
31
+ }
32
+ export interface PeerDisconnectedFrame {
33
+ type: "peer_disconnected";
34
+ payload: {
35
+ peerId: string;
36
+ };
37
+ timestamp: string;
38
+ }
39
+ export interface PeersSnapshotFrame {
40
+ type: "peers_snapshot";
41
+ payload: {
42
+ peers: PeerInfo[];
43
+ };
44
+ timestamp: string;
45
+ }
46
+ export interface QueuedMessagesFrame {
47
+ type: "queued_messages";
48
+ payload: {
49
+ messages: MessageRecord[];
50
+ };
51
+ timestamp: string;
52
+ }
53
+ export interface ErrorFrame {
54
+ type: "error";
55
+ payload: {
56
+ code: string;
57
+ message: string;
58
+ };
59
+ timestamp: string;
60
+ }
61
+ /**
62
+ * Discriminated union of all server-to-client WebSocket frame types.
63
+ * The `type` field acts as the discriminator for client-side dispatch.
64
+ */
65
+ export type WsServerFrame = MessageFrame | PeerConnectedFrame | PeerDisconnectedFrame | PeersSnapshotFrame | QueuedMessagesFrame | ErrorFrame;
66
+ /** Create a frame delivering a single message to a peer. */
67
+ export declare function createMessageFrame(message: MessageRecord): MessageFrame;
68
+ /** Create a frame notifying peers that a new peer has connected via WebSocket. */
69
+ export declare function createPeerConnectedFrame(peer: {
70
+ peerId: string;
71
+ name: string;
72
+ project: string;
73
+ }): PeerConnectedFrame;
74
+ /** Create a frame notifying peers that a peer has disconnected. */
75
+ export declare function createPeerDisconnectedFrame(peerId: string): PeerDisconnectedFrame;
76
+ /** Create a snapshot frame listing all currently connected peers in the session. */
77
+ export declare function createPeersSnapshotFrame(peers: PeerInfo[]): PeersSnapshotFrame;
78
+ /** Create a frame delivering all queued offline messages to a reconnecting peer. */
79
+ export declare function createQueuedMessagesFrame(messages: MessageRecord[]): QueuedMessagesFrame;
80
+ /** Create an error frame sent to a peer before closing the connection. */
81
+ export declare function createErrorFrame(code: string, message: string): ErrorFrame;
82
+ //# sourceMappingURL=frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/ws/frames.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,WAAW;IACtB,oCAAoC;;IAEpC,iDAAiD;;IAEjD,sCAAsC;;IAEtC,+BAA+B;;IAE/B,wCAAwC;;CAEhC,CAAC;AAMX,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE;QAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;KAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE;QAAE,QAAQ,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB,YAAY,GACZ,kBAAkB,GAClB,qBAAqB,GACrB,kBAAkB,GAClB,mBAAmB,GACnB,UAAU,CAAC;AAMf,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAMvE;AAED,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,kBAAkB,CAMrB;AAED,mEAAmE;AACnE,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,GACb,qBAAqB,CAMvB;AAED,oFAAoF;AACpF,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,QAAQ,EAAE,GAChB,kBAAkB,CAMpB;AAED,oFAAoF;AACpF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,aAAa,EAAE,GACxB,mBAAmB,CAMrB;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,UAAU,CAMZ"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * WebSocket close codes for different disconnect scenarios.
3
+ * Uses standard RFC 6455 / IANA-registered codes.
4
+ */
5
+ export const WsCloseCode = {
6
+ /** Clean client-initiated close. */
7
+ NORMAL_CLOSURE: 1000,
8
+ /** Heartbeat timeout -- 3 missed pings (90s). */
9
+ HEARTBEAT_TIMEOUT: 1001,
10
+ /** Invalid frame data from client. */
11
+ UNSUPPORTED_DATA: 1003,
12
+ /** Unexpected server error. */
13
+ INTERNAL_ERROR: 1011,
14
+ /** Server shutting down via SIGTERM. */
15
+ SERVICE_RESTART: 1012,
16
+ };
17
+ // ---------------------------------------------------------------------------
18
+ // Frame creation helpers -- each returns a typed WsServerFrame with timestamp
19
+ // ---------------------------------------------------------------------------
20
+ /** Create a frame delivering a single message to a peer. */
21
+ export function createMessageFrame(message) {
22
+ return {
23
+ type: "message",
24
+ payload: message,
25
+ timestamp: new Date().toISOString(),
26
+ };
27
+ }
28
+ /** Create a frame notifying peers that a new peer has connected via WebSocket. */
29
+ export function createPeerConnectedFrame(peer) {
30
+ return {
31
+ type: "peer_connected",
32
+ payload: peer,
33
+ timestamp: new Date().toISOString(),
34
+ };
35
+ }
36
+ /** Create a frame notifying peers that a peer has disconnected. */
37
+ export function createPeerDisconnectedFrame(peerId) {
38
+ return {
39
+ type: "peer_disconnected",
40
+ payload: { peerId },
41
+ timestamp: new Date().toISOString(),
42
+ };
43
+ }
44
+ /** Create a snapshot frame listing all currently connected peers in the session. */
45
+ export function createPeersSnapshotFrame(peers) {
46
+ return {
47
+ type: "peers_snapshot",
48
+ payload: { peers },
49
+ timestamp: new Date().toISOString(),
50
+ };
51
+ }
52
+ /** Create a frame delivering all queued offline messages to a reconnecting peer. */
53
+ export function createQueuedMessagesFrame(messages) {
54
+ return {
55
+ type: "queued_messages",
56
+ payload: { messages },
57
+ timestamp: new Date().toISOString(),
58
+ };
59
+ }
60
+ /** Create an error frame sent to a peer before closing the connection. */
61
+ export function createErrorFrame(code, message) {
62
+ return {
63
+ type: "error",
64
+ payload: { code, message },
65
+ timestamp: new Date().toISOString(),
66
+ };
67
+ }
68
+ //# sourceMappingURL=frames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.js","sourceRoot":"","sources":["../../src/ws/frames.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,oCAAoC;IACpC,cAAc,EAAE,IAAI;IACpB,iDAAiD;IACjD,iBAAiB,EAAE,IAAI;IACvB,sCAAsC;IACtC,gBAAgB,EAAE,IAAI;IACtB,+BAA+B;IAC/B,cAAc,EAAE,IAAI;IACpB,wCAAwC;IACxC,eAAe,EAAE,IAAI;CACb,CAAC;AAsDX,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAE9E,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAAC,OAAsB;IACvD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,wBAAwB,CAAC,IAIxC;IACC,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,2BAA2B,CACzC,MAAc;IAEd,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,EAAE,MAAM,EAAE;QACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,wBAAwB,CACtC,KAAiB;IAEjB,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,EAAE,KAAK,EAAE;QAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,yBAAyB,CACvC,QAAyB;IAEzB,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,EAAE,QAAQ,EAAE;QACrB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,OAAe;IAEf,OAAO;QACL,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;QAC1B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { WSEvents } from "hono/ws";
2
+ import type { ConnectionManager } from "../services/connection-manager.js";
3
+ import type { MessageQueueService } from "../services/message-queue.js";
4
+ import type { SessionStore } from "../services/session-store.js";
5
+ /**
6
+ * Dependencies for the WebSocket handler.
7
+ * Injected via createWsHandler to keep the handler testable.
8
+ */
9
+ export interface WsHandlerDeps {
10
+ connectionManager: ConnectionManager;
11
+ messageQueue: MessageQueueService;
12
+ sessionStore: SessionStore;
13
+ heartbeatIntervalMs: number;
14
+ heartbeatMaxMissed: number;
15
+ }
16
+ /**
17
+ * Create a WSEvents handler for a single WebSocket connection.
18
+ *
19
+ * Manages the full connection lifecycle:
20
+ * - onOpen: heartbeat setup, connection tracking, peers snapshot, offline queue flush, broadcast peer_connected
21
+ * - onMessage: log and ignore (clients use REST for actions)
22
+ * - onClose: remove connection from tracker (triggers grace timer internally)
23
+ * - onError: log and continue
24
+ */
25
+ export declare function createWsHandler(sessionId: string, peerId: string, deps: WsHandlerDeps): WSEvents;
26
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/ws/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAQjE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,YAAY,EAAE,mBAAmB,CAAC;IAClC,YAAY,EAAE,YAAY,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAClB,QAAQ,CA+EV"}
@@ -0,0 +1,72 @@
1
+ import { setupHeartbeat } from "./heartbeat.js";
2
+ import { createPeersSnapshotFrame, createQueuedMessagesFrame, createPeerConnectedFrame, } from "./frames.js";
3
+ /**
4
+ * Create a WSEvents handler for a single WebSocket connection.
5
+ *
6
+ * Manages the full connection lifecycle:
7
+ * - onOpen: heartbeat setup, connection tracking, peers snapshot, offline queue flush, broadcast peer_connected
8
+ * - onMessage: log and ignore (clients use REST for actions)
9
+ * - onClose: remove connection from tracker (triggers grace timer internally)
10
+ * - onError: log and continue
11
+ */
12
+ export function createWsHandler(sessionId, peerId, deps) {
13
+ return {
14
+ async onOpen(_evt, ws) {
15
+ const rawWs = ws.raw;
16
+ // 1. Set up heartbeat ping/pong
17
+ const heartbeatInterval = setupHeartbeat(rawWs, deps.heartbeatIntervalMs, deps.heartbeatMaxMissed);
18
+ // 2. Register connection in the connection manager
19
+ const conn = {
20
+ ws,
21
+ rawWs,
22
+ peerId,
23
+ sessionId,
24
+ connectedAt: Date.now(),
25
+ heartbeatInterval,
26
+ };
27
+ deps.connectionManager.addConnection(conn);
28
+ // 3. Send peers_snapshot: all currently connected peer IDs with their info
29
+ const connectedPeerIds = deps.connectionManager.getConnectedPeerIds(sessionId);
30
+ const session = await deps.sessionStore.getSession(sessionId);
31
+ if (session) {
32
+ const peers = connectedPeerIds
33
+ .map((pid) => session.peers[pid])
34
+ .filter((p) => p !== undefined);
35
+ const snapshotFrame = createPeersSnapshotFrame(peers);
36
+ ws.send(JSON.stringify(snapshotFrame));
37
+ }
38
+ // 4. Flush offline message queue
39
+ const messages = await deps.messageQueue.dequeueAll(sessionId, peerId);
40
+ if (messages.length > 0) {
41
+ const queueFrame = createQueuedMessagesFrame(messages);
42
+ ws.send(JSON.stringify(queueFrame));
43
+ }
44
+ // 5. Broadcast peer_connected to other peers in the session
45
+ if (session) {
46
+ const peerInfo = session.peers[peerId];
47
+ if (peerInfo) {
48
+ const connectedFrame = createPeerConnectedFrame({
49
+ peerId: peerInfo.peerId,
50
+ name: peerInfo.label,
51
+ project: peerInfo.cwd,
52
+ });
53
+ deps.connectionManager.broadcastToSession(sessionId, JSON.stringify(connectedFrame), peerId);
54
+ }
55
+ }
56
+ },
57
+ onMessage(_evt, _ws) {
58
+ // Clients use REST for all state-mutating operations.
59
+ // No recognized client-to-server frame types needed yet.
60
+ // Protocol-level ping/pong handles keepalive at the ws layer.
61
+ },
62
+ onClose(_evt, ws) {
63
+ // Remove connection from tracker -- this handles grace timer start internally
64
+ deps.connectionManager.removeConnection(sessionId, peerId, ws);
65
+ },
66
+ onError(evt, _ws) {
67
+ // Log the error but do not crash the server
68
+ console.error(`WebSocket error for peer ${peerId} in session ${sessionId}:`, evt);
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/ws/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type WebSocket from "ws";
2
+ /**
3
+ * Set up server-side ping/pong heartbeat for a WebSocket connection.
4
+ *
5
+ * Sends native WebSocket ping frames at the configured interval.
6
+ * If `maxMissed` consecutive pongs are not received, the connection
7
+ * is forcefully terminated via `ws.terminate()`.
8
+ *
9
+ * The client's WebSocket stack auto-replies to ping frames with pong,
10
+ * so no application-level keepalive is needed.
11
+ *
12
+ * @param rawWs The underlying ws.WebSocket object (from ws.raw)
13
+ * @param intervalMs Milliseconds between ping frames (default: 30s)
14
+ * @param maxMissed Number of missed pongs before termination (default: 3)
15
+ * @returns The interval handle (caller should store it for cleanup)
16
+ */
17
+ export declare function setupHeartbeat(rawWs: WebSocket, intervalMs: number, maxMissed: number): ReturnType<typeof setInterval>;
18
+ //# sourceMappingURL=heartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../../src/ws/heartbeat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,UAAU,CAAC,OAAO,WAAW,CAAC,CA2BhC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Set up server-side ping/pong heartbeat for a WebSocket connection.
3
+ *
4
+ * Sends native WebSocket ping frames at the configured interval.
5
+ * If `maxMissed` consecutive pongs are not received, the connection
6
+ * is forcefully terminated via `ws.terminate()`.
7
+ *
8
+ * The client's WebSocket stack auto-replies to ping frames with pong,
9
+ * so no application-level keepalive is needed.
10
+ *
11
+ * @param rawWs The underlying ws.WebSocket object (from ws.raw)
12
+ * @param intervalMs Milliseconds between ping frames (default: 30s)
13
+ * @param maxMissed Number of missed pongs before termination (default: 3)
14
+ * @returns The interval handle (caller should store it for cleanup)
15
+ */
16
+ export function setupHeartbeat(rawWs, intervalMs, maxMissed) {
17
+ let missedPings = 0;
18
+ // Reset counter when pong is received
19
+ rawWs.on("pong", () => {
20
+ missedPings = 0;
21
+ });
22
+ const interval = setInterval(() => {
23
+ // Check if we've exceeded the missed pong threshold
24
+ if (missedPings >= maxMissed) {
25
+ rawWs.terminate();
26
+ clearInterval(interval);
27
+ return;
28
+ }
29
+ // Increment and send ping
30
+ missedPings++;
31
+ rawWs.ping();
32
+ }, intervalMs);
33
+ // Fast-path cleanup when connection closes normally
34
+ rawWs.on("close", () => {
35
+ clearInterval(interval);
36
+ });
37
+ return interval;
38
+ }
39
+ //# sourceMappingURL=heartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../../src/ws/heartbeat.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAgB,EAChB,UAAkB,EAClB,SAAiB;IAEjB,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,sCAAsC;IACtC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,WAAW,GAAG,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,oDAAoD;QACpD,IAAI,WAAW,IAAI,SAAS,EAAE,CAAC;YAC7B,KAAK,CAAC,SAAS,EAAE,CAAC;YAClB,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QAED,0BAA0B;QAC1B,WAAW,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,EAAE,CAAC;IACf,CAAC,EAAE,UAAU,CAAC,CAAC;IAEf,oDAAoD;IACpD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,38 @@
1
+ services:
2
+ server:
3
+ build:
4
+ context: ..
5
+ dockerfile: server/Dockerfile
6
+ restart: unless-stopped
7
+ expose:
8
+ - "3000"
9
+ volumes:
10
+ - cogent_data:/data
11
+ env_file:
12
+ - .env
13
+ environment:
14
+ - COGENT_SERVER_STATE_DIR=/data/sessions
15
+ - NODE_ENV=production
16
+ stop_grace_period: 30s
17
+
18
+ caddy:
19
+ image: caddy:2-alpine
20
+ restart: unless-stopped
21
+ ports:
22
+ - "80:80"
23
+ - "443:443"
24
+ - "443:443/udp"
25
+ volumes:
26
+ - ./Caddyfile:/etc/caddy/Caddyfile:ro
27
+ - caddy_data:/data
28
+ - caddy_config:/config
29
+ env_file:
30
+ - .env
31
+ depends_on:
32
+ server:
33
+ condition: service_healthy
34
+
35
+ volumes:
36
+ cogent_data:
37
+ caddy_data:
38
+ caddy_config:
@@ -0,0 +1,63 @@
1
+ # CC Cloud Bridge Server - Nginx Reverse Proxy Configuration
2
+ #
3
+ # Alternative to Caddy. Requires manual TLS certificate management via certbot.
4
+ # The docker-compose.yml uses Caddy by default for automatic HTTPS.
5
+ #
6
+ # To use this instead of Caddy:
7
+ # 1. Replace "example.com" with your actual domain throughout this file
8
+ # 2. Obtain TLS certificates: certbot certonly --standalone -d example.com
9
+ # 3. Set up certbot auto-renewal: certbot renew --quiet (via cron/systemd timer)
10
+ # 4. Point the server upstream to your CC Bridge Server instance
11
+ #
12
+ # Reference: https://nginx.org/en/docs/http/websocket.html
13
+
14
+ http {
15
+ # WebSocket upgrade mapping -- required for WS proxying
16
+ map $http_upgrade $connection_upgrade {
17
+ default upgrade;
18
+ '' close;
19
+ }
20
+
21
+ upstream cc_bridge {
22
+ # Replace with your server address if not running on localhost
23
+ server 127.0.0.1:3000;
24
+ }
25
+
26
+ # Redirect all HTTP traffic to HTTPS
27
+ server {
28
+ listen 80;
29
+ # Replace example.com with your domain
30
+ server_name example.com;
31
+ return 301 https://$host$request_uri;
32
+ }
33
+
34
+ server {
35
+ listen 443 ssl;
36
+ # Replace example.com with your domain
37
+ server_name example.com;
38
+
39
+ # TLS certificates (managed by certbot)
40
+ # Replace example.com with your domain
41
+ ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
42
+ ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
43
+
44
+ location / {
45
+ proxy_pass http://cc_bridge;
46
+ proxy_http_version 1.1;
47
+
48
+ # WebSocket upgrade headers
49
+ proxy_set_header Upgrade $http_upgrade;
50
+ proxy_set_header Connection $connection_upgrade;
51
+
52
+ # Preserve original client information
53
+ proxy_set_header Host $host;
54
+ proxy_set_header X-Real-IP $remote_addr;
55
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
56
+ proxy_set_header X-Forwarded-Proto $scheme;
57
+
58
+ # WebSocket timeout (default 60s is too short for long-lived connections)
59
+ proxy_read_timeout 86400s;
60
+ proxy_send_timeout 86400s;
61
+ }
62
+ }
63
+ }