@ethora/sdk-backend 25.1.1

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 ADDED
@@ -0,0 +1,889 @@
1
+ <!-- @format -->
2
+
3
+ # Adding Ethora SDK to Your Node.js Backend
4
+
5
+ This guide will walk you through integrating the Ethora SDK into your existing Node.js backend application.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Prerequisites](#prerequisites)
10
+ - [Installation](#installation)
11
+ - [Environment Configuration](#environment-configuration)
12
+ - [Basic Integration](#basic-integration)
13
+ - [Integration Patterns](#integration-patterns)
14
+ - [Common Use Cases](#common-use-cases)
15
+ - [Error Handling](#error-handling)
16
+ - [Best Practices](#best-practices)
17
+ - [Troubleshooting](#troubleshooting)
18
+
19
+ ## Prerequisites
20
+
21
+ - Node.js 18+ or higher
22
+ - TypeScript 5.0+ (for TypeScript projects)
23
+ - An existing Node.js backend application (Express, Fastify, NestJS, etc.)
24
+ - Ethora API credentials:
25
+ - `ETHORA_CHAT_API_URL`
26
+ - `ETHORA_CHAT_APP_ID`
27
+ - `ETHORA_CHAT_APP_SECRET`
28
+ - `ETHORA_CHAT_BOT_JID` (optional, for chatbot features)
29
+
30
+ ## API Documentation (Swagger)
31
+
32
+ Ethora exposes Swagger UI from every running backend instance at:
33
+
34
+ - **Hosted (Ethora main)**: `https://api.ethoradev.com/api-docs/`
35
+ - **Enterprise/self-hosted**: `https://api.<your-domain>/api-docs/`
36
+
37
+ If you are running a separate staging instance, the same pattern applies (e.g. `https://api.asterotoken.com/api-docs/`).
38
+
39
+ ## Installation
40
+
41
+ ### Step 1: Install the Package
42
+
43
+ ```bash
44
+ npm install @ethora/sdk-backend
45
+ # or
46
+ yarn add @ethora/sdk-backend
47
+ # or
48
+ pnpm add @ethora/sdk-backend
49
+ ```
50
+
51
+ ### Step 2: Install Type Definitions (if using TypeScript)
52
+
53
+ The package includes TypeScript definitions, so no additional `@types` package is needed.
54
+
55
+ ## Environment Configuration
56
+
57
+ ### Step 1: Add Environment Variables
58
+
59
+ Add the following environment variables to your `.env` file or your environment configuration:
60
+
61
+ ```bash
62
+ # Required
63
+ ETHORA_CHAT_API_URL=https://api.ethoradev.com
64
+ ETHORA_CHAT_APP_ID=your_app_id_here
65
+ ETHORA_CHAT_APP_SECRET=your_app_secret_here
66
+
67
+ # Optional (for chatbot features)
68
+ ETHORA_CHAT_BOT_JID=your_bot_jid@domain.com
69
+ ```
70
+
71
+ ### Step 2: Load Environment Variables
72
+
73
+ If you're using a `.env` file, ensure you have `dotenv` installed and configured:
74
+
75
+ ```bash
76
+ npm install dotenv
77
+ ```
78
+
79
+ In your main application file (e.g., `app.js`, `server.js`, or `index.ts`):
80
+
81
+ ```typescript
82
+ import dotenv from "dotenv";
83
+
84
+ // Load environment variables
85
+ dotenv.config();
86
+ ```
87
+
88
+ ## Basic Integration
89
+
90
+ ### Step 1: Import the SDK
91
+
92
+ ```typescript
93
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
94
+ ```
95
+
96
+ ### Step 2: Initialize the Service
97
+
98
+ You can initialize the service in several ways:
99
+
100
+ #### Option A: Singleton Pattern (Recommended)
101
+
102
+ ```typescript
103
+ // services/chatService.ts
104
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
105
+
106
+ // Get the singleton instance
107
+ const chatService = getEthoraSDKService();
108
+
109
+ export default chatService;
110
+ ```
111
+
112
+ #### Option B: Direct Initialization
113
+
114
+ ```typescript
115
+ // In your route handler or service
116
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
117
+
118
+ const chatService = getEthoraSDKService();
119
+ ```
120
+
121
+ #### Option C: Dependency Injection (for frameworks like NestJS)
122
+
123
+ ```typescript
124
+ // chat.service.ts
125
+ import { Injectable } from "@nestjs/common";
126
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
127
+
128
+ @Injectable()
129
+ export class ChatService {
130
+ private readonly ethoraService = getEthoraSDKService();
131
+
132
+ // Your methods here
133
+ }
134
+ ```
135
+
136
+ ## Integration Patterns
137
+
138
+ ### Pattern 1: Express.js Integration
139
+
140
+ ```typescript
141
+ // routes/chat.ts
142
+ import express, { Request, Response } from "express";
143
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
144
+ import axios from "axios";
145
+
146
+ const router = express.Router();
147
+ const chatService = getEthoraSDKService();
148
+
149
+ // Create a chat room for a workspace
150
+ router.post(
151
+ "/workspaces/:workspaceId/chat",
152
+ async (req: Request, res: Response) => {
153
+ try {
154
+ const { workspaceId } = req.params;
155
+ const roomData = req.body;
156
+
157
+ const response = await chatService.createChatRoom(workspaceId, {
158
+ title: roomData.title || `Chat Room ${workspaceId}`,
159
+ uuid: workspaceId,
160
+ type: roomData.type || "group",
161
+ ...roomData,
162
+ });
163
+
164
+ res.json({ success: true, data: response });
165
+ } catch (error) {
166
+ if (axios.isAxiosError(error)) {
167
+ res.status(error.response?.status || 500).json({
168
+ error: "Failed to create chat room",
169
+ details: error.response?.data,
170
+ });
171
+ } else {
172
+ res.status(500).json({ error: "Internal server error" });
173
+ }
174
+ }
175
+ }
176
+ );
177
+
178
+ // Create a user
179
+ router.post("/users/:userId", async (req: Request, res: Response) => {
180
+ try {
181
+ const { userId } = req.params;
182
+ const userData = req.body;
183
+
184
+ const response = await chatService.createUser(userId, userData);
185
+ res.json({ success: true, data: response });
186
+ } catch (error) {
187
+ if (axios.isAxiosError(error)) {
188
+ res.status(error.response?.status || 500).json({
189
+ error: "Failed to create user",
190
+ details: error.response?.data,
191
+ });
192
+ } else {
193
+ res.status(500).json({ error: "Internal server error" });
194
+ }
195
+ }
196
+ });
197
+
198
+ // Grant user access to chat room
199
+ router.post(
200
+ "/workspaces/:workspaceId/chat/users/:userId",
201
+ async (req: Request, res: Response) => {
202
+ try {
203
+ const { workspaceId, userId } = req.params;
204
+
205
+ await chatService.grantUserAccessToChatRoom(workspaceId, userId);
206
+ res.json({ success: true, message: "Access granted" });
207
+ } catch (error) {
208
+ if (axios.isAxiosError(error)) {
209
+ res.status(error.response?.status || 500).json({
210
+ error: "Failed to grant access",
211
+ details: error.response?.data,
212
+ });
213
+ } else {
214
+ res.status(500).json({ error: "Internal server error" });
215
+ }
216
+ }
217
+ }
218
+ );
219
+
220
+ // Generate client JWT token
221
+ router.get("/users/:userId/chat-token", (req: Request, res: Response) => {
222
+ try {
223
+ const { userId } = req.params;
224
+ const token = chatService.createChatUserJwtToken(userId);
225
+ res.json({ token });
226
+ } catch (error) {
227
+ res.status(500).json({ error: "Failed to generate token" });
228
+ }
229
+ });
230
+
231
+ // Get users
232
+ router.get("/users", async (req: Request, res: Response) => {
233
+ try {
234
+ const { chatName, xmppUsername } = req.query;
235
+ const params: any = {};
236
+ if (chatName) params.chatName = String(chatName);
237
+ if (xmppUsername) params.xmppUsername = String(xmppUsername);
238
+
239
+ const response = await chatService.getUsers(
240
+ Object.keys(params).length > 0 ? params : undefined
241
+ );
242
+ res.json({ success: true, data: response });
243
+ } catch (error) {
244
+ if (axios.isAxiosError(error)) {
245
+ res.status(error.response?.status || 500).json({
246
+ error: "Failed to get users",
247
+ details: error.response?.data,
248
+ });
249
+ } else {
250
+ res.status(500).json({ error: "Internal server error" });
251
+ }
252
+ }
253
+ });
254
+
255
+ // Update users (batch)
256
+ router.patch("/users", async (req: Request, res: Response) => {
257
+ try {
258
+ const { users } = req.body;
259
+ if (!Array.isArray(users) || users.length === 0) {
260
+ return res.status(400).json({ error: "users must be a non-empty array" });
261
+ }
262
+ if (users.length > 100) {
263
+ return res
264
+ .status(400)
265
+ .json({ error: "Maximum 100 users allowed per request" });
266
+ }
267
+
268
+ const response = await chatService.updateUsers(users);
269
+ res.json({ success: true, data: response });
270
+ } catch (error) {
271
+ if (axios.isAxiosError(error)) {
272
+ res.status(error.response?.status || 500).json({
273
+ error: "Failed to update users",
274
+ details: error.response?.data,
275
+ });
276
+ } else {
277
+ res.status(500).json({ error: "Internal server error" });
278
+ }
279
+ }
280
+ });
281
+
282
+ export default router;
283
+ ```
284
+
285
+ ### Pattern 2: NestJS Integration
286
+
287
+ ```typescript
288
+ // chat/chat.service.ts
289
+ import { Injectable, HttpException, HttpStatus } from "@nestjs/common";
290
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
291
+ import axios from "axios";
292
+
293
+ @Injectable()
294
+ export class ChatService {
295
+ private readonly ethoraService = getEthoraSDKService();
296
+
297
+ async createChatRoom(workspaceId: string, roomData?: any) {
298
+ try {
299
+ return await this.ethoraService.createChatRoom(workspaceId, roomData);
300
+ } catch (error) {
301
+ if (axios.isAxiosError(error)) {
302
+ throw new HttpException(
303
+ {
304
+ message: "Failed to create chat room",
305
+ details: error.response?.data,
306
+ },
307
+ error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR
308
+ );
309
+ }
310
+ throw error;
311
+ }
312
+ }
313
+
314
+ async createUser(userId: string, userData?: any) {
315
+ try {
316
+ return await this.ethoraService.createUser(userId, userData);
317
+ } catch (error) {
318
+ if (axios.isAxiosError(error)) {
319
+ throw new HttpException(
320
+ {
321
+ message: "Failed to create user",
322
+ details: error.response?.data,
323
+ },
324
+ error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR
325
+ );
326
+ }
327
+ throw error;
328
+ }
329
+ }
330
+
331
+ generateClientToken(userId: string): string {
332
+ return this.ethoraService.createChatUserJwtToken(userId);
333
+ }
334
+ }
335
+
336
+ // chat/chat.controller.ts
337
+ import { Controller, Post, Get, Param, Body } from "@nestjs/common";
338
+ import { ChatService } from "./chat.service";
339
+
340
+ @Controller("chat")
341
+ export class ChatController {
342
+ constructor(private readonly chatService: ChatService) {}
343
+
344
+ @Post("workspaces/:workspaceId/rooms")
345
+ async createChatRoom(
346
+ @Param("workspaceId") workspaceId: string,
347
+ @Body() roomData: any
348
+ ) {
349
+ return this.chatService.createChatRoom(workspaceId, roomData);
350
+ }
351
+
352
+ @Post("users/:userId")
353
+ async createUser(@Param("userId") userId: string, @Body() userData: any) {
354
+ return this.chatService.createUser(userId, userData);
355
+ }
356
+
357
+ @Get("users/:userId/token")
358
+ getClientToken(@Param("userId") userId: string) {
359
+ return { token: this.chatService.generateClientToken(userId) };
360
+ }
361
+ }
362
+ ```
363
+
364
+ ### Pattern 3: Fastify Integration
365
+
366
+ ```typescript
367
+ // routes/chat.ts
368
+ import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
369
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
370
+
371
+ const chatService = getEthoraSDKService();
372
+
373
+ export async function chatRoutes(fastify: FastifyInstance) {
374
+ // Create chat room
375
+ fastify.post(
376
+ "/workspaces/:workspaceId/chat",
377
+ async (request: FastifyRequest, reply: FastifyReply) => {
378
+ const { workspaceId } = request.params as { workspaceId: string };
379
+ const roomData = request.body as any;
380
+
381
+ try {
382
+ const response = await chatService.createChatRoom(
383
+ workspaceId,
384
+ roomData
385
+ );
386
+ return { success: true, data: response };
387
+ } catch (error) {
388
+ reply.code(500).send({ error: "Failed to create chat room" });
389
+ }
390
+ }
391
+ );
392
+
393
+ // Generate client token
394
+ fastify.get(
395
+ "/users/:userId/chat-token",
396
+ async (request: FastifyRequest, reply: FastifyReply) => {
397
+ const { userId } = request.params as { userId: string };
398
+ const token = chatService.createChatUserJwtToken(userId);
399
+ return { token };
400
+ }
401
+ );
402
+ }
403
+ ```
404
+
405
+ ## Common Use Cases
406
+
407
+ ### Use Case 1: Workspace Setup Flow
408
+
409
+ When creating a new workspace, set up the chat room and initial users:
410
+
411
+ ```typescript
412
+ async function setupWorkspaceChat(
413
+ workspaceId: string,
414
+ userIds: string[],
415
+ adminUserId: string
416
+ ) {
417
+ const chatService = getEthoraSDKService();
418
+
419
+ try {
420
+ // 1. Create chat room
421
+ await chatService.createChatRoom(workspaceId, {
422
+ title: `Workspace ${workspaceId}`,
423
+ uuid: workspaceId,
424
+ type: "group",
425
+ });
426
+
427
+ // 2. Create users (if they don't exist)
428
+ for (const userId of userIds) {
429
+ try {
430
+ await chatService.createUser(userId, {
431
+ firstName: "User",
432
+ lastName: "Name",
433
+ });
434
+ } catch (error) {
435
+ // User might already exist, continue
436
+ console.warn(`User ${userId} might already exist`);
437
+ }
438
+ }
439
+
440
+ // 3. Grant access to all users
441
+ await chatService.grantUserAccessToChatRoom(workspaceId, userIds);
442
+
443
+ // 4. Grant chatbot access (if configured)
444
+ try {
445
+ await chatService.grantChatbotAccessToChatRoom(workspaceId);
446
+ } catch (error) {
447
+ console.warn("Chatbot access not configured or failed");
448
+ }
449
+
450
+ return { success: true };
451
+ } catch (error) {
452
+ console.error("Failed to setup workspace chat:", error);
453
+ throw error;
454
+ }
455
+ }
456
+ ```
457
+
458
+ ### Use Case 2: User Onboarding
459
+
460
+ When a new user joins your platform:
461
+
462
+ ```typescript
463
+ async function onboardNewUser(
464
+ userId: string,
465
+ userData: { firstName: string; lastName: string; email: string }
466
+ ) {
467
+ const chatService = getEthoraSDKService();
468
+
469
+ try {
470
+ // Create user in chat service
471
+ await chatService.createUser(userId, {
472
+ firstName: userData.firstName,
473
+ lastName: userData.lastName,
474
+ email: userData.email,
475
+ displayName: `${userData.firstName} ${userData.lastName}`,
476
+ });
477
+
478
+ // Generate client token for frontend
479
+ const clientToken = chatService.createChatUserJwtToken(userId);
480
+
481
+ return {
482
+ success: true,
483
+ chatToken: clientToken,
484
+ };
485
+ } catch (error) {
486
+ console.error("Failed to onboard user:", error);
487
+ throw error;
488
+ }
489
+ }
490
+ ```
491
+
492
+ ### Use Case 3: Adding User to Existing Workspace
493
+
494
+ When adding a user to an existing workspace:
495
+
496
+ ```typescript
497
+ async function addUserToWorkspace(workspaceId: string, userId: string) {
498
+ const chatService = getEthoraSDKService();
499
+
500
+ try {
501
+ // Ensure user exists
502
+ try {
503
+ await chatService.createUser(userId);
504
+ } catch (error) {
505
+ // User might already exist, continue
506
+ }
507
+
508
+ // Grant access to workspace chat room
509
+ await chatService.grantUserAccessToChatRoom(workspaceId, userId);
510
+
511
+ return { success: true };
512
+ } catch (error) {
513
+ console.error("Failed to add user to workspace:", error);
514
+ throw error;
515
+ }
516
+ }
517
+ ```
518
+
519
+ ### Use Case 4: Cleanup on Workspace Deletion
520
+
521
+ When deleting a workspace:
522
+
523
+ ```typescript
524
+ async function cleanupWorkspaceChat(workspaceId: string, userIds: string[]) {
525
+ const chatService = getEthoraSDKService();
526
+
527
+ try {
528
+ // Delete chat room (handles non-existent gracefully)
529
+ await chatService.deleteChatRoom(workspaceId);
530
+
531
+ // Optionally delete users (if they're no longer needed)
532
+ if (userIds.length > 0) {
533
+ try {
534
+ await chatService.deleteUsers(userIds);
535
+ } catch (error) {
536
+ console.warn("Some users might not exist:", error);
537
+ }
538
+ }
539
+
540
+ return { success: true };
541
+ } catch (error) {
542
+ console.error("Failed to cleanup workspace chat:", error);
543
+ throw error;
544
+ }
545
+ }
546
+ ```
547
+
548
+ ### Use Case 5: Getting Users
549
+
550
+ Retrieve users from the chat service:
551
+
552
+ ```typescript
553
+ async function getUsersExample() {
554
+ const chatService = getEthoraSDKService();
555
+
556
+ try {
557
+ // Get all users
558
+ const allUsers = await chatService.getUsers();
559
+ console.log(`Total users: ${allUsers.results?.length || 0}`);
560
+
561
+ // Get users by chat name (group chat)
562
+ const groupChatUsers = await chatService.getUsers({
563
+ chatName: "appId_workspaceId",
564
+ });
565
+
566
+ // Get users by chat name (1-on-1 chat)
567
+ const oneOnOneUsers = await chatService.getUsers({
568
+ chatName: "userA-userB",
569
+ });
570
+
571
+ return { allUsers, groupChatUsers, oneOnOneUsers };
572
+ } catch (error) {
573
+ console.error("Failed to get users:", error);
574
+ throw error;
575
+ }
576
+ }
577
+ ```
578
+
579
+ ### Use Case 6: Updating Users (Batch)
580
+
581
+ Update multiple users at once:
582
+
583
+ ```typescript
584
+ async function updateUsersExample() {
585
+ const chatService = getEthoraSDKService();
586
+
587
+ try {
588
+ // Update multiple users (1-100 users per request)
589
+ const response = await chatService.updateUsers([
590
+ {
591
+ xmppUsername: "appId_user1",
592
+ firstName: "John",
593
+ lastName: "Doe",
594
+ username: "johndoe",
595
+ profileImage: "https://example.com/avatar1.jpg",
596
+ },
597
+ {
598
+ xmppUsername: "appId_user2",
599
+ firstName: "Jane",
600
+ lastName: "Smith",
601
+ username: "janesmith",
602
+ },
603
+ ]);
604
+
605
+ // Check results
606
+ response.results?.forEach((result: any) => {
607
+ if (result.status === "updated") {
608
+ console.log(`User ${result.xmppUsername} updated successfully`);
609
+ } else if (result.status === "not-found") {
610
+ console.warn(`User ${result.xmppUsername} not found`);
611
+ } else if (result.status === "skipped") {
612
+ console.log(`User ${result.xmppUsername} update skipped`);
613
+ }
614
+ });
615
+
616
+ return response;
617
+ } catch (error) {
618
+ console.error("Failed to update users:", error);
619
+ throw error;
620
+ }
621
+ }
622
+ ```
623
+
624
+ ## Error Handling
625
+
626
+ ### Handling API Errors
627
+
628
+ The SDK uses Axios for HTTP requests, so errors are AxiosError instances:
629
+
630
+ ```typescript
631
+ import axios from "axios";
632
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
633
+
634
+ const chatService = getEthoraSDKService();
635
+
636
+ async function createChatRoomSafely(workspaceId: string) {
637
+ try {
638
+ return await chatService.createChatRoom(workspaceId);
639
+ } catch (error) {
640
+ if (axios.isAxiosError(error)) {
641
+ const status = error.response?.status;
642
+ const errorData = error.response?.data;
643
+
644
+ // Handle specific error cases
645
+ if (status === 422) {
646
+ // Validation error
647
+ console.error("Validation error:", errorData);
648
+ } else if (status === 401) {
649
+ // Authentication error
650
+ console.error("Authentication failed - check your credentials");
651
+ } else if (status === 404) {
652
+ // Resource not found
653
+ console.error("Resource not found");
654
+ } else {
655
+ // Other HTTP errors
656
+ console.error(`HTTP error ${status}:`, errorData);
657
+ }
658
+ } else {
659
+ // Non-HTTP errors
660
+ console.error("Unexpected error:", error);
661
+ }
662
+ throw error;
663
+ }
664
+ }
665
+ ```
666
+
667
+ ### Graceful Error Handling for Idempotent Operations
668
+
669
+ Some operations are idempotent and can be safely retried:
670
+
671
+ ```typescript
672
+ async function ensureChatRoomExists(workspaceId: string) {
673
+ const chatService = getEthoraSDKService();
674
+
675
+ try {
676
+ await chatService.createChatRoom(workspaceId);
677
+ } catch (error) {
678
+ if (axios.isAxiosError(error)) {
679
+ const errorData = error.response?.data;
680
+ const errorMessage =
681
+ typeof errorData === "object" && errorData !== null
682
+ ? (errorData as { error?: string }).error || ""
683
+ : String(errorData || "");
684
+
685
+ // If room already exists, that's okay
686
+ if (
687
+ error.response?.status === 422 &&
688
+ (errorMessage.includes("already exist") ||
689
+ errorMessage.includes("already exists"))
690
+ ) {
691
+ console.log("Chat room already exists, continuing...");
692
+ return; // Success - room exists
693
+ }
694
+ }
695
+ // Re-throw if it's a different error
696
+ throw error;
697
+ }
698
+ }
699
+ ```
700
+
701
+ ## Best Practices
702
+
703
+ ### 1. Use Singleton Pattern
704
+
705
+ The SDK provides a singleton instance. Reuse it rather than creating multiple instances:
706
+
707
+ ```typescript
708
+ // Good
709
+ const chatService = getEthoraSDKService();
710
+
711
+ // Avoid
712
+ const chatService1 = getEthoraSDKService();
713
+ const chatService2 = getEthoraSDKService(); // Unnecessary
714
+ ```
715
+
716
+ ### 2. Centralize Chat Service
717
+
718
+ Create a service wrapper in your application:
719
+
720
+ ```typescript
721
+ // services/chatService.ts
722
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
723
+ import type { ChatRepository } from "@ethora/sdk-backend";
724
+
725
+ class ChatServiceWrapper {
726
+ private service: ChatRepository;
727
+
728
+ constructor() {
729
+ this.service = getEthoraSDKService();
730
+ }
731
+
732
+ async setupWorkspace(workspaceId: string, userIds: string[]) {
733
+ // Your custom logic here
734
+ await this.service.createChatRoom(workspaceId);
735
+ // ... more setup logic
736
+ }
737
+
738
+ // Expose other methods as needed
739
+ getService() {
740
+ return this.service;
741
+ }
742
+ }
743
+
744
+ export default new ChatServiceWrapper();
745
+ ```
746
+
747
+ ### 3. Environment Variable Validation
748
+
749
+ Validate environment variables on application startup:
750
+
751
+ ```typescript
752
+ // config/validateEnv.ts
753
+ function validateEthoraConfig() {
754
+ const required = [
755
+ "ETHORA_CHAT_API_URL",
756
+ "ETHORA_CHAT_APP_ID",
757
+ "ETHORA_CHAT_APP_SECRET",
758
+ ];
759
+
760
+ const missing = required.filter((key) => !process.env[key]);
761
+
762
+ if (missing.length > 0) {
763
+ throw new Error(
764
+ `Missing required Ethora environment variables: ${missing.join(", ")}`
765
+ );
766
+ }
767
+ }
768
+
769
+ // Call on startup
770
+ validateEthoraConfig();
771
+ ```
772
+
773
+ ### 4. Logging Integration
774
+
775
+ Integrate with your existing logging system:
776
+
777
+ ```typescript
778
+ import { getEthoraSDKService } from "@ethora/sdk-backend";
779
+ import { logger } from "./utils/logger"; // Your logger
780
+
781
+ const chatService = getEthoraSDKService();
782
+
783
+ async function createChatRoomWithLogging(workspaceId: string) {
784
+ logger.info(`Creating chat room for workspace: ${workspaceId}`);
785
+ try {
786
+ const result = await chatService.createChatRoom(workspaceId);
787
+ logger.info(`Chat room created successfully: ${workspaceId}`);
788
+ return result;
789
+ } catch (error) {
790
+ logger.error(`Failed to create chat room: ${workspaceId}`, error);
791
+ throw error;
792
+ }
793
+ }
794
+ ```
795
+
796
+ ### 5. Type Safety
797
+
798
+ Use TypeScript types from the SDK:
799
+
800
+ ```typescript
801
+ import type { UUID, ApiResponse } from "@ethora/sdk-backend";
802
+
803
+ async function createUserTyped(
804
+ userId: UUID,
805
+ userData: {
806
+ firstName: string;
807
+ lastName: string;
808
+ email: string;
809
+ }
810
+ ): Promise<ApiResponse> {
811
+ const chatService = getEthoraSDKService();
812
+ return await chatService.createUser(userId, userData);
813
+ }
814
+ ```
815
+
816
+ ## Troubleshooting
817
+
818
+ ### Issue: "Missing required environment variables"
819
+
820
+ **Solution:** Ensure all required environment variables are set:
821
+
822
+ ```bash
823
+ ETHORA_CHAT_API_URL=https://api.ethoradev.com
824
+ ETHORA_CHAT_APP_ID=your_app_id
825
+ ETHORA_CHAT_APP_SECRET=your_app_secret
826
+ ```
827
+
828
+ ### Issue: "Authentication failed" (401 errors)
829
+
830
+ **Solution:** Verify your `ETHORA_CHAT_APP_SECRET` is correct and matches your app ID.
831
+
832
+ ### Issue: "User already exists" errors
833
+
834
+ **Solution:** Handle idempotent operations gracefully:
835
+
836
+ ```typescript
837
+ try {
838
+ await chatService.createUser(userId);
839
+ } catch (error) {
840
+ if (axios.isAxiosError(error) && error.response?.status === 422) {
841
+ // User already exists, continue
842
+ console.log("User already exists");
843
+ } else {
844
+ throw error;
845
+ }
846
+ }
847
+ ```
848
+
849
+ ### Issue: "Chat room not found" during deletion
850
+
851
+ **Solution:** The SDK handles this gracefully. The `deleteChatRoom` method returns `{ ok: false, reason: "Chat room not found" }` if the room doesn't exist, which is safe to ignore.
852
+
853
+ ### Issue: TypeScript compilation errors
854
+
855
+ **Solution:** Ensure you're using TypeScript 5.0+ and have proper type definitions:
856
+
857
+ ```bash
858
+ npm install --save-dev typescript@^5.0.0
859
+ ```
860
+
861
+ ## Next Steps
862
+
863
+ - Review the [API Reference](../README.md#api-reference) for detailed method documentation
864
+ - Check out the [Examples](../examples/) directory for complete integration examples
865
+ - See the [Healthcare Insurance Demo](../examples/healthcare-insurance/) for a real-world use case
866
+
867
+ ## Support
868
+
869
+ For issues, questions, or contributions, please refer to the main [README.md](../README.md) file.
870
+
871
+ ### TypeScript Configuration
872
+
873
+ The project uses strict TypeScript settings. See `tsconfig.json` for details.
874
+
875
+ ## Contributing
876
+
877
+ 1. Fork the repository
878
+ 2. Create a feature branch
879
+ 3. Make your changes
880
+ 4. Add tests if applicable
881
+ 5. Submit a pull request
882
+
883
+ ## License
884
+
885
+ Apache 2.0
886
+
887
+ ## Support
888
+
889
+ For issues and questions, please open an issue on the GitHub repository.