@emotion-machine/claw-messenger 0.1.3 → 0.1.6
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/dist/channel.js +1 -0
- package/dist/ws/client.d.ts +4 -0
- package/dist/ws/client.js +46 -3
- package/package.json +3 -1
package/dist/channel.js
CHANGED
|
@@ -156,6 +156,7 @@ export const clawMessengerPlugin = {
|
|
|
156
156
|
messageToolHints: () => [
|
|
157
157
|
"You can react to messages using iMessage tapbacks via the react action. Available: love (❤️), like (👍), dislike (👎), laugh (😂), emphasize (‼️), question (❓).",
|
|
158
158
|
"Use reactions naturally — the way a real person would in iMessage.",
|
|
159
|
+
"If you encounter connection issues, the server is WebSocket-only — do NOT test with HTTP. Full API docs and troubleshooting: https://clawmessenger.com/docs",
|
|
159
160
|
],
|
|
160
161
|
},
|
|
161
162
|
// Legacy API (OpenClaw <2026.3.22): kept for backward compatibility
|
package/dist/ws/client.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket client with reconnection and request/response correlation.
|
|
3
3
|
* Connects to claw-messenger server with API key auth.
|
|
4
|
+
*
|
|
5
|
+
* Uses the `ws` npm package instead of the native WebSocket to avoid
|
|
6
|
+
* Node 22+ HTTP/2 compatibility issues (native WebSocket silently fails
|
|
7
|
+
* against HTTP/2 servers like Render.com).
|
|
4
8
|
*/
|
|
5
9
|
type MessageHandler = (message: Record<string, unknown>) => void;
|
|
6
10
|
export interface WsClientOptions {
|
package/dist/ws/client.js
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket client with reconnection and request/response correlation.
|
|
3
3
|
* Connects to claw-messenger server with API key auth.
|
|
4
|
+
*
|
|
5
|
+
* Uses the `ws` npm package instead of the native WebSocket to avoid
|
|
6
|
+
* Node 22+ HTTP/2 compatibility issues (native WebSocket silently fails
|
|
7
|
+
* against HTTP/2 servers like Render.com).
|
|
4
8
|
*/
|
|
9
|
+
import WebSocket from "ws";
|
|
5
10
|
const MAX_RECONNECT_DELAY_MS = 30_000;
|
|
11
|
+
const MAX_RECONNECT_ATTEMPTS = 50;
|
|
6
12
|
const PING_INTERVAL_MS = 30_000;
|
|
7
13
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
8
14
|
const PONG_TIMEOUT_MS = 10_000;
|
|
15
|
+
/** Close codes that indicate the client should NOT reconnect. */
|
|
16
|
+
const NO_RECONNECT_CODES = new Set([
|
|
17
|
+
1008, // Policy violation — connection limit reached (evicted)
|
|
18
|
+
4003, // Subscription inactive (cancelled/expired)
|
|
19
|
+
]);
|
|
9
20
|
export class WsClient {
|
|
10
21
|
ws = null;
|
|
11
22
|
opts;
|
|
@@ -84,10 +95,23 @@ export class WsClient {
|
|
|
84
95
|
}
|
|
85
96
|
url.searchParams.set("key", this.opts.apiKey);
|
|
86
97
|
this.opts.log?.(`Connecting to ${url.origin}${url.pathname}...`);
|
|
98
|
+
// Close the previous socket if it's still open, so we don't leak
|
|
99
|
+
// connections on the server. Set this.ws to null first so the old
|
|
100
|
+
// socket's onclose handler sees it's stale and skips reconnect.
|
|
101
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
102
|
+
const oldWs = this.ws;
|
|
103
|
+
this.ws = null;
|
|
104
|
+
try {
|
|
105
|
+
oldWs.close(1000, "Replaced by new connection");
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
87
109
|
const ws = new WebSocket(url.toString());
|
|
88
110
|
this.ws = ws;
|
|
89
111
|
ws.onopen = () => {
|
|
90
|
-
|
|
112
|
+
// Don't reset reconnectAttempt here — reset on first successful
|
|
113
|
+
// message exchange instead, so eviction loops can't keep the
|
|
114
|
+
// counter at 0 and bypass backoff.
|
|
91
115
|
this.lastPongAt = Date.now();
|
|
92
116
|
this.opts.log?.("Connected");
|
|
93
117
|
// Reject stale pending requests from previous connection
|
|
@@ -106,17 +130,36 @@ export class WsClient {
|
|
|
106
130
|
};
|
|
107
131
|
ws.onclose = (event) => {
|
|
108
132
|
this.opts.log?.(`Disconnected (code=${event.code})`);
|
|
133
|
+
// Only handle close for the CURRENT socket. If this.ws has been
|
|
134
|
+
// replaced by a newer connection (or set to null during replacement),
|
|
135
|
+
// this is a stale socket and we must NOT reconnect.
|
|
136
|
+
if (ws !== this.ws) {
|
|
137
|
+
this.opts.log?.("Stale socket closed — ignoring (newer connection exists)");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
109
140
|
this._clearTimers();
|
|
110
141
|
this.opts.onDisconnect?.();
|
|
111
|
-
if (
|
|
112
|
-
|
|
142
|
+
if (this.stopped)
|
|
143
|
+
return;
|
|
144
|
+
if (NO_RECONNECT_CODES.has(event.code)) {
|
|
145
|
+
this.opts.log?.(`Not reconnecting (code=${event.code}: ${event.reason})`);
|
|
146
|
+
this.stopped = true;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
|
150
|
+
this.opts.log?.(`Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached — giving up`);
|
|
151
|
+
this.stopped = true;
|
|
152
|
+
return;
|
|
113
153
|
}
|
|
154
|
+
this._scheduleReconnect();
|
|
114
155
|
};
|
|
115
156
|
ws.onerror = (event) => {
|
|
116
157
|
this.opts.log?.(`WebSocket error`);
|
|
117
158
|
};
|
|
118
159
|
}
|
|
119
160
|
_handleMessage(data) {
|
|
161
|
+
// Connection is stable — reset backoff counter
|
|
162
|
+
this.reconnectAttempt = 0;
|
|
120
163
|
// Check if this is a response to a pending request
|
|
121
164
|
const id = data.id;
|
|
122
165
|
if (id && this.pendingRequests.has(id)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emotion-machine/claw-messenger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "iMessage, RCS & SMS channel plugin for OpenClaw — no phone or Mac Mini required",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,10 +50,12 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@sinclair/typebox": "^0.34.48",
|
|
53
|
+
"ws": "^8.18.0",
|
|
53
54
|
"zod": "^4.3.6"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@types/node": "^20.14.0",
|
|
58
|
+
"@types/ws": "^8.5.0",
|
|
57
59
|
"typescript": "^5.5.0"
|
|
58
60
|
},
|
|
59
61
|
"engines": {
|