@thisisjeron/openclaw-better-gateway 1.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/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # OpenClaw Better Gateway
2
+
3
+ An OpenClaw plugin that enhances the gateway web UI with automatic WebSocket reconnection and quality-of-life improvements.
4
+
5
+ ## Features
6
+
7
+ ✅ **Auto-Reconnect** — WebSocket disconnects are automatically recovered (up to 10 attempts)
8
+ ✅ **Connection Status Indicator** — Visual feedback showing connection state
9
+ ✅ **Network Awareness** — Detects online/offline and reconnects when back
10
+ ✅ **Drop-in Enhancement** — Same gateway UI, just better
11
+
12
+ ## Installation
13
+
14
+ ### From npm (coming soon)
15
+ ```bash
16
+ openclaw plugins install @thisisjeron/openclaw-better-gateway
17
+ ```
18
+
19
+ ### From source
20
+ ```bash
21
+ git clone https://github.com/ThisIsJeron/openclaw-better-gateway.git
22
+ cd openclaw-better-gateway
23
+ npm install && npm run build
24
+ openclaw plugins install -l .
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ After installation and gateway restart, access the enhanced UI at:
30
+ ```
31
+ https://your-gateway/better-gateway/
32
+ ```
33
+
34
+ ### Endpoints
35
+
36
+ | Path | Description |
37
+ |------|-------------|
38
+ | `/better-gateway/` | Enhanced gateway UI with auto-reconnect |
39
+ | `/better-gateway/help` | Installation instructions & bookmarklet |
40
+ | `/better-gateway/inject.js` | Standalone script for manual injection |
41
+
42
+ ## Configuration
43
+
44
+ In your OpenClaw config (`openclaw.json`):
45
+
46
+ ```json
47
+ {
48
+ "plugins": {
49
+ "entries": {
50
+ "better-gateway": {
51
+ "enabled": true,
52
+ "reconnectIntervalMs": 3000,
53
+ "maxReconnectAttempts": 10
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## How It Works
61
+
62
+ The plugin:
63
+ 1. Proxies the original gateway UI from `/`
64
+ 2. Injects an auto-reconnect script that wraps WebSocket
65
+ 3. Serves the enhanced version at `/better-gateway/`
66
+
67
+ When a WebSocket connection drops unexpectedly, the script automatically attempts to reconnect instead of showing "please refresh" errors.
68
+
69
+ ## Roadmap
70
+
71
+ ### Phase 1: Core Stability ✅
72
+ - [x] Auto-reconnect on WebSocket disconnect
73
+ - [x] Connection status indicator
74
+ - [x] Network online/offline detection
75
+ - [x] Configurable retry attempts and intervals
76
+
77
+ ### Phase 2: Enhanced UX
78
+ - [ ] Session state recovery after gateway restart
79
+ - [ ] Smarter reconnection (exponential backoff)
80
+ - [ ] Toast notifications for connection events
81
+ - [ ] Persist UI state across reconnects
82
+
83
+ ### Phase 3: Customization
84
+ - [ ] Theme support (dark/light/custom)
85
+ - [ ] Custom CSS injection
86
+ - [ ] Widget system for dashboard additions
87
+ - [ ] User preferences storage
88
+
89
+ ### Phase 4: Power Features
90
+ - [ ] Multi-gateway dashboard
91
+ - [ ] Session comparison view
92
+ - [ ] Performance metrics overlay
93
+ - [ ] Keyboard shortcuts
94
+ - [ ] Command palette
95
+
96
+ ## Development
97
+
98
+ ```bash
99
+ # Install dependencies
100
+ npm install
101
+
102
+ # Build
103
+ npm run build
104
+
105
+ # Run tests
106
+ npm test
107
+
108
+ # Watch mode
109
+ npm run dev
110
+ ```
111
+
112
+ ## Contributing
113
+
114
+ PRs welcome! Please include tests for new features.
115
+
116
+ ## License
117
+
118
+ MIT
119
+
120
+ ---
121
+
122
+ Built with 🐾 by [ThisIsJeron](https://github.com/ThisIsJeron) and Clawd
@@ -0,0 +1,36 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+ interface PluginConfig {
3
+ reconnectIntervalMs: number;
4
+ maxReconnectAttempts: number;
5
+ }
6
+ interface PluginApi {
7
+ registerHttpHandler: (handler: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>) => void;
8
+ logger: {
9
+ info: (msg: string) => void;
10
+ warn: (msg: string) => void;
11
+ error: (msg: string) => void;
12
+ debug: (msg: string) => void;
13
+ };
14
+ dataDir: string;
15
+ pluginConfig: PluginConfig;
16
+ }
17
+ declare const _default: {
18
+ id: string;
19
+ name: string;
20
+ configSchema: {
21
+ parse(raw: unknown): PluginConfig;
22
+ uiHints: {
23
+ reconnectIntervalMs: {
24
+ label: string;
25
+ placeholder: string;
26
+ };
27
+ maxReconnectAttempts: {
28
+ label: string;
29
+ placeholder: string;
30
+ };
31
+ };
32
+ };
33
+ register(api: PluginApi): void;
34
+ };
35
+ export default _default;
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAA0B,MAAM,WAAW,CAAC;AAQpF,UAAU,YAAY;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,SAAS;IACjB,mBAAmB,EAAE,CACnB,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,KACrE,IAAI,CAAC;IACV,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAC7B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;KAC9B,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC;CAC5B;;;;;mBAgJc,OAAO,GAAG,YAAY;;;;;;;;;;;;kBAqBrB,SAAS,GAAG,IAAI;;AA1BhC,wBA2JE"}
package/dist/index.js ADDED
@@ -0,0 +1,266 @@
1
+ import { request as httpRequest } from "node:http";
2
+ import { readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ const DEFAULT_CONFIG = {
8
+ reconnectIntervalMs: 3000,
9
+ maxReconnectAttempts: 10,
10
+ };
11
+ let injectScript = null;
12
+ function loadInjectScript() {
13
+ if (injectScript === null) {
14
+ const scriptPath = join(__dirname, "inject.js");
15
+ injectScript = readFileSync(scriptPath, "utf-8");
16
+ }
17
+ return injectScript;
18
+ }
19
+ function generateConfigScript(config) {
20
+ return `window.__BETTER_GATEWAY_CONFIG__ = ${JSON.stringify({
21
+ reconnectIntervalMs: config.reconnectIntervalMs,
22
+ maxReconnectAttempts: config.maxReconnectAttempts,
23
+ })};`;
24
+ }
25
+ function generateLandingPage(config, gatewayHost) {
26
+ const script = loadInjectScript();
27
+ const bookmarklet = `javascript:(function(){${encodeURIComponent(script.replace(/\n/g, " "))}})()`;
28
+ return `<!DOCTYPE html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="UTF-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>Better Gateway</title>
34
+ <style>
35
+ body {
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
37
+ max-width: 800px;
38
+ margin: 40px auto;
39
+ padding: 20px;
40
+ background: #1a1a2e;
41
+ color: #eee;
42
+ }
43
+ h1 { color: #00d4ff; }
44
+ h2 { color: #888; margin-top: 2em; }
45
+ code {
46
+ background: #2d2d44;
47
+ padding: 2px 6px;
48
+ border-radius: 4px;
49
+ font-size: 0.9em;
50
+ }
51
+ pre {
52
+ background: #2d2d44;
53
+ padding: 16px;
54
+ border-radius: 8px;
55
+ overflow-x: auto;
56
+ }
57
+ .bookmarklet {
58
+ display: inline-block;
59
+ background: #00d4ff;
60
+ color: #1a1a2e;
61
+ padding: 12px 24px;
62
+ border-radius: 8px;
63
+ text-decoration: none;
64
+ font-weight: bold;
65
+ margin: 10px 0;
66
+ }
67
+ .bookmarklet:hover { background: #00b8e6; }
68
+ .status {
69
+ display: inline-block;
70
+ padding: 4px 12px;
71
+ border-radius: 4px;
72
+ font-size: 0.85em;
73
+ }
74
+ .status.ok { background: #2d5a27; color: #7fff7f; }
75
+ .feature { margin: 8px 0; padding-left: 20px; }
76
+ .feature::before { content: "✓ "; color: #00d4ff; }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <h1>🔌 Better Gateway</h1>
81
+ <p>Auto-reconnect enhancement for OpenClaw Gateway UI</p>
82
+
83
+ <h2>Features</h2>
84
+ <div class="feature">Automatic WebSocket reconnection on disconnect</div>
85
+ <div class="feature">Visual connection status indicator</div>
86
+ <div class="feature">Network online/offline detection</div>
87
+ <div class="feature">Configurable retry attempts (${config.maxReconnectAttempts} max)</div>
88
+ <div class="feature">Reconnect interval: ${config.reconnectIntervalMs}ms</div>
89
+
90
+ <h2>Option 1: Bookmarklet</h2>
91
+ <p>Drag this to your bookmarks bar, then click it when on the Gateway UI:</p>
92
+ <p><a class="bookmarklet" href="${bookmarklet}">⚡ Better Gateway</a></p>
93
+
94
+ <h2>Option 2: Console Injection</h2>
95
+ <p>Open DevTools (F12) on the Gateway UI and paste:</p>
96
+ <pre>fetch('/better-gateway/inject.js').then(r=>r.text()).then(eval)</pre>
97
+
98
+ <h2>Option 3: Userscript (Tampermonkey)</h2>
99
+ <p>Create a new userscript with:</p>
100
+ <pre>// ==UserScript==
101
+ // @name Better Gateway
102
+ // @match ${gatewayHost}/*
103
+ // @grant none
104
+ // ==/UserScript==
105
+
106
+ fetch('/better-gateway/inject.js').then(r=>r.text()).then(eval);</pre>
107
+
108
+ <h2>Script URL</h2>
109
+ <p><code>/better-gateway/inject.js</code></p>
110
+
111
+ <hr style="margin: 40px 0; border-color: #333;">
112
+ <p style="color: #666; font-size: 0.85em;">
113
+ <a href="https://github.com/ThisIsJeron/openclaw-better-gateway" style="color: #00d4ff;">GitHub</a> ·
114
+ Config: reconnect=${config.reconnectIntervalMs}ms, maxAttempts=${config.maxReconnectAttempts}
115
+ </p>
116
+ </body>
117
+ </html>`;
118
+ }
119
+ function generateUserscript(config, gatewayUrl) {
120
+ const script = loadInjectScript();
121
+ return `// ==UserScript==
122
+ // @name Better Gateway - Auto Reconnect
123
+ // @namespace https://github.com/ThisIsJeron/openclaw-better-gateway
124
+ // @version 1.0.0
125
+ // @description Adds automatic WebSocket reconnection to OpenClaw Gateway UI
126
+ // @match ${gatewayUrl}/*
127
+ // @grant none
128
+ // ==/UserScript==
129
+
130
+ window.__BETTER_GATEWAY_CONFIG__ = ${JSON.stringify({
131
+ reconnectIntervalMs: config.reconnectIntervalMs,
132
+ maxReconnectAttempts: config.maxReconnectAttempts,
133
+ })};
134
+
135
+ ${script}`;
136
+ }
137
+ export default {
138
+ id: "better-gateway",
139
+ name: "Better Gateway",
140
+ configSchema: {
141
+ parse(raw) {
142
+ const config = raw || {};
143
+ return {
144
+ reconnectIntervalMs: config.reconnectIntervalMs ?? DEFAULT_CONFIG.reconnectIntervalMs,
145
+ maxReconnectAttempts: config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts,
146
+ };
147
+ },
148
+ uiHints: {
149
+ reconnectIntervalMs: {
150
+ label: "Reconnect Interval (ms)",
151
+ placeholder: "3000",
152
+ },
153
+ maxReconnectAttempts: {
154
+ label: "Max Reconnect Attempts",
155
+ placeholder: "10",
156
+ },
157
+ },
158
+ },
159
+ register(api) {
160
+ const config = { ...DEFAULT_CONFIG, ...(api.pluginConfig || {}) };
161
+ api.logger.info(`Better Gateway loaded (reconnect: ${config.reconnectIntervalMs}ms, max: ${config.maxReconnectAttempts})`);
162
+ api.registerHttpHandler(async (req, res) => {
163
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
164
+ const pathname = url.pathname;
165
+ if (!pathname.startsWith("/better-gateway")) {
166
+ return false;
167
+ }
168
+ const hostHeader = req.headers.host || "localhost:18789";
169
+ const gatewayHost = `http://${hostHeader}`;
170
+ // Serve the inject script
171
+ if (pathname === "/better-gateway/inject.js") {
172
+ const script = loadInjectScript();
173
+ const configuredScript = `${generateConfigScript(config)}\n${script}`;
174
+ res.writeHead(200, {
175
+ "Content-Type": "application/javascript",
176
+ "Content-Length": Buffer.byteLength(configuredScript),
177
+ "Cache-Control": "no-cache",
178
+ });
179
+ res.end(configuredScript);
180
+ api.logger.debug("Served inject.js");
181
+ return true;
182
+ }
183
+ // Serve userscript download
184
+ if (pathname === "/better-gateway/userscript.user.js") {
185
+ const userscript = generateUserscript(config, gatewayHost);
186
+ res.writeHead(200, {
187
+ "Content-Type": "application/javascript",
188
+ "Content-Length": Buffer.byteLength(userscript),
189
+ "Content-Disposition": "attachment; filename=better-gateway.user.js",
190
+ });
191
+ res.end(userscript);
192
+ api.logger.debug("Served userscript");
193
+ return true;
194
+ }
195
+ // Serve landing/help page at /better-gateway/help
196
+ if (pathname === "/better-gateway/help") {
197
+ const html = generateLandingPage(config, gatewayHost);
198
+ res.writeHead(200, {
199
+ "Content-Type": "text/html",
200
+ "Content-Length": Buffer.byteLength(html),
201
+ });
202
+ res.end(html);
203
+ api.logger.debug("Served help page");
204
+ return true;
205
+ }
206
+ // Enhanced gateway UI - proxy ALL /better-gateway/* paths to internal gateway
207
+ // Strip /better-gateway prefix and proxy the rest
208
+ const internalPort = 18789;
209
+ let targetPath = pathname.replace(/^\/better-gateway/, "") || "/";
210
+ if (url.search) {
211
+ targetPath += url.search;
212
+ }
213
+ const proxyReq = httpRequest({
214
+ hostname: "127.0.0.1",
215
+ port: internalPort,
216
+ path: targetPath,
217
+ method: req.method || "GET",
218
+ family: 4, // Force IPv4
219
+ headers: {
220
+ ...req.headers,
221
+ "Host": "127.0.0.1:18789",
222
+ },
223
+ }, (proxyRes) => {
224
+ const contentType = proxyRes.headers["content-type"] || "";
225
+ const chunks = [];
226
+ proxyRes.on("data", (chunk) => chunks.push(chunk));
227
+ proxyRes.on("end", () => {
228
+ let body = Buffer.concat(chunks).toString("utf-8");
229
+ // If it's HTML, inject our script and fix relative URLs
230
+ if (contentType.includes("text/html")) {
231
+ const injectTag = `<script>${generateConfigScript(config)}\n${loadInjectScript()}</script>`;
232
+ // Add <base href="/"> to make relative URLs resolve from root
233
+ const baseTag = `<base href="/">`;
234
+ if (body.includes("<head>")) {
235
+ body = body.replace("<head>", `<head>${baseTag}`);
236
+ }
237
+ if (body.includes("</head>")) {
238
+ body = body.replace("</head>", `${injectTag}</head>`);
239
+ }
240
+ else if (body.includes("</body>")) {
241
+ body = body.replace("</body>", `${injectTag}</body>`);
242
+ }
243
+ else {
244
+ body = body + injectTag;
245
+ }
246
+ }
247
+ const headers = {
248
+ "Content-Type": contentType,
249
+ "Content-Length": Buffer.byteLength(body),
250
+ };
251
+ res.writeHead(proxyRes.statusCode || 200, headers);
252
+ res.end(body);
253
+ api.logger.debug("Served enhanced gateway UI");
254
+ });
255
+ });
256
+ proxyReq.on("error", (err) => {
257
+ api.logger.error(`Proxy error: ${err.message}`);
258
+ res.writeHead(502, { "Content-Type": "text/plain" });
259
+ res.end("Failed to fetch gateway UI");
260
+ });
261
+ proxyReq.end();
262
+ return true;
263
+ });
264
+ },
265
+ };
266
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../src/inject.js"],"names":[],"mappings":""}
package/dist/inject.js ADDED
@@ -0,0 +1,181 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ const config = window.__BETTER_GATEWAY_CONFIG__ || {
5
+ reconnectIntervalMs: 3000,
6
+ maxReconnectAttempts: 10,
7
+ };
8
+
9
+ let reconnectAttempts = 0;
10
+ let statusIndicator = null;
11
+ let originalWebSocket = window.WebSocket;
12
+ let activeConnections = new Set();
13
+ let currentState = "connected";
14
+
15
+ function createStatusIndicator() {
16
+ if (statusIndicator) return statusIndicator;
17
+
18
+ statusIndicator = document.createElement("div");
19
+ statusIndicator.id = "better-gateway-status";
20
+ statusIndicator.style.cssText = `
21
+ position: fixed;
22
+ bottom: 12px;
23
+ left: 12px;
24
+ padding: 8px 14px;
25
+ border-radius: 6px;
26
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
27
+ font-size: 13px;
28
+ font-weight: 500;
29
+ z-index: 999999;
30
+ transition: all 0.3s ease;
31
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
32
+ cursor: pointer;
33
+ user-select: none;
34
+ `;
35
+
36
+ // Click handler - refresh on failed/disconnected, or force reconnect
37
+ statusIndicator.addEventListener("click", function () {
38
+ if (currentState === "failed" || currentState === "disconnected") {
39
+ window.location.reload();
40
+ } else if (currentState === "connected") {
41
+ // Show a brief "all good" feedback
42
+ const original = statusIndicator.innerHTML;
43
+ statusIndicator.innerHTML = `<span style="margin-right: 6px;">✓</span>All systems go!`;
44
+ setTimeout(function () {
45
+ statusIndicator.innerHTML = original;
46
+ }, 1500);
47
+ }
48
+ });
49
+
50
+ // Hover effect
51
+ statusIndicator.addEventListener("mouseenter", function () {
52
+ statusIndicator.style.transform = "scale(1.05)";
53
+ statusIndicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.25)";
54
+ });
55
+ statusIndicator.addEventListener("mouseleave", function () {
56
+ statusIndicator.style.transform = "scale(1)";
57
+ statusIndicator.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.15)";
58
+ });
59
+
60
+ document.body.appendChild(statusIndicator);
61
+ return statusIndicator;
62
+ }
63
+
64
+ function updateStatus(state, message) {
65
+ currentState = state;
66
+ const indicator = createStatusIndicator();
67
+
68
+ const styles = {
69
+ connected: {
70
+ background: "#10b981",
71
+ color: "#ffffff",
72
+ icon: "●",
73
+ },
74
+ disconnected: {
75
+ background: "#ef4444",
76
+ color: "#ffffff",
77
+ icon: "●",
78
+ clickHint: " (click to refresh)",
79
+ },
80
+ reconnecting: {
81
+ background: "#f59e0b",
82
+ color: "#ffffff",
83
+ icon: "↻",
84
+ },
85
+ failed: {
86
+ background: "#6b7280",
87
+ color: "#ffffff",
88
+ icon: "↻",
89
+ clickHint: " (click to refresh)",
90
+ },
91
+ };
92
+
93
+ const style = styles[state] || styles.disconnected;
94
+ indicator.style.background = style.background;
95
+ indicator.style.color = style.color;
96
+
97
+ const displayMessage = message + (style.clickHint || "");
98
+ indicator.innerHTML = `<span style="margin-right: 6px;">${style.icon}</span>${displayMessage}`;
99
+ indicator.title = state === "connected" ? "Click for status" : "Click to refresh page";
100
+
101
+ if (state === "connected") {
102
+ setTimeout(function () {
103
+ indicator.style.opacity = "0.7";
104
+ }, 2000);
105
+ } else {
106
+ indicator.style.opacity = "1";
107
+ }
108
+ }
109
+
110
+ function wrapWebSocket(OriginalWebSocket) {
111
+ function BetterWebSocket(url, protocols) {
112
+ const ws = new OriginalWebSocket(url, protocols);
113
+ const wrappedWs = ws;
114
+
115
+ activeConnections.add(wrappedWs);
116
+
117
+ ws.addEventListener("open", function () {
118
+ reconnectAttempts = 0;
119
+ updateStatus("connected", "Connected");
120
+ });
121
+
122
+ ws.addEventListener("close", function (event) {
123
+ activeConnections.delete(wrappedWs);
124
+
125
+ if (!event.wasClean && reconnectAttempts < config.maxReconnectAttempts) {
126
+ reconnectAttempts++;
127
+ updateStatus(
128
+ "reconnecting",
129
+ "Reconnecting (" + reconnectAttempts + "/" + config.maxReconnectAttempts + ")..."
130
+ );
131
+
132
+ setTimeout(function () {
133
+ try {
134
+ new BetterWebSocket(url, protocols);
135
+ } catch (e) {
136
+ console.error("[BetterGateway] Reconnection failed:", e);
137
+ }
138
+ }, config.reconnectIntervalMs);
139
+ } else if (reconnectAttempts >= config.maxReconnectAttempts) {
140
+ updateStatus("failed", "Connection failed");
141
+ } else {
142
+ updateStatus("disconnected", "Disconnected");
143
+ }
144
+ });
145
+
146
+ ws.addEventListener("error", function () {
147
+ updateStatus("disconnected", "Connection error");
148
+ });
149
+
150
+ return ws;
151
+ }
152
+
153
+ BetterWebSocket.prototype = OriginalWebSocket.prototype;
154
+ BetterWebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
155
+ BetterWebSocket.OPEN = OriginalWebSocket.OPEN;
156
+ BetterWebSocket.CLOSING = OriginalWebSocket.CLOSING;
157
+ BetterWebSocket.CLOSED = OriginalWebSocket.CLOSED;
158
+
159
+ return BetterWebSocket;
160
+ }
161
+
162
+ window.WebSocket = wrapWebSocket(originalWebSocket);
163
+
164
+ if (document.readyState === "loading") {
165
+ document.addEventListener("DOMContentLoaded", function () {
166
+ updateStatus("connected", "Ready");
167
+ });
168
+ } else {
169
+ updateStatus("connected", "Ready");
170
+ }
171
+
172
+ window.addEventListener("online", function () {
173
+ updateStatus("connected", "Back online");
174
+ });
175
+
176
+ window.addEventListener("offline", function () {
177
+ updateStatus("disconnected", "Offline");
178
+ });
179
+
180
+ console.log("[BetterGateway] Auto-reconnect enabled", config);
181
+ })();
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.js","sourceRoot":"","sources":["../src/inject.js"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "better-gateway",
3
+ "name": "Better Gateway",
4
+ "version": "1.0.0",
5
+ "description": "Enhanced gateway UI with auto-refresh and WebSocket reconnection",
6
+ "main": "dist/index.js",
7
+ "configSchema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "reconnectIntervalMs": {
11
+ "type": "number",
12
+ "default": 3000,
13
+ "description": "WebSocket reconnection interval in milliseconds"
14
+ },
15
+ "maxReconnectAttempts": {
16
+ "type": "number",
17
+ "default": 10,
18
+ "description": "Maximum number of reconnection attempts before giving up"
19
+ }
20
+ },
21
+ "required": []
22
+ }
23
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@thisisjeron/openclaw-better-gateway",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw plugin that enhances the gateway UI with auto-reconnect and connection monitoring",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc && cp src/inject.js dist/inject.js",
9
+ "dev": "tsc --watch",
10
+ "clean": "rm -rf dist",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "openclaw",
17
+ "openclaw-plugin",
18
+ "gateway",
19
+ "websocket",
20
+ "auto-reconnect"
21
+ ],
22
+ "author": "ThisIsJeron",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/ThisIsJeron/openclaw-better-gateway.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/ThisIsJeron/openclaw-better-gateway/issues"
30
+ },
31
+ "homepage": "https://github.com/ThisIsJeron/openclaw-better-gateway#readme",
32
+ "files": [
33
+ "dist",
34
+ "openclaw.plugin.json",
35
+ "README.md"
36
+ ],
37
+ "dependencies": {},
38
+ "devDependencies": {
39
+ "@types/node": "^20.14.0",
40
+ "jsdom": "^26.0.0",
41
+ "typescript": "^5.5.0",
42
+ "vitest": "^3.0.0"
43
+ },
44
+ "openclaw": {
45
+ "extensions": ["dist/index.js"]
46
+ }
47
+ }