@cloudrise/openclaw-channel-rocketchat 0.1.0 → 0.1.10

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 ADDED
@@ -0,0 +1,4 @@
1
+ # Rocket.Chat test credentials (DO NOT COMMIT REAL VALUES)
2
+ ROCKETCHAT_BASE_URL=http://localhost:3000
3
+ ROCKETCHAT_AUTH_TOKEN=replace_me
4
+ ROCKETCHAT_USER_ID=replace_me
package/README.md CHANGED
@@ -1,20 +1,338 @@
1
- # @cloudrise/openclaw-channel-rocketchat
1
+ # OpenClaw Rocket.Chat Channel Plugin
2
2
 
3
- Rocket.Chat channel plugin for **OpenClaw** (Cloudrise-maintained).
3
+ [![npm](https://img.shields.io/npm/v/@cloudrise/openclaw-channel-rocketchat)](https://www.npmjs.com/package/@cloudrise/openclaw-channel-rocketchat)
4
+ [![license](https://img.shields.io/npm/l/@cloudrise/openclaw-channel-rocketchat)](LICENSE)
5
+
6
+ Neutral, self-host friendly Rocket.Chat channel plugin for **OpenClaw** (Cloudrise-maintained).
4
7
 
5
8
  - **Inbound:** Rocket.Chat Realtime (DDP/WebSocket) subscribe to `stream-room-messages`
6
9
  - **Outbound:** Rocket.Chat REST `chat.postMessage`
7
10
 
11
+ ## Upgrade / rename notice
12
+
13
+ If you were using the old Clawdbot-era package:
14
+
15
+ - Old: `@cloudrise/clawdbot-channel-rocketchat`
16
+ - New: `@cloudrise/openclaw-channel-rocketchat`
17
+
18
+ ## Authors
19
+
20
+ - Chad (AI assistant running in OpenClaw) — primary implementer
21
+ - Marshal Morse — project owner, requirements, infrastructure, and testing
22
+
23
+ ## Quickstart (5–10 minutes)
24
+
25
+ 1) **Create a Rocket.Chat bot user** (or a dedicated user account) and obtain:
26
+ - `userId`
27
+ - `authToken` (treat like a password)
28
+
29
+ 2) **Add the bot user to the rooms** you want it to monitor (channels/private groups). For DMs, ensure users can message the bot.
30
+
31
+ 3) **Install + enable the plugin in OpenClaw**
32
+
33
+ ```yaml
34
+ plugins:
35
+ installs:
36
+ rocketchat:
37
+ source: npm
38
+ spec: "@cloudrise/openclaw-channel-rocketchat"
39
+ entries:
40
+ rocketchat:
41
+ enabled: true
42
+
43
+ channels:
44
+ rocketchat:
45
+ baseUrl: "https://chat.example.com"
46
+ userId: "<ROCKETCHAT_USER_ID>"
47
+ authToken: "<ROCKETCHAT_AUTH_TOKEN>"
48
+
49
+ # Optional: keep noise down
50
+ replyMode: auto
51
+ rooms:
52
+ GENERAL:
53
+ requireMention: true
54
+ ```
55
+
56
+ 4) **Restart the gateway**.
57
+
58
+ 5) **Test** by @mentioning the bot in a room it’s a member of.
59
+
60
+ ### Example chat commands (reply to a room + model switching)
61
+
62
+ In Rocket.Chat you can send a normal message, or you can switch the session’s model first.
63
+
64
+ **Switch model, then ask a question**:
65
+
66
+ Rocket.Chat treats messages starting with `/` as Rocket.Chat slash-commands.
67
+ So for model switching, either:
68
+
69
+ - put the directive *after* an @mention (works on most servers/clients), or
70
+ - use the plugin’s alternate `--model` / `--<alias>` syntax.
71
+
72
+ ```text
73
+ # Option A: use /model after an @mention
74
+ @Chad /model qwen3
75
+ @Chad write a 5-line summary of our incident in plain English
76
+
77
+ # Option B: alternate syntax (avoids Rocket.Chat /commands)
78
+ @Chad --model qwen3
79
+ @Chad write a 5-line summary of our incident in plain English
80
+
81
+ # Option C: shorthand alias form
82
+ @Chad --qwen3
83
+ @Chad write a 5-line summary of our incident in plain English
84
+ ```
85
+
86
+ **Example output** (with `messages.responsePrefix: "({model}) "` enabled):
87
+
88
+ ```text
89
+ (mlx-qwen/mlx-community/Qwen3-14B-4bit) Here’s a 5-line summary...
90
+ ...
91
+ ```
92
+
93
+ **Send a one-off message to a specific Rocket.Chat room** (from the gateway host):
94
+
95
+ ```bash
96
+ openclaw message send --channel rocketchat --to room:GENERAL --message "Hello from OpenClaw"
97
+ ```
98
+
99
+ **Send using a specific model for that one message**:
100
+
101
+ ```bash
102
+ openclaw message send --channel rocketchat --to room:GENERAL --message "/model qwen3 Hello from Qwen3"
103
+ ```
104
+
105
+ ---
106
+
8
107
  ## Install
9
108
 
109
+ ### Install from npm
110
+
10
111
  ```bash
11
- npm i @cloudrise/openclaw-channel-rocketchat
112
+ npm install @cloudrise/openclaw-channel-rocketchat
12
113
  ```
13
114
 
14
- ## Upgrade / rename notice
115
+ ### Configure OpenClaw to load the plugin
15
116
 
16
- If you were using the old Clawdbot-era package:
117
+ You need to tell OpenClaw to load the installed plugin.
17
118
 
18
- - Old: `@cloudrise/clawdbot-channel-rocketchat`
19
- - New: `@cloudrise/openclaw-channel-rocketchat`
119
+ **Option A (recommended): install via `plugins.installs` (npm source)**
120
+
121
+ ```yaml
122
+ plugins:
123
+ installs:
124
+ rocketchat:
125
+ source: npm
126
+ spec: "@cloudrise/openclaw-channel-rocketchat"
127
+ entries:
128
+ rocketchat:
129
+ enabled: true
130
+ ```
131
+
132
+ **Option B: load from a local path**
133
+
134
+ ```yaml
135
+ plugins:
136
+ load:
137
+ paths:
138
+ - /absolute/path/to/node_modules/@cloudrise/openclaw-channel-rocketchat
139
+ entries:
140
+ rocketchat:
141
+ enabled: true
142
+ ```
143
+
144
+ Then restart the gateway.
145
+
146
+ ## Features
147
+
148
+ - **Image attachments**: receives images uploaded to Rocket.Chat and passes them to the vision model.
149
+ - **Model prefix**: honors `messages.responsePrefix` (e.g. `({model}) `) so replies can include the model name.
150
+
151
+ ## Model switching
152
+
153
+ There are two parts:
154
+
155
+ 1) **Switching models in chat** (temporary, per-session) via `/model ...`
156
+ 2) **Defining short aliases** like `qwen3` so you don’t have to type the full `provider/model`
157
+
158
+ ### Switching models in chat (`/model`)
159
+
160
+ In any chat where OpenClaw slash-commands are enabled, you can switch the current session’s model:
161
+
162
+ ```text
163
+ /model
164
+ /model list
165
+ /model status
166
+ /model openai/gpt-5.2
167
+ /model qwen3
168
+ ```
169
+
170
+ Tip: on Rocket.Chat you’ll often be writing something like:
171
+
172
+ ```text
173
+ @Chad /model qwen3
174
+ @Chad what do you think about ...
175
+ ```
176
+
177
+ ### Model aliases (shortcuts like `qwen3`)
178
+
179
+ OpenClaw supports **model aliases** so you can type a short name (like `qwen3`) instead of a full `provider/model` ref.
180
+
181
+ **Option A: define aliases in config**
182
+
183
+ Aliases come from `agents.defaults.models.<modelId>.alias`.
184
+
185
+ ```yaml
186
+ agents:
187
+ defaults:
188
+ models:
189
+ "mlx-qwen/mlx-community/qwen3-14b-4bit":
190
+ alias: qwen3
191
+ ```
192
+
193
+ **Option B: use the CLI**
194
+
195
+ ```bash
196
+ openclaw models aliases add qwen3 mlx-qwen/mlx-community/Qwen3-14B-4bit
197
+ openclaw models aliases list
198
+ ```
199
+
200
+ Notes:
201
+ - Model refs are normalized to lowercase.
202
+ - If you define the same alias in config and via CLI, your config value wins.
203
+
204
+ ## Configuration
205
+
206
+ > Use the room **rid** (e.g. `GENERAL`) for per-room settings.
207
+
208
+ ### Minimal (single account)
209
+
210
+ ```yaml
211
+ channels:
212
+ rocketchat:
213
+ baseUrl: "https://chat.example.com"
214
+ userId: "<ROCKETCHAT_USER_ID>"
215
+ authToken: "<ROCKETCHAT_AUTH_TOKEN>"
216
+ ```
217
+
218
+ ### Multiple accounts / multiple Rocket.Chat servers
219
+
220
+ You can configure multiple Rocket.Chat “accounts” under `channels.rocketchat.accounts` and choose which one to use via `accountId` when sending.
221
+
222
+ ```yaml
223
+ channels:
224
+ rocketchat:
225
+ accounts:
226
+ prod:
227
+ name: "Prod RC"
228
+ baseUrl: "https://chat.example.com"
229
+ userId: "<PROD_USER_ID>"
230
+ authToken: "<PROD_AUTH_TOKEN>"
231
+
232
+ staging:
233
+ name: "Staging RC"
234
+ baseUrl: "https://chat-staging.example.com"
235
+ userId: "<STAGING_USER_ID>"
236
+ authToken: "<STAGING_AUTH_TOKEN>"
237
+ ```
238
+
239
+ Notes:
240
+ - The legacy single-account format (top-level `baseUrl/userId/authToken`) still works and is treated as `accountId: default`.
241
+ - Per-room settings live under each account (e.g. `channels.rocketchat.accounts.prod.rooms`).
242
+
243
+ ### Reply routing (thread vs channel)
244
+
245
+ ```yaml
246
+ channels:
247
+ rocketchat:
248
+ # thread | channel | auto
249
+ replyMode: auto
250
+
251
+ rooms:
252
+ GENERAL:
253
+ requireMention: false
254
+ # Optional per-room override
255
+ # replyMode: channel
256
+ ```
257
+
258
+ **Auto rules** (deterministic):
259
+ - If the inbound message is already in a thread (`tmid` exists) → reply in that thread
260
+ - Else if the inbound message is “long” (≥280 chars or contains a newline) → reply in a thread
261
+ - Else → reply in channel
262
+
263
+ ### Per-message overrides
264
+
265
+ Prefix your message:
266
+ - `!thread ...` → force the reply to be posted as a thread reply
267
+ - `!channel ...` → force the reply to be posted in the channel
268
+
269
+ (The prefix is stripped before the message is sent to the agent.)
270
+
271
+ ### Typing indicator
272
+
273
+ ```yaml
274
+ channels:
275
+ rocketchat:
276
+ # Delay (ms) before emitting typing indicator
277
+ typingDelayMs: 500
278
+ ```
279
+
280
+ (When using multiple accounts, this can also be set per account at `channels.rocketchat.accounts.<accountId>.typingDelayMs`.)
281
+
282
+ Typing indicators are emitted via DDP `stream-notify-room` using `<RID>/user-activity`.
283
+ - Channel replies emit typing without `tmid` → shows under channel composer
284
+ - Thread replies include `{ tmid: ... }` → shows under thread composer
285
+
286
+ ## Development
287
+
288
+ ```bash
289
+ git clone git@github.com:cloudrise-network/openclaw-channel-rocketchat.git
290
+ cd openclaw-channel-rocketchat
291
+ npm install
292
+ ```
293
+
294
+ Local smoke tests (uses env vars; see `.env.example`):
295
+
296
+ ```bash
297
+ # REST send
298
+ node test-chad.mjs
299
+
300
+ # Realtime receive
301
+ node test-realtime.mjs
302
+ ```
303
+
304
+ ## Packaging + publishing (no secrets)
305
+
306
+ Before publishing:
307
+
308
+ 1) Run a quick secret scan (at minimum):
309
+
310
+ ```bash
311
+ grep -RIn --exclude-dir=node_modules --exclude=package-lock.json -E "npm_[A-Za-z0-9]+|ghp_[A-Za-z0-9]+|xox[baprs]-|authToken\s*[:=]\s*\"" .
312
+ ```
313
+
314
+ 2) Bump version in `package.json`.
315
+
316
+ 3) Verify the tarball:
317
+
318
+ ```bash
319
+ npm pack
320
+ ```
321
+
322
+ 4) Publish:
323
+
324
+ ```bash
325
+ npm publish
326
+ ```
327
+
328
+ (There is also a GitHub Actions workflow in `.github/workflows/publish.yml`.)
329
+
330
+ ## Security
331
+
332
+ Treat Rocket.Chat `authToken` like a password.
333
+
334
+ This repository is intended to be publishable (no secrets committed).
335
+
336
+ ## License
20
337
 
338
+ MIT
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@cloudrise/openclaw-channel-rocketchat",
3
- "version": "0.1.0",
3
+ "version": "0.1.10",
4
4
  "description": "Rocket.Chat channel plugin for OpenClaw (Cloudrise)",
5
+ "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "index.ts",
7
8
  "openclaw": {
@@ -8,6 +8,8 @@ import type {
8
8
  RuntimeEnv,
9
9
  } from "openclaw/plugin-sdk";
10
10
 
11
+ import { createReplyPrefixContext } from "openclaw/plugin-sdk";
12
+
11
13
  import { getRocketChatRuntime } from "../runtime.js";
12
14
  import { resolveRocketChatAccount, type ResolvedRocketChatAccount } from "./accounts.js";
13
15
  import {
@@ -20,7 +22,7 @@ import {
20
22
  type RocketChatRoom,
21
23
  type RocketChatClient,
22
24
  } from "./client.js";
23
- import { RocketChatRealtime, type IncomingMessage } from "./realtime.js";
25
+ import { RocketChatRealtime, type IncomingMessage, type RocketChatAttachment, type RocketChatFile } from "./realtime.js";
24
26
  import { sendMessageRocketChat } from "./send.js";
25
27
 
26
28
  export type MonitorRocketChatOpts = {
@@ -68,17 +70,92 @@ function chatType(kind: "dm" | "group" | "channel"): "direct" | "group" | "chann
68
70
  return "channel";
69
71
  }
70
72
 
73
+ /** Image MIME types we can send to vision models */
74
+ const IMAGE_MIME_TYPES = new Set([
75
+ "image/jpeg",
76
+ "image/png",
77
+ "image/gif",
78
+ "image/webp",
79
+ ]);
80
+
81
+ function isImageMime(mime?: string): boolean {
82
+ if (!mime) return false;
83
+ return IMAGE_MIME_TYPES.has(mime.toLowerCase().split(";")[0].trim());
84
+ }
85
+
86
+ /**
87
+ * Extract image URLs from Rocket.Chat message attachments/files.
88
+ * Returns full URLs that can be fetched with auth headers.
89
+ */
90
+ function extractImageUrls(
91
+ msg: IncomingMessage,
92
+ baseUrl: string
93
+ ): Array<{ url: string; mimeType?: string }> {
94
+ const images: Array<{ url: string; mimeType?: string }> = [];
95
+
96
+ // From attachments array (used for image_url references)
97
+ if (msg.attachments?.length) {
98
+ for (const att of msg.attachments) {
99
+ if (att.image_url) {
100
+ const url = att.image_url.startsWith("http")
101
+ ? att.image_url
102
+ : `${baseUrl}${att.image_url.startsWith("/") ? "" : "/"}${att.image_url}`;
103
+ images.push({ url, mimeType: att.image_type });
104
+ }
105
+ }
106
+ }
107
+
108
+ // From file/files (used for direct uploads)
109
+ const files = msg.files ?? (msg.file ? [msg.file] : []);
110
+ for (const f of files) {
111
+ if (f._id && f.name && isImageMime(f.type)) {
112
+ // Rocket.Chat file-upload URL pattern
113
+ const url = `${baseUrl}/file-upload/${f._id}/${encodeURIComponent(f.name)}`;
114
+ images.push({ url, mimeType: f.type });
115
+ }
116
+ }
117
+
118
+ return images;
119
+ }
120
+
121
+ /**
122
+ * Fetch an image from Rocket.Chat and return as base64 data URL.
123
+ */
124
+ async function fetchImageAsDataUrl(
125
+ url: string,
126
+ authToken: string,
127
+ userId: string,
128
+ mimeType?: string
129
+ ): Promise<string | null> {
130
+ try {
131
+ const res = await fetch(url, {
132
+ headers: {
133
+ "X-Auth-Token": authToken,
134
+ "X-User-Id": userId,
135
+ },
136
+ });
137
+ if (!res.ok) return null;
138
+
139
+ const contentType = mimeType ?? res.headers.get("content-type") ?? "image/png";
140
+ if (!isImageMime(contentType)) return null;
141
+
142
+ const buffer = await res.arrayBuffer();
143
+ const base64 = Buffer.from(buffer).toString("base64");
144
+ return `data:${contentType};base64,${base64}`;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
71
150
  export async function monitorRocketChatProvider(
72
151
  opts: MonitorRocketChatOpts
73
152
  ): Promise<() => void> {
74
- console.log("[ROCKETCHAT DEBUG] monitorRocketChatProvider called!");
75
153
  const core = getRocketChatRuntime();
76
154
  const logger = core?.logging?.getChildLogger?.({ module: "rocketchat" }) ?? {
77
155
  info: console.log,
78
156
  debug: console.log,
79
157
  error: console.error,
80
158
  };
81
- console.log("[ROCKETCHAT DEBUG] Got logger, getting config...");
82
159
  const cfg = opts.config ?? core?.config?.loadConfig?.() ?? {};
83
160
 
84
161
  const account = resolveRocketChatAccount({
@@ -229,9 +306,7 @@ export async function monitorRocketChatProvider(
229
306
  });
230
307
 
231
308
  // Connect and subscribe to all rooms
232
- console.log("[ROCKETCHAT DEBUG] About to connect realtime...");
233
309
  await realtime.connect();
234
- console.log("[ROCKETCHAT DEBUG] Realtime connected!");
235
310
 
236
311
  // Subscribe to current rooms
237
312
  await refreshSubscriptions();
@@ -295,8 +370,16 @@ async function handleIncomingMessage(
295
370
  ? msg.ts.$date
296
371
  : Date.parse(String(msg.ts));
297
372
 
373
+ // Extract image attachments (if any)
374
+ const baseUrl = account.baseUrl;
375
+ const authToken = account.authToken;
376
+ const userId = account.userId;
377
+ const imageRefs = extractImageUrls(msg, baseUrl);
378
+
298
379
  let rawBody = msg.msg.trim();
299
- if (!rawBody) return;
380
+
381
+ // Allow messages with only images (no text)
382
+ if (!rawBody && imageRefs.length === 0) return;
300
383
 
301
384
  // Optional per-message overrides
302
385
  // - !thread -> force reply in thread
@@ -309,7 +392,8 @@ async function handleIncomingMessage(
309
392
  forcedReplyMode = "channel";
310
393
  rawBody = rawBody.replace(/^!channel\b\s*/i, "").trim();
311
394
  }
312
- if (!rawBody) return;
395
+ // Skip if no text and no images
396
+ if (!rawBody && imageRefs.length === 0) return;
313
397
 
314
398
  // Determine reply mode
315
399
  const baseReplyMode: "thread" | "channel" | "auto" =
@@ -390,20 +474,45 @@ async function handleIncomingMessage(
390
474
  : undefined;
391
475
 
392
476
  // Format the envelope body
477
+ // For image-only messages, use a placeholder so the agent knows there's content
478
+ const effectiveRawBody = rawBody || (imageRefs.length > 0 ? "[image]" : "");
393
479
  const body = core.channel?.reply?.formatAgentEnvelope?.({
394
480
  channel: "Rocket.Chat",
395
481
  from: fromLabel,
396
482
  timestamp: ts,
397
483
  previousTimestamp,
398
484
  envelope: envelopeOptions,
399
- body: rawBody,
400
- }) ?? rawBody;
485
+ body: effectiveRawBody,
486
+ }) ?? effectiveRawBody;
487
+
488
+ // Rocket.Chat NOTE: Messages starting with "/" are treated as Rocket.Chat slash-commands.
489
+ // To make model switching usable from chat, we support an alternate syntax:
490
+ // --model qwen3
491
+ // --qwen3
492
+ // which we normalize into OpenClaw inline directives.
493
+ const commandBody = rawBody
494
+ .replace(/^\s*--model\b/i, "/model")
495
+ .replace(/^\s*--/, "/");
496
+
497
+ // Fetch images as data URLs (authenticated fetch required for Rocket.Chat uploads)
498
+ let mediaUrls: string[] | undefined;
499
+ if (imageRefs.length > 0) {
500
+ const fetched = await Promise.all(
501
+ imageRefs.map((ref) =>
502
+ fetchImageAsDataUrl(ref.url, authToken, userId, ref.mimeType)
503
+ )
504
+ );
505
+ mediaUrls = fetched.filter((u): u is string => u !== null);
506
+ if (mediaUrls.length > 0) {
507
+ logger.debug?.(`Fetched ${mediaUrls.length} image(s) from Rocket.Chat attachments`);
508
+ }
509
+ }
401
510
 
402
511
  // Finalize inbound context
403
512
  const ctxPayload = core.channel?.reply?.finalizeInboundContext?.({
404
513
  Body: body,
405
514
  RawBody: rawBody,
406
- CommandBody: rawBody,
515
+ CommandBody: commandBody,
407
516
  From: isGroup ? `rocketchat:room:${roomId}` : `rocketchat:${senderId}`,
408
517
  To: `rocketchat:${roomId}`,
409
518
  SessionKey: route.sessionKey,
@@ -419,6 +528,9 @@ async function handleIncomingMessage(
419
528
  Timestamp: ts,
420
529
  OriginatingChannel: "rocketchat",
421
530
  OriginatingTo: `rocketchat:${roomId}`,
531
+
532
+ // Image attachments (fetched as base64 data URLs)
533
+ MediaUrls: mediaUrls?.length ? mediaUrls : undefined,
422
534
  });
423
535
 
424
536
  if (!ctxPayload) {
@@ -484,10 +596,16 @@ async function handleIncomingMessage(
484
596
  startTypingAfterDelay();
485
597
 
486
598
  try {
599
+ // Wire up responsePrefix support (e.g. messages.responsePrefix: "({model}) ")
600
+ // so Rocket.Chat replies can include the selected model name.
601
+ const prefix = createReplyPrefixContext({ cfg, agentId: route.agentId });
602
+
487
603
  await core.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher?.({
488
604
  ctx: ctxPayload,
489
605
  cfg,
490
606
  dispatcherOptions: {
607
+ responsePrefix: prefix.responsePrefix,
608
+ responsePrefixContextProvider: prefix.responsePrefixContextProvider,
491
609
  deliver: async (payload) => {
492
610
  const text = (payload as { text?: string }).text ?? "";
493
611
  if (!text.trim()) return;
@@ -504,6 +622,9 @@ async function handleIncomingMessage(
504
622
  logger.error?.(`Rocket.Chat ${info.kind} reply failed: ${String(err)}`);
505
623
  },
506
624
  },
625
+ replyOptions: {
626
+ onModelSelected: prefix.onModelSelected,
627
+ },
507
628
  });
508
629
  } finally {
509
630
  await stopTyping();
@@ -33,6 +33,24 @@ export type RealtimeOpts = {
33
33
  logger?: { debug?: (msg: string) => void; info?: (msg: string) => void };
34
34
  };
35
35
 
36
+ export type RocketChatAttachment = {
37
+ title?: string;
38
+ title_link?: string;
39
+ image_url?: string;
40
+ audio_url?: string;
41
+ video_url?: string;
42
+ type?: string;
43
+ image_type?: string;
44
+ image_size?: number;
45
+ };
46
+
47
+ export type RocketChatFile = {
48
+ _id: string;
49
+ name: string;
50
+ type?: string;
51
+ size?: number;
52
+ };
53
+
36
54
  export type IncomingMessage = {
37
55
  _id: string;
38
56
  rid: string;
@@ -41,12 +59,9 @@ export type IncomingMessage = {
41
59
  u: { _id: string; username: string; name?: string };
42
60
  tmid?: string;
43
61
  t?: string;
44
- attachments?: Array<{
45
- title?: string;
46
- image_url?: string;
47
- audio_url?: string;
48
- video_url?: string;
49
- }>;
62
+ attachments?: RocketChatAttachment[];
63
+ file?: RocketChatFile;
64
+ files?: RocketChatFile[];
50
65
  };
51
66
 
52
67
  export class RocketChatRealtime {
package/test-chad.mjs CHANGED
@@ -1,79 +1,23 @@
1
- import WebSocket from "ws";
2
-
3
- const baseUrl = "http://10.99.0.4:3000";
4
- const authToken = "m2E8phh3dmtOag3UTh84d60YZOQVfa6wjhIWBV01jRr";
5
- const userId = "M3NTzRzjwZfE9FRxi";
6
-
7
- const wsUrl = baseUrl.replace(/^http/, "ws") + "/websocket";
8
- console.log("Connecting as Chad to:", wsUrl);
9
-
10
- const ws = new WebSocket(wsUrl);
11
- let msgId = 0;
12
-
13
- function send(msg) {
14
- console.log(">>> SEND:", JSON.stringify(msg));
15
- ws.send(JSON.stringify(msg));
1
+ // Local manual test helper.
2
+ // DO NOT hardcode credentials in this repo.
3
+ //
4
+ // Usage:
5
+ // export ROCKETCHAT_BASE_URL='https://your-rocketchat'
6
+ // export ROCKETCHAT_AUTH_TOKEN='...'
7
+ // export ROCKETCHAT_USER_ID='...'
8
+ // node ./test-realtime.mjs
9
+
10
+ const baseUrl = process.env.ROCKETCHAT_BASE_URL;
11
+ const authToken = process.env.ROCKETCHAT_AUTH_TOKEN;
12
+ const userId = process.env.ROCKETCHAT_USER_ID;
13
+
14
+ if (!baseUrl || !authToken || !userId) {
15
+ throw new Error('Missing env: ROCKETCHAT_BASE_URL, ROCKETCHAT_AUTH_TOKEN, ROCKETCHAT_USER_ID');
16
16
  }
17
17
 
18
- ws.on("open", () => {
19
- console.log("Connected, sending DDP connect...");
20
- send({ msg: "connect", version: "1", support: ["1"] });
21
- });
22
-
23
- ws.on("message", (data) => {
24
- const msg = JSON.parse(data.toString());
25
- console.log("<<< RECV:", JSON.stringify(msg).slice(0, 200));
26
-
27
- if (msg.msg === "connected") {
28
- console.log("DDP connected, logging in as Chad...");
29
- send({
30
- msg: "method",
31
- method: "login",
32
- id: String(++msgId),
33
- params: [{ resume: authToken }]
34
- });
35
- }
36
-
37
- if (msg.msg === "result" && msg.id === "1") {
38
- if (msg.error) {
39
- console.error("LOGIN FAILED:", msg.error);
40
- ws.close();
41
- return;
42
- }
43
- console.log("Logged in as Chad! Subscribing to GENERAL...");
44
- send({
45
- msg: "sub",
46
- id: String(++msgId),
47
- name: "stream-room-messages",
48
- params: ["GENERAL", false]
49
- });
50
- }
51
-
52
- if (msg.msg === "ping") {
53
- send({ msg: "pong" });
54
- }
55
-
56
- if (msg.msg === "changed") {
57
- console.log("\n*** MESSAGE RECEIVED ***");
58
- console.log(JSON.stringify(msg, null, 2));
59
- console.log("************************\n");
60
- }
61
-
62
- if (msg.msg === "nosub") {
63
- console.error("SUBSCRIPTION FAILED:", msg);
64
- }
65
- });
66
-
67
- ws.on("error", (err) => {
68
- console.error("WebSocket Error:", err.message);
69
- });
70
-
71
- ws.on("close", (code, reason) => {
72
- console.log("Connection closed:", code, reason?.toString());
73
- });
18
+ console.log('Base URL:', baseUrl);
19
+ console.log('User ID:', userId);
20
+ console.log('Auth token set:', authToken ? 'yes' : 'no');
74
21
 
75
- console.log("Listening for 60 seconds... Send a message in #general!");
76
- setTimeout(() => {
77
- console.log("Test complete, closing...");
78
- ws.close();
79
- }, 60000);
22
+ // TODO: call into the library / run your test logic here.
23
+ console.log('TODO: implement test logic');
package/test-chad2.mjs CHANGED
@@ -1,111 +1,23 @@
1
- import WebSocket from "ws";
2
-
3
- const baseUrl = "http://10.99.0.4:3000";
4
- const authToken = "m2E8phh3dmtOag3UTh84d60YZOQVfa6wjhIWBV01jRr";
5
- const userId = "M3NTzRzjwZfE9FRxi";
6
-
7
- const wsUrl = baseUrl.replace(/^http/, "ws") + "/websocket";
8
- console.log("Connecting as Chad to:", wsUrl);
9
-
10
- const ws = new WebSocket(wsUrl);
11
- let msgId = 0;
12
-
13
- function send(msg) {
14
- console.log(">>> SEND:", JSON.stringify(msg));
15
- ws.send(JSON.stringify(msg));
1
+ // Local manual test helper.
2
+ // DO NOT hardcode credentials in this repo.
3
+ //
4
+ // Usage:
5
+ // export ROCKETCHAT_BASE_URL='https://your-rocketchat'
6
+ // export ROCKETCHAT_AUTH_TOKEN='...'
7
+ // export ROCKETCHAT_USER_ID='...'
8
+ // node ./test-realtime.mjs
9
+
10
+ const baseUrl = process.env.ROCKETCHAT_BASE_URL;
11
+ const authToken = process.env.ROCKETCHAT_AUTH_TOKEN;
12
+ const userId = process.env.ROCKETCHAT_USER_ID;
13
+
14
+ if (!baseUrl || !authToken || !userId) {
15
+ throw new Error('Missing env: ROCKETCHAT_BASE_URL, ROCKETCHAT_AUTH_TOKEN, ROCKETCHAT_USER_ID');
16
16
  }
17
17
 
18
- ws.on("open", () => {
19
- console.log("Connected, sending DDP connect...");
20
- send({ msg: "connect", version: "1", support: ["1"] });
21
- });
22
-
23
- ws.on("message", (data) => {
24
- const msg = JSON.parse(data.toString());
25
- const preview = JSON.stringify(msg).slice(0, 300);
26
- console.log("<<< RECV:", preview);
27
-
28
- if (msg.msg === "connected") {
29
- console.log("\n=== DDP connected, logging in as Chad... ===\n");
30
- send({
31
- msg: "method",
32
- method: "login",
33
- id: String(++msgId),
34
- params: [{ resume: authToken }]
35
- });
36
- }
37
-
38
- if (msg.msg === "result" && msg.id === "1") {
39
- if (msg.error) {
40
- console.error("LOGIN FAILED:", msg.error);
41
- ws.close();
42
- return;
43
- }
44
- console.log("\n=== Logged in! Trying multiple subscription formats... ===\n");
45
-
46
- // Try format 1: just roomId and boolean
47
- send({
48
- msg: "sub",
49
- id: String(++msgId),
50
- name: "stream-room-messages",
51
- params: ["GENERAL", false]
52
- });
53
-
54
- // Try format 2: with event name
55
- send({
56
- msg: "sub",
57
- id: String(++msgId),
58
- name: "stream-room-messages",
59
- params: ["GENERAL", { useCollection: false, args: [{ visitorToken: null }] }]
60
- });
61
-
62
- // Try format 3: stream-notify-user for notifications
63
- send({
64
- msg: "sub",
65
- id: String(++msgId),
66
- name: "stream-notify-user",
67
- params: [`${userId}/notification`, false]
68
- });
69
-
70
- // Try format 4: stream-notify-room
71
- send({
72
- msg: "sub",
73
- id: String(++msgId),
74
- name: "stream-notify-room",
75
- params: ["GENERAL/deleteMessage", false]
76
- });
77
- }
78
-
79
- if (msg.msg === "ping") {
80
- send({ msg: "pong" });
81
- }
82
-
83
- if (msg.msg === "changed") {
84
- console.log("\n**************************************************");
85
- console.log("*** MESSAGE/EVENT RECEIVED ***");
86
- console.log(JSON.stringify(msg, null, 2));
87
- console.log("**************************************************\n");
88
- }
89
-
90
- if (msg.msg === "nosub") {
91
- console.error("SUBSCRIPTION FAILED:", JSON.stringify(msg));
92
- }
93
-
94
- if (msg.msg === "ready") {
95
- console.log("=== Subscription ready:", msg.subs, "===");
96
- }
97
- });
98
-
99
- ws.on("error", (err) => {
100
- console.error("WebSocket Error:", err.message);
101
- });
102
-
103
- ws.on("close", (code, reason) => {
104
- console.log("Connection closed:", code, reason?.toString());
105
- });
18
+ console.log('Base URL:', baseUrl);
19
+ console.log('User ID:', userId);
20
+ console.log('Auth token set:', authToken ? 'yes' : 'no');
106
21
 
107
- console.log("Listening for 45 seconds... Send a message in #general!");
108
- setTimeout(() => {
109
- console.log("Test complete, closing...");
110
- ws.close();
111
- }, 45000);
22
+ // TODO: call into the library / run your test logic here.
23
+ console.log('TODO: implement test logic');
package/test-realtime.mjs CHANGED
@@ -1,68 +1,23 @@
1
- import WebSocket from "ws";
2
-
3
- const baseUrl = "http://10.99.0.4:3000";
4
- const authToken = "tZATKtqCY9b4q7X9RbY0_d6v5uZg7rDTvW7mqDKPHjF";
5
- const userId = "j4CZSpAFxRoSk6ieH";
6
-
7
- const wsUrl = baseUrl.replace(/^http/, "ws") + "/websocket";
8
- console.log("Connecting to:", wsUrl);
9
-
10
- const ws = new WebSocket(wsUrl);
11
- let msgId = 0;
12
-
13
- function send(msg) {
14
- console.log(">>> SEND:", JSON.stringify(msg));
15
- ws.send(JSON.stringify(msg));
1
+ // Local manual test helper.
2
+ // DO NOT hardcode credentials in this repo.
3
+ //
4
+ // Usage:
5
+ // export ROCKETCHAT_BASE_URL='https://your-rocketchat'
6
+ // export ROCKETCHAT_AUTH_TOKEN='...'
7
+ // export ROCKETCHAT_USER_ID='...'
8
+ // node ./test-realtime.mjs
9
+
10
+ const baseUrl = process.env.ROCKETCHAT_BASE_URL;
11
+ const authToken = process.env.ROCKETCHAT_AUTH_TOKEN;
12
+ const userId = process.env.ROCKETCHAT_USER_ID;
13
+
14
+ if (!baseUrl || !authToken || !userId) {
15
+ throw new Error('Missing env: ROCKETCHAT_BASE_URL, ROCKETCHAT_AUTH_TOKEN, ROCKETCHAT_USER_ID');
16
16
  }
17
17
 
18
- ws.on("open", () => {
19
- console.log("Connected, sending DDP connect...");
20
- send({ msg: "connect", version: "1", support: ["1"] });
21
- });
22
-
23
- ws.on("message", (data) => {
24
- const msg = JSON.parse(data.toString());
25
- console.log("<<< RECV:", JSON.stringify(msg));
26
-
27
- if (msg.msg === "connected") {
28
- console.log("DDP connected, logging in...");
29
- send({
30
- msg: "method",
31
- method: "login",
32
- id: String(++msgId),
33
- params: [{ resume: authToken }]
34
- });
35
- }
36
-
37
- if (msg.msg === "result" && msg.id === "1") {
38
- console.log("Logged in! Subscribing to GENERAL...");
39
- send({
40
- msg: "sub",
41
- id: String(++msgId),
42
- name: "stream-room-messages",
43
- params: ["GENERAL", false]
44
- });
45
- }
46
-
47
- if (msg.msg === "ping") {
48
- send({ msg: "pong" });
49
- }
50
-
51
- if (msg.msg === "changed") {
52
- console.log("*** MESSAGE RECEIVED ***", msg);
53
- }
54
- });
55
-
56
- ws.on("error", (err) => {
57
- console.error("Error:", err);
58
- });
59
-
60
- ws.on("close", (code, reason) => {
61
- console.log("Closed:", code, reason?.toString());
62
- });
18
+ console.log('Base URL:', baseUrl);
19
+ console.log('User ID:', userId);
20
+ console.log('Auth token set:', authToken ? 'yes' : 'no');
63
21
 
64
- // Keep alive
65
- setTimeout(() => {
66
- console.log("Test complete, closing...");
67
- ws.close();
68
- }, 60000);
22
+ // TODO: call into the library / run your test logic here.
23
+ console.log('TODO: implement test logic');