@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 +122 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +266 -0
- package/dist/index.js.map +1 -0
- package/dist/inject.d.ts +2 -0
- package/dist/inject.d.ts.map +1 -0
- package/dist/inject.js +181 -0
- package/dist/inject.js.map +1 -0
- package/openclaw.plugin.json +23 -0
- package/package.json +47 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|
package/dist/inject.d.ts
ADDED
|
@@ -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
|
+
}
|