@ethora/sdk-backend 25.12.15

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