@omnixal/openclaw-nats-plugin 0.1.14 → 0.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnixal/openclaw-nats-plugin",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "NATS JetStream event-driven plugin for OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,6 +15,7 @@ export interface GatewayInjectPayload {
15
15
  export class GatewayClientService extends BaseService implements OnModuleInit, OnModuleDestroy {
16
16
  private ws: WebSocket | null = null;
17
17
  private connected = false;
18
+ private connectSent = false;
18
19
  private reconnectAttempt = 0;
19
20
  private reconnectTimer: Timer | null = null;
20
21
  private requestId = 0;
@@ -31,21 +32,21 @@ export class GatewayClientService extends BaseService implements OnModuleInit, O
31
32
 
32
33
  private connect(): void {
33
34
  try {
34
- const url = this.token ? `${this.wsUrl}?token=${this.token}` : this.wsUrl;
35
- this.ws = new WebSocket(url);
35
+ this.connectSent = false;
36
+ this.ws = new WebSocket(this.wsUrl);
36
37
 
37
38
  this.ws.onopen = () => {
38
- this.logger.info('Gateway WebSocket connected');
39
39
  this.reconnectAttempt = 0;
40
- this.sendConnect();
40
+ this.logger.info('Gateway WebSocket opened, waiting for challenge');
41
41
  };
42
42
 
43
43
  this.ws.onmessage = (event) => {
44
- this.handleMessage(event);
44
+ this.handleMessage(String(event.data));
45
45
  };
46
46
 
47
47
  this.ws.onclose = () => {
48
48
  this.connected = false;
49
+ this.connectSent = false;
49
50
  this.scheduleReconnect();
50
51
  };
51
52
 
@@ -53,29 +54,79 @@ export class GatewayClientService extends BaseService implements OnModuleInit, O
53
54
  this.logger.warn('Gateway WebSocket error');
54
55
  this.connected = false;
55
56
  };
56
- } catch {
57
- this.logger.warn('Failed to connect to Gateway WebSocket');
57
+ } catch (err) {
58
+ this.logger.warn('Failed to connect to Gateway WebSocket', err);
58
59
  this.scheduleReconnect();
59
60
  }
60
61
  }
61
62
 
62
- private handleMessage(event: { data: unknown }): void {
63
+ private handleMessage(data: string): void {
64
+ let frame: any;
63
65
  try {
64
- const frame = JSON.parse(String(event.data));
65
- if (frame.type === 'res' && frame.ok) {
66
+ frame = JSON.parse(data);
67
+ } catch {
68
+ this.logger.warn(`Failed to parse WebSocket message: ${data.slice(0, 200)}`);
69
+ return;
70
+ }
71
+
72
+ // Server challenge — respond with connect frame
73
+ if (frame.type === 'event' && frame.event === 'connect.challenge') {
74
+ this.logger.debug('Received connect.challenge from server');
75
+ this.sendConnectFrame();
76
+ return;
77
+ }
78
+
79
+ // Some gateway versions send an event before challenge; treat any pre-connect event as trigger
80
+ if (!this.connectSent && frame.type === 'event') {
81
+ this.logger.debug('Received event before connect sent, sending connect frame');
82
+ this.sendConnectFrame();
83
+ return;
84
+ }
85
+
86
+ // Successful connect response (hello-ok)
87
+ if (frame.type === 'res' && frame.ok === true) {
88
+ if (frame.payload?.type === 'hello-ok') {
66
89
  this.connected = true;
90
+ this.logger.info('OpenClaw handshake complete — connected');
91
+ return;
67
92
  }
68
- } catch {
69
- // ignore parse errors
93
+ // Regular RPC response — ignore for now
94
+ return;
95
+ }
96
+
97
+ // Error response
98
+ if (frame.type === 'res' && frame.ok === false) {
99
+ this.logger.warn('Gateway RPC error', { id: frame.id, error: frame.error });
70
100
  }
71
101
  }
72
102
 
73
- private sendConnect(): void {
103
+ private sendConnectFrame(): void {
104
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || this.connectSent) return;
105
+ this.connectSent = true;
106
+ this.logger.info('Sending connect frame');
107
+
74
108
  this.send({
75
109
  type: 'req',
76
- id: ++this.requestId,
110
+ id: `connect-${++this.requestId}`,
77
111
  method: 'connect',
78
- params: {},
112
+ params: {
113
+ minProtocol: 3,
114
+ maxProtocol: 3,
115
+ client: {
116
+ id: 'nats-sidecar',
117
+ version: '1.0.0',
118
+ platform: 'linux',
119
+ mode: 'backend',
120
+ },
121
+ role: 'operator',
122
+ scopes: ['operator.read'],
123
+ caps: [],
124
+ commands: [],
125
+ permissions: {},
126
+ auth: { token: this.token },
127
+ locale: 'en-US',
128
+ userAgent: 'nats-sidecar/1.0.0',
129
+ },
79
130
  });
80
131
  }
81
132
 
@@ -87,7 +138,7 @@ export class GatewayClientService extends BaseService implements OnModuleInit, O
87
138
 
88
139
  private scheduleReconnect(): void {
89
140
  if (this.reconnectTimer) return;
90
- const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), 30000);
141
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), 30_000);
91
142
  this.reconnectAttempt++;
92
143
  this.logger.debug(`Reconnecting to Gateway in ${delay}ms (attempt ${this.reconnectAttempt})`);
93
144
  this.reconnectTimer = setTimeout(() => {
@@ -102,7 +153,7 @@ export class GatewayClientService extends BaseService implements OnModuleInit, O
102
153
  }
103
154
  this.send({
104
155
  type: 'req',
105
- id: ++this.requestId,
156
+ id: `rpc-${++this.requestId}`,
106
157
  method: 'send',
107
158
  params: {
108
159
  target: payload.target,
@@ -127,5 +178,6 @@ export class GatewayClientService extends BaseService implements OnModuleInit, O
127
178
  this.ws = null;
128
179
  }
129
180
  this.connected = false;
181
+ this.connectSent = false;
130
182
  }
131
183
  }