@ethora/sdk-backend 26.1.1 → 26.2.2

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 CHANGED
@@ -79,7 +79,7 @@ npm install dotenv
79
79
  In your main application file (e.g., `app.js`, `server.js`, or `index.ts`):
80
80
 
81
81
  ```typescript
82
- import dotenv from "dotenv";
82
+ import dotenv from 'dotenv';
83
83
 
84
84
  // Load environment variables
85
85
  dotenv.config();
@@ -90,7 +90,7 @@ dotenv.config();
90
90
  ### Step 1: Import the SDK
91
91
 
92
92
  ```typescript
93
- import { getEthoraSDKService } from "@ethora/sdk-backend";
93
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
94
94
  ```
95
95
 
96
96
  ### Step 2: Initialize the Service
@@ -101,7 +101,7 @@ You can initialize the service in several ways:
101
101
 
102
102
  ```typescript
103
103
  // services/chatService.ts
104
- import { getEthoraSDKService } from "@ethora/sdk-backend";
104
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
105
105
 
106
106
  // Get the singleton instance
107
107
  const chatService = getEthoraSDKService();
@@ -113,7 +113,7 @@ export default chatService;
113
113
 
114
114
  ```typescript
115
115
  // In your route handler or service
116
- import { getEthoraSDKService } from "@ethora/sdk-backend";
116
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
117
117
 
118
118
  const chatService = getEthoraSDKService();
119
119
  ```
@@ -122,8 +122,8 @@ const chatService = getEthoraSDKService();
122
122
 
123
123
  ```typescript
124
124
  // chat.service.ts
125
- import { Injectable } from "@nestjs/common";
126
- import { getEthoraSDKService } from "@ethora/sdk-backend";
125
+ import { Injectable } from '@nestjs/common';
126
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
127
127
 
128
128
  @Injectable()
129
129
  export class ChatService {
@@ -139,16 +139,16 @@ export class ChatService {
139
139
 
140
140
  ```typescript
141
141
  // routes/chat.ts
142
- import express, { Request, Response } from "express";
143
- import { getEthoraSDKService } from "@ethora/sdk-backend";
144
- import axios from "axios";
142
+ import express, { Request, Response } from 'express';
143
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
144
+ import axios from 'axios';
145
145
 
146
146
  const router = express.Router();
147
147
  const chatService = getEthoraSDKService();
148
148
 
149
149
  // Create a chat room for a workspace
150
150
  router.post(
151
- "/workspaces/:workspaceId/chat",
151
+ '/workspaces/:workspaceId/chat',
152
152
  async (req: Request, res: Response) => {
153
153
  try {
154
154
  const { workspaceId } = req.params;
@@ -157,7 +157,7 @@ router.post(
157
157
  const response = await chatService.createChatRoom(workspaceId, {
158
158
  title: roomData.title || `Chat Room ${workspaceId}`,
159
159
  uuid: workspaceId,
160
- type: roomData.type || "group",
160
+ type: roomData.type || 'group',
161
161
  ...roomData,
162
162
  });
163
163
 
@@ -165,18 +165,18 @@ router.post(
165
165
  } catch (error) {
166
166
  if (axios.isAxiosError(error)) {
167
167
  res.status(error.response?.status || 500).json({
168
- error: "Failed to create chat room",
168
+ error: 'Failed to create chat room',
169
169
  details: error.response?.data,
170
170
  });
171
171
  } else {
172
- res.status(500).json({ error: "Internal server error" });
172
+ res.status(500).json({ error: 'Internal server error' });
173
173
  }
174
174
  }
175
- }
175
+ },
176
176
  );
177
177
 
178
178
  // Create a user
179
- router.post("/users/:userId", async (req: Request, res: Response) => {
179
+ router.post('/users/:userId', async (req: Request, res: Response) => {
180
180
  try {
181
181
  const { userId } = req.params;
182
182
  const userData = req.body;
@@ -186,50 +186,72 @@ router.post("/users/:userId", async (req: Request, res: Response) => {
186
186
  } catch (error) {
187
187
  if (axios.isAxiosError(error)) {
188
188
  res.status(error.response?.status || 500).json({
189
- error: "Failed to create user",
189
+ error: 'Failed to create user',
190
190
  details: error.response?.data,
191
191
  });
192
192
  } else {
193
- res.status(500).json({ error: "Internal server error" });
193
+ res.status(500).json({ error: 'Internal server error' });
194
194
  }
195
195
  }
196
196
  });
197
197
 
198
198
  // Grant user access to chat room
199
199
  router.post(
200
- "/workspaces/:workspaceId/chat/users/:userId",
200
+ '/workspaces/:workspaceId/chat/users/:userId',
201
201
  async (req: Request, res: Response) => {
202
202
  try {
203
203
  const { workspaceId, userId } = req.params;
204
204
 
205
205
  await chatService.grantUserAccessToChatRoom(workspaceId, userId);
206
- res.json({ success: true, message: "Access granted" });
206
+ res.json({ success: true, message: 'Access granted' });
207
207
  } catch (error) {
208
208
  if (axios.isAxiosError(error)) {
209
209
  res.status(error.response?.status || 500).json({
210
- error: "Failed to grant access",
210
+ error: 'Failed to grant access',
211
211
  details: error.response?.data,
212
212
  });
213
213
  } else {
214
- res.status(500).json({ error: "Internal server error" });
214
+ res.status(500).json({ error: 'Internal server error' });
215
215
  }
216
216
  }
217
- }
217
+ },
218
+ );
219
+
220
+ // Remove user access from chat room
221
+ router.delete(
222
+ '/workspaces/:workspaceId/chat/users/:userId',
223
+ async (req: Request, res: Response) => {
224
+ try {
225
+ const { workspaceId, userId } = req.params;
226
+
227
+ await chatService.removeUserAccessFromChatRoom(workspaceId, userId);
228
+ res.json({ success: true, message: 'Access removed' });
229
+ } catch (error) {
230
+ if (axios.isAxiosError(error)) {
231
+ res.status(error.response?.status || 500).json({
232
+ error: 'Failed to remove access',
233
+ details: error.response?.data,
234
+ });
235
+ } else {
236
+ res.status(500).json({ error: 'Internal server error' });
237
+ }
238
+ }
239
+ },
218
240
  );
219
241
 
220
242
  // Generate client JWT token
221
- router.get("/users/:userId/chat-token", (req: Request, res: Response) => {
243
+ router.get('/users/:userId/chat-token', (req: Request, res: Response) => {
222
244
  try {
223
245
  const { userId } = req.params;
224
246
  const token = chatService.createChatUserJwtToken(userId);
225
247
  res.json({ token });
226
248
  } catch (error) {
227
- res.status(500).json({ error: "Failed to generate token" });
249
+ res.status(500).json({ error: 'Failed to generate token' });
228
250
  }
229
251
  });
230
252
 
231
253
  // Get users
232
- router.get("/users", async (req: Request, res: Response) => {
254
+ router.get('/users', async (req: Request, res: Response) => {
233
255
  try {
234
256
  const { chatName, xmppUsername } = req.query;
235
257
  const params: any = {};
@@ -237,32 +259,32 @@ router.get("/users", async (req: Request, res: Response) => {
237
259
  if (xmppUsername) params.xmppUsername = String(xmppUsername);
238
260
 
239
261
  const response = await chatService.getUsers(
240
- Object.keys(params).length > 0 ? params : undefined
262
+ Object.keys(params).length > 0 ? params : undefined,
241
263
  );
242
264
  res.json({ success: true, data: response });
243
265
  } catch (error) {
244
266
  if (axios.isAxiosError(error)) {
245
267
  res.status(error.response?.status || 500).json({
246
- error: "Failed to get users",
268
+ error: 'Failed to get users',
247
269
  details: error.response?.data,
248
270
  });
249
271
  } else {
250
- res.status(500).json({ error: "Internal server error" });
272
+ res.status(500).json({ error: 'Internal server error' });
251
273
  }
252
274
  }
253
275
  });
254
276
 
255
277
  // Update users (batch)
256
- router.patch("/users", async (req: Request, res: Response) => {
278
+ router.patch('/users', async (req: Request, res: Response) => {
257
279
  try {
258
280
  const { users } = req.body;
259
281
  if (!Array.isArray(users) || users.length === 0) {
260
- return res.status(400).json({ error: "users must be a non-empty array" });
282
+ return res.status(400).json({ error: 'users must be a non-empty array' });
261
283
  }
262
284
  if (users.length > 100) {
263
285
  return res
264
286
  .status(400)
265
- .json({ error: "Maximum 100 users allowed per request" });
287
+ .json({ error: 'Maximum 100 users allowed per request' });
266
288
  }
267
289
 
268
290
  const response = await chatService.updateUsers(users);
@@ -270,11 +292,11 @@ router.patch("/users", async (req: Request, res: Response) => {
270
292
  } catch (error) {
271
293
  if (axios.isAxiosError(error)) {
272
294
  res.status(error.response?.status || 500).json({
273
- error: "Failed to update users",
295
+ error: 'Failed to update users',
274
296
  details: error.response?.data,
275
297
  });
276
298
  } else {
277
- res.status(500).json({ error: "Internal server error" });
299
+ res.status(500).json({ error: 'Internal server error' });
278
300
  }
279
301
  }
280
302
  });
@@ -286,9 +308,9 @@ export default router;
286
308
 
287
309
  ```typescript
288
310
  // chat/chat.service.ts
289
- import { Injectable, HttpException, HttpStatus } from "@nestjs/common";
290
- import { getEthoraSDKService } from "@ethora/sdk-backend";
291
- import axios from "axios";
311
+ import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
312
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
313
+ import axios from 'axios';
292
314
 
293
315
  @Injectable()
294
316
  export class ChatService {
@@ -301,10 +323,10 @@ export class ChatService {
301
323
  if (axios.isAxiosError(error)) {
302
324
  throw new HttpException(
303
325
  {
304
- message: "Failed to create chat room",
326
+ message: 'Failed to create chat room',
305
327
  details: error.response?.data,
306
328
  },
307
- error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR
329
+ error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
308
330
  );
309
331
  }
310
332
  throw error;
@@ -318,10 +340,10 @@ export class ChatService {
318
340
  if (axios.isAxiosError(error)) {
319
341
  throw new HttpException(
320
342
  {
321
- message: "Failed to create user",
343
+ message: 'Failed to create user',
322
344
  details: error.response?.data,
323
345
  },
324
- error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR
346
+ error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
325
347
  );
326
348
  }
327
349
  throw error;
@@ -334,28 +356,28 @@ export class ChatService {
334
356
  }
335
357
 
336
358
  // chat/chat.controller.ts
337
- import { Controller, Post, Get, Param, Body } from "@nestjs/common";
338
- import { ChatService } from "./chat.service";
359
+ import { Controller, Post, Get, Param, Body } from '@nestjs/common';
360
+ import { ChatService } from './chat.service';
339
361
 
340
- @Controller("chat")
362
+ @Controller('chat')
341
363
  export class ChatController {
342
364
  constructor(private readonly chatService: ChatService) {}
343
365
 
344
- @Post("workspaces/:workspaceId/rooms")
366
+ @Post('workspaces/:workspaceId/rooms')
345
367
  async createChatRoom(
346
- @Param("workspaceId") workspaceId: string,
347
- @Body() roomData: any
368
+ @Param('workspaceId') workspaceId: string,
369
+ @Body() roomData: any,
348
370
  ) {
349
371
  return this.chatService.createChatRoom(workspaceId, roomData);
350
372
  }
351
373
 
352
- @Post("users/:userId")
353
- async createUser(@Param("userId") userId: string, @Body() userData: any) {
374
+ @Post('users/:userId')
375
+ async createUser(@Param('userId') userId: string, @Body() userData: any) {
354
376
  return this.chatService.createUser(userId, userData);
355
377
  }
356
378
 
357
- @Get("users/:userId/token")
358
- getClientToken(@Param("userId") userId: string) {
379
+ @Get('users/:userId/token')
380
+ getClientToken(@Param('userId') userId: string) {
359
381
  return { token: this.chatService.generateClientToken(userId) };
360
382
  }
361
383
  }
@@ -365,15 +387,15 @@ export class ChatController {
365
387
 
366
388
  ```typescript
367
389
  // routes/chat.ts
368
- import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
369
- import { getEthoraSDKService } from "@ethora/sdk-backend";
390
+ import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
391
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
370
392
 
371
393
  const chatService = getEthoraSDKService();
372
394
 
373
395
  export async function chatRoutes(fastify: FastifyInstance) {
374
396
  // Create chat room
375
397
  fastify.post(
376
- "/workspaces/:workspaceId/chat",
398
+ '/workspaces/:workspaceId/chat',
377
399
  async (request: FastifyRequest, reply: FastifyReply) => {
378
400
  const { workspaceId } = request.params as { workspaceId: string };
379
401
  const roomData = request.body as any;
@@ -381,23 +403,23 @@ export async function chatRoutes(fastify: FastifyInstance) {
381
403
  try {
382
404
  const response = await chatService.createChatRoom(
383
405
  workspaceId,
384
- roomData
406
+ roomData,
385
407
  );
386
408
  return { success: true, data: response };
387
409
  } catch (error) {
388
- reply.code(500).send({ error: "Failed to create chat room" });
410
+ reply.code(500).send({ error: 'Failed to create chat room' });
389
411
  }
390
- }
412
+ },
391
413
  );
392
414
 
393
415
  // Generate client token
394
416
  fastify.get(
395
- "/users/:userId/chat-token",
417
+ '/users/:userId/chat-token',
396
418
  async (request: FastifyRequest, reply: FastifyReply) => {
397
419
  const { userId } = request.params as { userId: string };
398
420
  const token = chatService.createChatUserJwtToken(userId);
399
421
  return { token };
400
- }
422
+ },
401
423
  );
402
424
  }
403
425
  ```
@@ -412,7 +434,7 @@ When creating a new workspace, set up the chat room and initial users:
412
434
  async function setupWorkspaceChat(
413
435
  workspaceId: string,
414
436
  userIds: string[],
415
- adminUserId: string
437
+ adminUserId: string,
416
438
  ) {
417
439
  const chatService = getEthoraSDKService();
418
440
 
@@ -421,15 +443,15 @@ async function setupWorkspaceChat(
421
443
  await chatService.createChatRoom(workspaceId, {
422
444
  title: `Workspace ${workspaceId}`,
423
445
  uuid: workspaceId,
424
- type: "group",
446
+ type: 'group',
425
447
  });
426
448
 
427
449
  // 2. Create users (if they don't exist)
428
450
  for (const userId of userIds) {
429
451
  try {
430
452
  await chatService.createUser(userId, {
431
- firstName: "User",
432
- lastName: "Name",
453
+ firstName: 'User',
454
+ lastName: 'Name',
433
455
  });
434
456
  } catch (error) {
435
457
  // User might already exist, continue
@@ -444,12 +466,12 @@ async function setupWorkspaceChat(
444
466
  try {
445
467
  await chatService.grantChatbotAccessToChatRoom(workspaceId);
446
468
  } catch (error) {
447
- console.warn("Chatbot access not configured or failed");
469
+ console.warn('Chatbot access not configured or failed');
448
470
  }
449
471
 
450
472
  return { success: true };
451
473
  } catch (error) {
452
- console.error("Failed to setup workspace chat:", error);
474
+ console.error('Failed to setup workspace chat:', error);
453
475
  throw error;
454
476
  }
455
477
  }
@@ -462,7 +484,7 @@ When a new user joins your platform:
462
484
  ```typescript
463
485
  async function onboardNewUser(
464
486
  userId: string,
465
- userData: { firstName: string; lastName: string; email: string }
487
+ userData: { firstName: string; lastName: string; email: string },
466
488
  ) {
467
489
  const chatService = getEthoraSDKService();
468
490
 
@@ -483,7 +505,7 @@ async function onboardNewUser(
483
505
  chatToken: clientToken,
484
506
  };
485
507
  } catch (error) {
486
- console.error("Failed to onboard user:", error);
508
+ console.error('Failed to onboard user:', error);
487
509
  throw error;
488
510
  }
489
511
  }
@@ -510,13 +532,50 @@ async function addUserToWorkspace(workspaceId: string, userId: string) {
510
532
 
511
533
  return { success: true };
512
534
  } catch (error) {
513
- console.error("Failed to add user to workspace:", error);
535
+ console.error('Failed to add user to workspace:', error);
536
+ throw error;
537
+ }
538
+ }
539
+ ```
540
+
541
+ ### Use Case 4: Removing User from Workspace
542
+
543
+ When removing a user from a workspace:
544
+
545
+ ```typescript
546
+ async function removeUserFromWorkspace(workspaceId: string, userId: string) {
547
+ const chatService = getEthoraSDKService();
548
+
549
+ try {
550
+ // Remove access from workspace chat room
551
+ await chatService.removeUserAccessFromChatRoom(workspaceId, userId);
552
+
553
+ return { success: true };
554
+ } catch (error) {
555
+ console.error('Failed to remove user from workspace:', error);
556
+ throw error;
557
+ }
558
+ }
559
+
560
+ // Remove multiple users at once
561
+ async function removeMultipleUsersFromWorkspace(
562
+ workspaceId: string,
563
+ userIds: string[],
564
+ ) {
565
+ const chatService = getEthoraSDKService();
566
+
567
+ try {
568
+ await chatService.removeUserAccessFromChatRoom(workspaceId, userIds);
569
+ return { success: true };
570
+ } catch (error) {
571
+ console.error('Failed to remove users from workspace:', error);
514
572
  throw error;
515
573
  }
516
574
  }
517
575
  ```
518
576
 
519
- ### Use Case 4: Cleanup on Workspace Deletion
577
+ ### Use Case 5: Cleanup on Workspace Deletion
578
+
520
579
 
521
580
  When deleting a workspace:
522
581
 
@@ -533,19 +592,19 @@ async function cleanupWorkspaceChat(workspaceId: string, userIds: string[]) {
533
592
  try {
534
593
  await chatService.deleteUsers(userIds);
535
594
  } catch (error) {
536
- console.warn("Some users might not exist:", error);
595
+ console.warn('Some users might not exist:', error);
537
596
  }
538
597
  }
539
598
 
540
599
  return { success: true };
541
600
  } catch (error) {
542
- console.error("Failed to cleanup workspace chat:", error);
601
+ console.error('Failed to cleanup workspace chat:', error);
543
602
  throw error;
544
603
  }
545
604
  }
546
605
  ```
547
606
 
548
- ### Use Case 5: Getting Users
607
+ ### Use Case 6: Getting Users
549
608
 
550
609
  Retrieve users from the chat service:
551
610
 
@@ -560,23 +619,23 @@ async function getUsersExample() {
560
619
 
561
620
  // Get users by chat name (group chat)
562
621
  const groupChatUsers = await chatService.getUsers({
563
- chatName: "appId_workspaceId",
622
+ chatName: 'appId_workspaceId',
564
623
  });
565
624
 
566
625
  // Get users by chat name (1-on-1 chat)
567
626
  const oneOnOneUsers = await chatService.getUsers({
568
- chatName: "userA-userB",
627
+ chatName: 'userA-userB',
569
628
  });
570
629
 
571
630
  return { allUsers, groupChatUsers, oneOnOneUsers };
572
631
  } catch (error) {
573
- console.error("Failed to get users:", error);
632
+ console.error('Failed to get users:', error);
574
633
  throw error;
575
634
  }
576
635
  }
577
636
  ```
578
637
 
579
- ### Use Case 6: Updating Users (Batch)
638
+ ### Use Case 7: Updating Users (Batch)
580
639
 
581
640
  Update multiple users at once:
582
641
 
@@ -588,48 +647,224 @@ async function updateUsersExample() {
588
647
  // Update multiple users (1-100 users per request)
589
648
  const response = await chatService.updateUsers([
590
649
  {
591
- xmppUsername: "appId_user1",
592
- firstName: "John",
593
- lastName: "Doe",
594
- username: "johndoe",
595
- profileImage: "https://example.com/avatar1.jpg",
650
+ xmppUsername: 'appId_user1',
651
+ firstName: 'John',
652
+ lastName: 'Doe',
653
+ username: 'johndoe',
654
+ profileImage: 'https://example.com/avatar1.jpg',
596
655
  },
597
656
  {
598
- xmppUsername: "appId_user2",
599
- firstName: "Jane",
600
- lastName: "Smith",
601
- username: "janesmith",
657
+ xmppUsername: 'appId_user2',
658
+ firstName: 'Jane',
659
+ lastName: 'Smith',
660
+ username: 'janesmith',
602
661
  },
603
662
  ]);
604
663
 
605
664
  // Check results
606
665
  response.results?.forEach((result: any) => {
607
- if (result.status === "updated") {
666
+ if (result.status === 'updated') {
608
667
  console.log(`User ${result.xmppUsername} updated successfully`);
609
- } else if (result.status === "not-found") {
668
+ } else if (result.status === 'not-found') {
610
669
  console.warn(`User ${result.xmppUsername} not found`);
611
- } else if (result.status === "skipped") {
670
+ } else if (result.status === 'skipped') {
612
671
  console.log(`User ${result.xmppUsername} update skipped`);
613
672
  }
614
673
  });
615
674
 
616
675
  return response;
617
676
  } catch (error) {
618
- console.error("Failed to update users:", error);
677
+ console.error('Failed to update users:', error);
619
678
  throw error;
620
679
  }
621
680
  }
622
681
  ```
623
682
 
683
+ ## API Reference
684
+
685
+ ### Core Methods
686
+
687
+ #### `createUser(userId: UUID, userData?: Record<string, unknown>): Promise<ApiResponse>`
688
+
689
+ Creates a user in the chat service using the `/v2/users/batch` endpoint.
690
+
691
+ **Parameters:**
692
+ - `userId` (UUID): The unique identifier of the user
693
+ - `userData` (optional): Additional user data
694
+ - `firstName` (string): User's first name
695
+ - `lastName` (string): User's last name (minimum 2 characters)
696
+ - `email` (string): User's email address
697
+ - `password` (string): User's password
698
+ - `displayName` (string): Display name (will be split into firstName/lastName if needed)
699
+
700
+ **Returns:** Promise resolving to the API response
701
+
702
+ **Note:** The API requires `lastName` to be at least 2 characters. If not provided or too short, defaults to "User".
703
+
704
+ ---
705
+
706
+ #### `createChatRoom(chatId: UUID, roomData?: Record<string, unknown>): Promise<ApiResponse>`
707
+
708
+ Creates a chat room using the `/v2/chats` endpoint.
709
+
710
+ **Parameters:**
711
+ - `chatId` (UUID): The unique identifier of the chat/workspace
712
+ - `roomData` (optional): Room configuration
713
+ - `title` (string): Chat room title
714
+ - `uuid` (string): Room UUID (defaults to chatId)
715
+ - `type` (string): Room type (defaults to "group")
716
+
717
+ **Returns:** Promise resolving to the API response
718
+
719
+ ---
720
+
721
+ #### `grantUserAccessToChatRoom(chatId: UUID, userId: UUID | UUID[]): Promise<ApiResponse>`
722
+
723
+ Grants user(s) access to a chat room using the `/v2/chats/users-access` endpoint.
724
+
725
+ **Parameters:**
726
+ - `chatId` (UUID): The unique identifier of the chat/workspace
727
+ - `userId` (UUID | UUID[]): Single user ID or array of user IDs
728
+
729
+ **Returns:** Promise resolving to the API response
730
+
731
+ **Note:** User IDs are automatically prefixed with `{appId}_` if they don't already have the prefix.
732
+
733
+ ---
734
+
735
+ #### `removeUserAccessFromChatRoom(chatId: UUID, userId: UUID | UUID[]): Promise<ApiResponse>`
736
+
737
+ Removes user(s) access from a chat room using the `/v2/chats/users-access` DELETE endpoint.
738
+
739
+ **Parameters:**
740
+ - `chatId` (UUID): The unique identifier of the chat/workspace
741
+ - `userId` (UUID | UUID[]): Single user ID or array of user IDs to remove
742
+
743
+ **Returns:** Promise resolving to the API response
744
+
745
+ **Note:** User IDs are automatically prefixed with `{appId}_` if they don't already have the prefix.
746
+
747
+ ---
748
+
749
+ #### `grantChatbotAccessToChatRoom(chatId: UUID): Promise<ApiResponse>`
750
+
751
+ Grants chatbot access to a chat room.
752
+
753
+ **Parameters:**
754
+ - `chatId` (UUID): The unique identifier of the chat/workspace
755
+
756
+ **Returns:** Promise resolving to the API response
757
+
758
+ **Requires:** `ETHORA_CHAT_BOT_JID` environment variable to be set
759
+
760
+ ---
761
+
762
+ #### `getUsers(params?: GetUsersQueryParams): Promise<ApiResponse>`
763
+
764
+ Retrieves users from the chat service using the `/v2/chats/users` endpoint.
765
+
766
+ **Parameters:**
767
+ - `params` (GetUsersQueryParams, optional): Query parameters
768
+ - `chatName` (string): Filter by chat name
769
+ - Group chats: `appId_chatId` format
770
+ - 1-on-1 chats: `xmppUsernameA-xmppUsernameB` format
771
+ - `xmppUsername` (string): Filter by specific XMPP username
772
+
773
+ **Query Modes:**
774
+ - No parameters: Returns all users of the app
775
+ - With `chatName`: Returns all users of the specified chat
776
+ - With `xmppUsername`: Returns a specific user
777
+
778
+ **Returns:** Promise resolving to the API response with users array
779
+
780
+ ---
781
+
782
+ #### `updateUsers(users: UpdateUserData[]): Promise<ApiResponse>`
783
+
784
+ Updates multiple users at once using the `/v2/chats/users` PATCH endpoint.
785
+
786
+ **Parameters:**
787
+ - `users` (UpdateUserData[]): Array of user data to update (1-100 users)
788
+ - `xmppUsername` (string, required): XMPP username to identify the user
789
+ - `firstName` (string, optional): First name
790
+ - `lastName` (string, optional): Last name
791
+ - `username` (string, optional): Username
792
+ - `profileImage` (string, optional): Profile image URL
793
+
794
+ **Returns:** Promise resolving to the API response with results array
795
+
796
+ **Response Status Values:**
797
+ - `updated`: User was successfully updated (includes updated user data)
798
+ - `not-found`: User was not found
799
+ - `skipped`: User update was skipped
800
+
801
+ **Limits:** 1-100 users per request
802
+
803
+ ---
804
+
805
+ #### `deleteUsers(userIds: UUID[]): Promise<ApiResponse>`
806
+
807
+ Deletes users from the chat service using the `/v1/users/batch` endpoint.
808
+
809
+ **Parameters:**
810
+ - `userIds` (UUID[]): Array of user IDs to delete
811
+
812
+ **Returns:** Promise resolving to the API response
813
+
814
+ **Note:** Gracefully handles non-existent users (422 status with "not found").
815
+
816
+ ---
817
+
818
+ #### `deleteChatRoom(chatId: UUID): Promise<ApiResponse>`
819
+
820
+ Deletes a chat room using the `/v1/chats` endpoint.
821
+
822
+ **Parameters:**
823
+ - `chatId` (UUID): The unique identifier of the chat/workspace
824
+
825
+ **Returns:** Promise resolving to the API response
826
+
827
+ **Note:** Gracefully handles non-existent rooms (422 status with "not found").
828
+
829
+ ---
830
+
831
+ ### Helper Methods
832
+
833
+ #### `createChatName(chatId: UUID, full?: boolean): string`
834
+
835
+ Generates a chat room JID from a chat ID.
836
+
837
+ **Parameters:**
838
+ - `chatId` (UUID): The unique identifier of the chat
839
+ - `full` (boolean, optional): Whether to include the full JID domain (default: true)
840
+
841
+ **Returns:** The JID string
842
+ - Full: `{appId}_{chatId}@conference.xmpp.ethoradev.com`
843
+ - Short: `{appId}_{chatId}`
844
+
845
+ ---
846
+
847
+ #### `createChatUserJwtToken(userId: UUID): string`
848
+
849
+ Creates a client-side JWT token for user authentication.
850
+
851
+ **Parameters:**
852
+ - `userId` (UUID): The unique identifier of the user
853
+
854
+ **Returns:** The encoded JWT token for client-side authentication
855
+
856
+ ---
857
+
624
858
  ## Error Handling
625
859
 
860
+
626
861
  ### Handling API Errors
627
862
 
628
863
  The SDK uses Axios for HTTP requests, so errors are AxiosError instances:
629
864
 
630
865
  ```typescript
631
- import axios from "axios";
632
- import { getEthoraSDKService } from "@ethora/sdk-backend";
866
+ import axios from 'axios';
867
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
633
868
 
634
869
  const chatService = getEthoraSDKService();
635
870
 
@@ -644,20 +879,20 @@ async function createChatRoomSafely(workspaceId: string) {
644
879
  // Handle specific error cases
645
880
  if (status === 422) {
646
881
  // Validation error
647
- console.error("Validation error:", errorData);
882
+ console.error('Validation error:', errorData);
648
883
  } else if (status === 401) {
649
884
  // Authentication error
650
- console.error("Authentication failed - check your credentials");
885
+ console.error('Authentication failed - check your credentials');
651
886
  } else if (status === 404) {
652
887
  // Resource not found
653
- console.error("Resource not found");
888
+ console.error('Resource not found');
654
889
  } else {
655
890
  // Other HTTP errors
656
891
  console.error(`HTTP error ${status}:`, errorData);
657
892
  }
658
893
  } else {
659
894
  // Non-HTTP errors
660
- console.error("Unexpected error:", error);
895
+ console.error('Unexpected error:', error);
661
896
  }
662
897
  throw error;
663
898
  }
@@ -678,17 +913,17 @@ async function ensureChatRoomExists(workspaceId: string) {
678
913
  if (axios.isAxiosError(error)) {
679
914
  const errorData = error.response?.data;
680
915
  const errorMessage =
681
- typeof errorData === "object" && errorData !== null
682
- ? (errorData as { error?: string }).error || ""
683
- : String(errorData || "");
916
+ typeof errorData === 'object' && errorData !== null
917
+ ? (errorData as { error?: string }).error || ''
918
+ : String(errorData || '');
684
919
 
685
920
  // If room already exists, that's okay
686
921
  if (
687
922
  error.response?.status === 422 &&
688
- (errorMessage.includes("already exist") ||
689
- errorMessage.includes("already exists"))
923
+ (errorMessage.includes('already exist') ||
924
+ errorMessage.includes('already exists'))
690
925
  ) {
691
- console.log("Chat room already exists, continuing...");
926
+ console.log('Chat room already exists, continuing...');
692
927
  return; // Success - room exists
693
928
  }
694
929
  }
@@ -719,8 +954,8 @@ Create a service wrapper in your application:
719
954
 
720
955
  ```typescript
721
956
  // services/chatService.ts
722
- import { getEthoraSDKService } from "@ethora/sdk-backend";
723
- import type { ChatRepository } from "@ethora/sdk-backend";
957
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
958
+ import type { ChatRepository } from '@ethora/sdk-backend';
724
959
 
725
960
  class ChatServiceWrapper {
726
961
  private service: ChatRepository;
@@ -752,16 +987,16 @@ Validate environment variables on application startup:
752
987
  // config/validateEnv.ts
753
988
  function validateEthoraConfig() {
754
989
  const required = [
755
- "ETHORA_CHAT_API_URL",
756
- "ETHORA_CHAT_APP_ID",
757
- "ETHORA_CHAT_APP_SECRET",
990
+ 'ETHORA_CHAT_API_URL',
991
+ 'ETHORA_CHAT_APP_ID',
992
+ 'ETHORA_CHAT_APP_SECRET',
758
993
  ];
759
994
 
760
995
  const missing = required.filter((key) => !process.env[key]);
761
996
 
762
997
  if (missing.length > 0) {
763
998
  throw new Error(
764
- `Missing required Ethora environment variables: ${missing.join(", ")}`
999
+ `Missing required Ethora environment variables: ${missing.join(', ')}`,
765
1000
  );
766
1001
  }
767
1002
  }
@@ -775,8 +1010,8 @@ validateEthoraConfig();
775
1010
  Integrate with your existing logging system:
776
1011
 
777
1012
  ```typescript
778
- import { getEthoraSDKService } from "@ethora/sdk-backend";
779
- import { logger } from "./utils/logger"; // Your logger
1013
+ import { getEthoraSDKService } from '@ethora/sdk-backend';
1014
+ import { logger } from './utils/logger'; // Your logger
780
1015
 
781
1016
  const chatService = getEthoraSDKService();
782
1017
 
@@ -798,7 +1033,7 @@ async function createChatRoomWithLogging(workspaceId: string) {
798
1033
  Use TypeScript types from the SDK:
799
1034
 
800
1035
  ```typescript
801
- import type { UUID, ApiResponse } from "@ethora/sdk-backend";
1036
+ import type { UUID, ApiResponse } from '@ethora/sdk-backend';
802
1037
 
803
1038
  async function createUserTyped(
804
1039
  userId: UUID,
@@ -806,7 +1041,7 @@ async function createUserTyped(
806
1041
  firstName: string;
807
1042
  lastName: string;
808
1043
  email: string;
809
- }
1044
+ },
810
1045
  ): Promise<ApiResponse> {
811
1046
  const chatService = getEthoraSDKService();
812
1047
  return await chatService.createUser(userId, userData);
@@ -839,7 +1074,7 @@ try {
839
1074
  } catch (error) {
840
1075
  if (axios.isAxiosError(error) && error.response?.status === 422) {
841
1076
  // User already exists, continue
842
- console.log("User already exists");
1077
+ console.log('User already exists');
843
1078
  } else {
844
1079
  throw error;
845
1080
  }
@@ -887,3 +1122,6 @@ Apache 2.0
887
1122
  ## Support
888
1123
 
889
1124
  For issues and questions, please open an issue on the GitHub repository.
1125
+
1126
+ To run tests with logs run from root:
1127
+ TEST_LOG_FILE=logs/chat-repo.log npm test