@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.
- package/.env.example +68 -0
- package/CHANGELOG.md +16 -0
- package/Caddyfile +8 -0
- package/Dockerfile +46 -0
- package/LICENSE +190 -0
- package/README.md +89 -0
- package/config.json.example +16 -0
- package/dist/__tests__/helpers.d.ts +56 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +138 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/app.d.ts +38 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +60 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +88 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +15 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +47 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +14 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +26 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/not-found.d.ts +8 -0
- package/dist/middleware/not-found.d.ts.map +1 -0
- package/dist/middleware/not-found.js +12 -0
- package/dist/middleware/not-found.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +17 -0
- package/dist/middleware/request-logger.d.ts.map +1 -0
- package/dist/middleware/request-logger.js +65 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/middleware/ws-auth.d.ts +21 -0
- package/dist/middleware/ws-auth.d.ts.map +1 -0
- package/dist/middleware/ws-auth.js +59 -0
- package/dist/middleware/ws-auth.js.map +1 -0
- package/dist/routes/health.d.ts +11 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +34 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/messages.d.ts +19 -0
- package/dist/routes/messages.d.ts.map +1 -0
- package/dist/routes/messages.js +154 -0
- package/dist/routes/messages.js.map +1 -0
- package/dist/routes/peers.d.ts +17 -0
- package/dist/routes/peers.d.ts.map +1 -0
- package/dist/routes/peers.js +169 -0
- package/dist/routes/peers.js.map +1 -0
- package/dist/routes/poll.d.ts +15 -0
- package/dist/routes/poll.d.ts.map +1 -0
- package/dist/routes/poll.js +97 -0
- package/dist/routes/poll.js.map +1 -0
- package/dist/routes/sessions.d.ts +14 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +113 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/ui.d.ts +21 -0
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +173 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/routes/validation-hook.d.ts +18 -0
- package/dist/routes/validation-hook.d.ts.map +1 -0
- package/dist/routes/validation-hook.js +24 -0
- package/dist/routes/validation-hook.js.map +1 -0
- package/dist/services/auth-service.d.ts +48 -0
- package/dist/services/auth-service.d.ts.map +1 -0
- package/dist/services/auth-service.js +63 -0
- package/dist/services/auth-service.js.map +1 -0
- package/dist/services/connection-manager.d.ts +108 -0
- package/dist/services/connection-manager.d.ts.map +1 -0
- package/dist/services/connection-manager.js +216 -0
- package/dist/services/connection-manager.js.map +1 -0
- package/dist/services/message-queue.d.ts +56 -0
- package/dist/services/message-queue.d.ts.map +1 -0
- package/dist/services/message-queue.js +164 -0
- package/dist/services/message-queue.js.map +1 -0
- package/dist/services/peer-cleanup.d.ts +39 -0
- package/dist/services/peer-cleanup.d.ts.map +1 -0
- package/dist/services/peer-cleanup.js +96 -0
- package/dist/services/peer-cleanup.js.map +1 -0
- package/dist/services/session-cleanup.d.ts +44 -0
- package/dist/services/session-cleanup.d.ts.map +1 -0
- package/dist/services/session-cleanup.js +100 -0
- package/dist/services/session-cleanup.js.map +1 -0
- package/dist/services/session-store.d.ts +103 -0
- package/dist/services/session-store.d.ts.map +1 -0
- package/dist/services/session-store.js +292 -0
- package/dist/services/session-store.js.map +1 -0
- package/dist/services/stats-service.d.ts +48 -0
- package/dist/services/stats-service.d.ts.map +1 -0
- package/dist/services/stats-service.js +77 -0
- package/dist/services/stats-service.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/components/Footer.d.ts +7 -0
- package/dist/ui/components/Footer.d.ts.map +1 -0
- package/dist/ui/components/Footer.js +17 -0
- package/dist/ui/components/Footer.js.map +1 -0
- package/dist/ui/components/Layout.d.ts +13 -0
- package/dist/ui/components/Layout.d.ts.map +1 -0
- package/dist/ui/components/Layout.js +11 -0
- package/dist/ui/components/Layout.js.map +1 -0
- package/dist/ui/components/NavBar.d.ts +12 -0
- package/dist/ui/components/NavBar.d.ts.map +1 -0
- package/dist/ui/components/NavBar.js +60 -0
- package/dist/ui/components/NavBar.js.map +1 -0
- package/dist/ui/components/StatCard.d.ts +14 -0
- package/dist/ui/components/StatCard.d.ts.map +1 -0
- package/dist/ui/components/StatCard.js +32 -0
- package/dist/ui/components/StatCard.js.map +1 -0
- package/dist/ui/components/Terminal.d.ts +13 -0
- package/dist/ui/components/Terminal.d.ts.map +1 -0
- package/dist/ui/components/Terminal.js +37 -0
- package/dist/ui/components/Terminal.js.map +1 -0
- package/dist/ui/pages/AdminDashboard.d.ts +13 -0
- package/dist/ui/pages/AdminDashboard.d.ts.map +1 -0
- package/dist/ui/pages/AdminDashboard.js +59 -0
- package/dist/ui/pages/AdminDashboard.js.map +1 -0
- package/dist/ui/pages/HowToPage.d.ts +8 -0
- package/dist/ui/pages/HowToPage.d.ts.map +1 -0
- package/dist/ui/pages/HowToPage.js +312 -0
- package/dist/ui/pages/HowToPage.js.map +1 -0
- package/dist/ui/pages/LandingPage.d.ts +13 -0
- package/dist/ui/pages/LandingPage.d.ts.map +1 -0
- package/dist/ui/pages/LandingPage.js +160 -0
- package/dist/ui/pages/LandingPage.js.map +1 -0
- package/dist/ui/pages/MessageLog.d.ts +25 -0
- package/dist/ui/pages/MessageLog.d.ts.map +1 -0
- package/dist/ui/pages/MessageLog.js +146 -0
- package/dist/ui/pages/MessageLog.js.map +1 -0
- package/dist/ui/pages/SessionDetail.d.ts +14 -0
- package/dist/ui/pages/SessionDetail.d.ts.map +1 -0
- package/dist/ui/pages/SessionDetail.js +165 -0
- package/dist/ui/pages/SessionDetail.js.map +1 -0
- package/dist/ui/pages/SessionList.d.ts +22 -0
- package/dist/ui/pages/SessionList.d.ts.map +1 -0
- package/dist/ui/pages/SessionList.js +88 -0
- package/dist/ui/pages/SessionList.js.map +1 -0
- package/dist/ui/styles/theme.d.ts +35 -0
- package/dist/ui/styles/theme.d.ts.map +1 -0
- package/dist/ui/styles/theme.js +65 -0
- package/dist/ui/styles/theme.js.map +1 -0
- package/dist/ws/frames.d.ts +82 -0
- package/dist/ws/frames.d.ts.map +1 -0
- package/dist/ws/frames.js +68 -0
- package/dist/ws/frames.js.map +1 -0
- package/dist/ws/handler.d.ts +26 -0
- package/dist/ws/handler.d.ts.map +1 -0
- package/dist/ws/handler.js +72 -0
- package/dist/ws/handler.js.map +1 -0
- package/dist/ws/heartbeat.d.ts +18 -0
- package/dist/ws/heartbeat.d.ts.map +1 -0
- package/dist/ws/heartbeat.js +39 -0
- package/dist/ws/heartbeat.js.map +1 -0
- package/docker-compose.yml +38 -0
- package/nginx.conf.example +63 -0
- 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":"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"}
|
|
@@ -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
|
+
}
|