
Research
/Security News
Popular npm Packages in the keyv and Cacheable Namespaces Compromised in Active Supply Chain Attack
Popular npm packages keyv and cacheable compromised.
@ethora/sdk-backend
Advanced tools
This guide will walk you through integrating the Ethora SDK into your existing Node.js backend application.
ETHORA_CHAT_API_URLETHORA_CHAT_APP_IDETHORA_CHAT_APP_SECRETETHORA_CHAT_BOT_JID (optional, for chatbot features)Ethora exposes Swagger UI from every running backend instance at:
https://api.ethoradev.com/api-docs/https://api.<your-domain>/api-docs/If you are running a separate staging instance, the same pattern applies (e.g. https://api.asterotoken.com/api-docs/).
npm install @ethora/sdk-backend
# or
yarn add @ethora/sdk-backend
# or
pnpm add @ethora/sdk-backend
The package includes TypeScript definitions, so no additional @types package is needed.
Add the following environment variables to your .env file or your environment configuration:
# Required
ETHORA_CHAT_API_URL=https://api.ethoradev.com
ETHORA_CHAT_APP_ID=your_app_id_here
ETHORA_CHAT_APP_SECRET=your_app_secret_here
# Optional (for chatbot features)
ETHORA_CHAT_BOT_JID=your_bot_jid@domain.com
If you're using a .env file, ensure you have dotenv installed and configured:
npm install dotenv
In your main application file (e.g., app.js, server.js, or index.ts):
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
import { getEthoraSDKService } from '@ethora/sdk-backend';
You can initialize the service in several ways:
// services/chatService.ts
import { getEthoraSDKService } from '@ethora/sdk-backend';
// Get the singleton instance
const chatService = getEthoraSDKService();
export default chatService;
// In your route handler or service
import { getEthoraSDKService } from '@ethora/sdk-backend';
const chatService = getEthoraSDKService();
// chat.service.ts
import { Injectable } from '@nestjs/common';
import { getEthoraSDKService } from '@ethora/sdk-backend';
@Injectable()
export class ChatService {
private readonly ethoraService = getEthoraSDKService();
// Your methods here
}
// routes/chat.ts
import express, { Request, Response } from 'express';
import { getEthoraSDKService } from '@ethora/sdk-backend';
import type {
ChatRepository,
ApiResponse,
CreateChatRoomRequest,
CreateUserData,
UpdateUserData,
GetUsersQueryParams,
GetUserChatsQueryParams,
UUID
} from '@ethora/sdk-backend';
import axios from 'axios';
const router = express.Router();
const chatService: ChatRepository = getEthoraSDKService();
// Create a chat room for a workspace
router.post(
'/workspaces/:workspaceId/chat',
async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
const roomData: Partial<CreateChatRoomRequest> = req.body;
const response: ApiResponse = await chatService.createChatRoom(workspaceId, {
title: roomData.title || `Chat Room ${workspaceId}`,
uuid: workspaceId,
type: roomData.type || 'group',
...roomData,
});
res.json({ success: true, data: response });
} catch (error) {
if (axios.isAxiosError(error)) {
res.status(error.response?.status || 500).json({
error: 'Failed to create chat room',
details: error.response?.data,
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
},
);
// Create a user
router.post('/users/:userId', async (req: Request, res: Response) => {
try {
const { userId } = req.params;
const userData: CreateUserData = req.body;
const response: ApiResponse = await chatService.createUser(userId, userData);
res.json({ success: true, data: response });
} catch (error) {
if (axios.isAxiosError(error)) {
res.status(error.response?.status || 500).json({
error: 'Failed to create user',
details: error.response?.data,
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// Grant user access to chat room
router.post(
'/workspaces/:workspaceId/chat/users/:userId',
async (req: Request, res: Response) => {
try {
const { workspaceId, userId } = req.params;
await chatService.grantUserAccessToChatRoom(workspaceId, userId);
res.json({ success: true, message: 'Access granted' });
} catch (error) {
if (axios.isAxiosError(error)) {
res.status(error.response?.status || 500).json({
error: 'Failed to grant access',
details: error.response?.data,
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
},
);
// Remove user access from chat room
router.delete(
'/workspaces/:workspaceId/chat/users/:userId',
async (req: Request, res: Response) => {
try {
const { workspaceId, userId } = req.params;
await chatService.removeUserAccessFromChatRoom(workspaceId, userId);
res.json({ success: true, message: 'Access removed' });
} catch (error) {
if (axios.isAxiosError(error)) {
res.status(error.response?.status || 500).json({
error: 'Failed to remove access',
details: error.response?.data,
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
},
);
// Generate client JWT token
router.get('/users/:userId/chat-token', (req: Request, res: Response) => {
try {
const { userId } = req.params;
const token = chatService.createChatUserJwtToken(userId);
res.json({ token });
} catch (error) {
res.status(500).json({ error: 'Failed to generate token' });
}
});
// Get users
router.get('/users', async (req: Request, res: Response) => {
try {
const { chatName, xmppUsername } = req.query;
const params: GetUsersQueryParams = {};
if (chatName) params.chatName = String(chatName);
if (xmppUsername) params.xmppUsername = String(xmppUsername);
const response: ApiResponse = await chatService.getUsers(
Object.keys(params).length > 0 ? params : undefined,
);
res.json({ success: true, data: response });
} catch (error) {
if (axios.isAxiosError(error)) {
res.status(error.response?.status || 500).json({
error: 'Failed to get users',
details: error.response?.data,
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// Update users (batch)
router.patch('/users', async (req: Request, res: Response) => {
try {
const { users } = req.body as { users: UpdateUserData[] };
if (!Array.isArray(users) || users.length === 0) {
return res.status(400).json({ error: 'users must be a non-empty array' });
}
const response: ApiResponse = await chatService.updateUsers(users);
res.json({ success: true, data: response });
} catch (error) {
if (axios.isAxiosError(error)) {
res.status(error.response?.status || 500).json({
error: 'Failed to update users',
details: error.response?.data,
});
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
// Update chat room
router.patch(
'/workspaces/:workspaceId/chat',
async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
const updateData: { title?: string; description?: string } = req.body;
const response: ApiResponse = await chatService.updateChatRoom(workspaceId, updateData);
res.json({ success: true, data: response });
} catch (error) {
res.status(500).json({ error: 'Failed to update chat room' });
}
},
);
// Get user chats
router.get('/users/:userId/chats', async (req: Request, res: Response) => {
try {
const { userId } = req.params;
const query: GetUserChatsQueryParams = req.query as unknown as GetUserChatsQueryParams;
const response: ApiResponse = await chatService.getUserChats(userId, query);
res.json({ success: true, data: response });
} catch (error) {
res.status(500).json({ error: 'Failed to get user chats' });
}
});
export default router;
// chat/chat.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { getEthoraSDKService } from '@ethora/sdk-backend';
import axios from 'axios';
@Injectable()
export class ChatService {
private readonly ethoraService: ChatRepository = getEthoraSDKService();
async createChatRoom(workspaceId: string, roomData?: Partial<CreateChatRoomRequest>): Promise<ApiResponse> {
try {
return await this.ethoraService.createChatRoom(workspaceId, roomData);
} catch (error) {
if (axios.isAxiosError(error)) {
throw new HttpException(
{
message: 'Failed to create chat room',
details: error.response?.data,
},
error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
throw error;
}
}
async createUser(userId: string, userData?: CreateUserData): Promise<ApiResponse> {
try {
return await this.ethoraService.createUser(userId, userData);
} catch (error) {
if (axios.isAxiosError(error)) {
throw new HttpException(
{
message: 'Failed to create user',
details: error.response?.data,
},
error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
throw error;
}
}
generateClientToken(userId: string): string {
return this.ethoraService.createChatUserJwtToken(userId);
}
}
// chat/chat.controller.ts
import { Controller, Post, Get, Param, Body } from '@nestjs/common';
import { ChatService } from './chat.service';
@Controller('chat')
export class ChatController {
constructor(private readonly chatService: ChatService) {}
@Post('workspaces/:workspaceId/rooms')
async createChatRoom(
@Param('workspaceId') workspaceId: string,
@Body() roomData: Partial<CreateChatRoomRequest>,
): Promise<ApiResponse> {
return this.chatService.createChatRoom(workspaceId, roomData);
}
@Post('users/:userId')
async createUser(
@Param('userId') userId: string,
@Body() userData: CreateUserData
): Promise<ApiResponse> {
return this.chatService.createUser(userId, userData);
}
@Get('users/:userId/token')
getClientToken(@Param('userId') userId: string) {
return { token: this.chatService.generateClientToken(userId) };
}
}
// routes/chat.ts
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { getEthoraSDKService } from '@ethora/sdk-backend';
const chatService = getEthoraSDKService();
export async function chatRoutes(fastify: FastifyInstance) {
// Create chat room
fastify.post(
'/workspaces/:workspaceId/chat',
async (request: FastifyRequest, reply: FastifyReply): Promise<ApiResponse | void> => {
const { workspaceId } = request.params as { workspaceId: string };
const roomData = request.body as Partial<CreateChatRoomRequest>;
try {
const response: ApiResponse = await chatService.createChatRoom(
workspaceId,
roomData,
);
return { success: true, data: response };
} catch (error) {
reply.code(500).send({ error: 'Failed to create chat room' });
}
},
);
// Generate client token
fastify.get(
'/users/:userId/chat-token',
async (request: FastifyRequest, reply: FastifyReply) => {
const { userId } = request.params as { userId: string };
const token = chatService.createChatUserJwtToken(userId);
return { token };
},
);
}
When creating a new workspace, set up the chat room and initial users:
async function setupWorkspaceChat(
workspaceId: string,
userIds: string[],
adminUserId: string,
): Promise<{ success: boolean }> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// 1. Create chat room
await chatService.createChatRoom(workspaceId, {
title: `Workspace ${workspaceId}`,
uuid: workspaceId,
type: 'group',
});
// 2. Create users (if they don't exist)
for (const userId of userIds) {
try {
await chatService.createUser(userId, {
firstName: 'User',
lastName: 'Name',
});
} catch (error) {
// User might already exist, continue
console.warn(`User ${userId} might already exist`);
}
}
// 3. Grant access to all users
await chatService.grantUserAccessToChatRoom(workspaceId, userIds);
// 4. Grant chatbot access (if configured)
try {
await chatService.grantChatbotAccessToChatRoom(workspaceId);
} catch (error) {
console.warn('Chatbot access not configured or failed');
}
return { success: true };
} catch (error) {
console.error('Failed to setup workspace chat:', error);
throw error;
}
}
When a new user joins your platform:
async function onboardNewUser(
userId: string,
userData: { firstName: string; lastName: string; email: string },
): Promise<{ success: boolean; chatToken: string }> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// Create user in chat service
await chatService.createUser(userId, {
firstName: userData.firstName,
lastName: userData.lastName,
email: userData.email,
displayName: `${userData.firstName} ${userData.lastName}`,
});
// Generate client token for frontend
const clientToken: string = chatService.createChatUserJwtToken(userId);
return {
success: true,
chatToken: clientToken,
};
} catch (error) {
console.error('Failed to onboard user:', error);
throw error;
}
}
When adding a user to an existing workspace:
async function addUserToWorkspace(
workspaceId: string,
userId: string,
): Promise<{ success: boolean }> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// Ensure user exists
try {
await chatService.createUser(userId);
} catch (error) {
// User might already exist, continue
}
// Grant access to workspace chat room
await chatService.grantUserAccessToChatRoom(workspaceId, userId);
return { success: true };
} catch (error) {
console.error('Failed to add user to workspace:', error);
throw error;
}
}
When removing a user from a workspace:
async function removeUserFromWorkspace(
workspaceId: string,
userId: string,
): Promise<{ success: boolean }> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// Remove access from workspace chat room
await chatService.removeUserAccessFromChatRoom(workspaceId, userId);
return { success: true };
} catch (error) {
console.error('Failed to remove user from workspace:', error);
throw error;
}
}
// Remove multiple users at once
async function removeMultipleUsersFromWorkspace(
workspaceId: string,
userIds: string[],
): Promise<{ success: boolean }> {
const chatService: ChatRepository = getEthoraSDKService();
try {
await chatService.removeUserAccessFromChatRoom(workspaceId, userIds);
return { success: true };
} catch (error) {
console.error('Failed to remove users from workspace:', error);
throw error;
}
}
When deleting a workspace:
async function cleanupWorkspaceChat(
workspaceId: string,
userIds: string[],
): Promise<{ success: boolean }> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// Delete chat room (handles non-existent gracefully)
await chatService.deleteChatRoom(workspaceId);
// Optionally delete users (if they're no longer needed)
if (userIds.length > 0) {
try {
await chatService.deleteUsers(userIds);
} catch (error) {
console.warn('Some users might not exist:', error);
}
}
return { success: true };
} catch (error) {
console.error('Failed to cleanup workspace chat:', error);
throw error;
}
}
Retrieve users from the chat service:
async function getUsersExample(): Promise<{
allUsers: ApiResponse;
groupChatUsers: ApiResponse;
oneOnOneUsers: ApiResponse
}> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// Get all users
const allUsers: ApiResponse = await chatService.getUsers();
console.log(`Total users: ${allUsers.results?.length || 0}`);
// Get users by chat name (group chat)
const groupChatUsers: ApiResponse = await chatService.getUsers({
chatName: 'appId_workspaceId',
});
// Get users by chat name (1-on-1 chat)
const oneOnOneUsers: ApiResponse = await chatService.getUsers({
chatName: 'userA-userB',
});
return { allUsers, groupChatUsers, oneOnOneUsers };
} catch (error) {
console.error('Failed to get users:', error);
throw error;
}
}
Update multiple users at once:
async function updateUsersExample(): Promise<ApiResponse> {
const chatService: ChatRepository = getEthoraSDKService();
try {
// Update multiple users (1-100 users per request)
const response: ApiResponse = await chatService.updateUsers([
{
xmppUsername: 'appId_user1',
firstName: 'John',
lastName: 'Doe',
}
]);
return response;
} catch (error) {
console.error('Failed to update users:', error);
throw error;
}
}
Update room title or description:
async function updateRoomExample(): Promise<ApiResponse> {
const chatService: ChatRepository = getEthoraSDKService();
try {
const response: ApiResponse = await chatService.updateChatRoom('workspaceId', {
title: 'New Room Title',
description: 'New Description',
});
return response;
} catch (error) {
console.error('Failed to update room:', error);
throw error;
}
}
Retrieve all rooms the user has access to:
async function getUserChatsExample(): Promise<ApiResponse> {
const chatService: ChatRepository = getEthoraSDKService();
try {
const query: GetUserChatsQueryParams = {
limit: 20,
includeMembers: true
};
const response: ApiResponse = await chatService.getUserChats('userId', query);
return response;
} catch (error) {
console.error('Failed to get user chats:', error);
throw error;
}
}
createUser(userId: UUID, userData?: CreateUserData): Promise<ApiResponse>Creates a user in the chat service using the /v2/users/batch endpoint.
Interface: CreateUserData
interface CreateUserData {
email: string; // string: User's email address
firstName: string; // string: User's first name
lastName: string; // string: User's last name (min 2 chars)
password?: string; // string (optional): User's password
displayName?: string; // string (optional): Full display name
}
Example Request:
await sdk.createUser("user-uuid-123", {
email: "john@example.com",
firstName: "John",
lastName: "Doe"
});
Note: The API requires lastName to be at least 2 characters. If not provided or too short, defaults to "User".
createChatRoom(chatId: UUID, roomData?: CreateChatRoomRequest): Promise<ApiResponse>Creates a chat room using the /v2/chats endpoint.
Interface: CreateChatRoomRequest
interface CreateChatRoomRequest {
title: string; // string: The display name of the chat room
uuid: string; // string: The workspace/chat identifier
type: string; // string: The room type (e.g., "group")
}
Example Request:
const roomData: CreateChatRoomRequest = {
title: "Engineering",
uuid: "room-abc-123",
type: "group"
};
await sdk.createChatRoom("room-abc-123", roomData);
grantUserAccessToChatRoom(chatId: UUID, userId: UUID | UUID[]): Promise<ApiResponse>Grants user(s) access to a chat room using the /v2/chats/users-access endpoint.
Example Request:
// Single user
await sdk.grantUserAccessToChatRoom("workspace-123", "user-uuid-456");
// Multiple users
await sdk.grantUserAccessToChatRoom("workspace-123", ["user-1", "user-2"]);
Note: User IDs are automatically prefixed with {appId}_ if they don't already have the prefix.
removeUserAccessFromChatRoom(chatId: UUID, userId: UUID | UUID[]): Promise<ApiResponse>Removes user(s) access from a chat room using the /v2/chats/users-access DELETE endpoint.
Example Request:
await sdk.removeUserAccessFromChatRoom("workspace-123", "user-456");
Note: User IDs are automatically prefixed with {appId}_ if they don't already have the prefix.
grantChatbotAccessToChatRoom(chatId: UUID): Promise<ApiResponse>Grants chatbot access to a chat room.
Parameters:
chatId (UUID): The unique identifier of the chat/workspaceReturns: Promise resolving to the API response
Requires: ETHORA_CHAT_BOT_JID environment variable to be set
getUsers(params?: GetUsersQueryParams): Promise<ApiResponse>Retrieves users from the chat service using the /v2/chats/users endpoint.
Parameters:
params (GetUsersQueryParams, optional): Query parameters
chatName (string): Filter by chat name
appId_chatId formatxmppUsernameA-xmppUsernameB formatxmppUsername (string): Filter by specific XMPP usernameQuery Modes:
chatName: Returns all users of the specified chatxmppUsername: Returns a specific userReturns: Promise resolving to the API response with users array
updateUsers(users: UpdateUserData[]): Promise<ApiResponse>Updates multiple users at once using the /v2/chats/users PATCH endpoint.
Interface: UpdateUserData
interface UpdateUserData {
xmppUsername: string; // string: Required (format: {appId}_{userId})
firstName?: string; // string (optional): New first name
lastName?: string; // string (optional): New last name
username?: string; // string (optional): New username
profileImage?: string; // string (optional): URL to profile image
description?: string; // string (optional): User bio/description
email?: string; // string (optional): New email address
}
Example Request:
await sdk.updateUsers([
{ xmppUsername: "appId_user1", firstName: "NewName" }
]);
getUserChats(userId: UUID, params?: GetUserChatsQueryParams): Promise<ApiResponse>Retrieves all rooms the user has access to.
Interface: GetUserChatsQueryParams
interface GetUserChatsQueryParams {
limit?: number; // number (optional): Pagination limit
offset?: number; // number (optional): Pagination offset
includeMembers?: boolean; // boolean (optional): Whether to return member lists
}
Example Request:
const query: GetUserChatsQueryParams = { limit: 50, includeMembers: true };
await sdk.getUserChats("user-uuid-123", query);
updateChatRoom(chatId: UUID, updateData: { title?: string; description?: string }): Promise<ApiResponse>Updates the metadata for a specific chat room.
Example Request:
await sdk.updateChatRoom("workspace-123", {
title: "New Team Title",
description: "Updated room description"
});
getUsers(params?: GetUsersQueryParams): Promise<ApiResponse>Retrieves users from the chat service using the /v2/chats/users endpoint.
Interface: GetUsersQueryParams
interface GetUsersQueryParams {
chatName?: string; // string (optional): Filter by roomId (appId_roomId)
xmppUsername?: string; // string (optional): Filter by specific JID
}
Example Request:
await sdk.getUsers({ chatName: "appId_workspace-123" });
Limits: 1-100 users per request
deleteUsers(userIds: UUID[]): Promise<ApiResponse>Deletes users from the chat service using the /v1/users/batch endpoint.
Example Request:
await sdk.deleteUsers(["user-id-1", "user-id-2"]);
Note: Gracefully handles non-existent users (422 status with "not found").
deleteChatRoom(chatId: UUID): Promise<ApiResponse>Deletes a chat room using the /v1/chats endpoint.
Example Request:
await sdk.deleteChatRoom("workspace-uuid-123");
Note: Gracefully handles non-existent rooms (422 status with "not found").
createChatName(chatId: UUID, full?: boolean): stringGenerates a chat room JID from a chat ID.
Parameters:
chatId (UUID): The unique identifier of the chatfull (boolean, optional): Whether to include the full JID domain (default: true)Returns: The JID string
{appId}_{chatId}@conference.xmpp.ethoradev.com{appId}_{chatId}createChatUserJwtToken(userId: UUID): stringCreates a client-side JWT token for user authentication.
Parameters:
userId (UUID): The unique identifier of the userReturns: The encoded JWT token for client-side authentication
The SDK uses Axios for HTTP requests, so errors are AxiosError instances:
import axios from 'axios';
import { getEthoraSDKService } from '@ethora/sdk-backend';
const chatService = getEthoraSDKService();
async function createChatRoomSafely(workspaceId: string) {
try {
return await chatService.createChatRoom(workspaceId);
} catch (error) {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const errorData = error.response?.data;
// Handle specific error cases
if (status === 422) {
// Validation error
console.error('Validation error:', errorData);
} else if (status === 401) {
// Authentication error
console.error('Authentication failed - check your credentials');
} else if (status === 404) {
// Resource not found
console.error('Resource not found');
} else {
// Other HTTP errors
console.error(`HTTP error ${status}:`, errorData);
}
} else {
// Non-HTTP errors
console.error('Unexpected error:', error);
}
throw error;
}
}
Some operations are idempotent and can be safely retried:
async function ensureChatRoomExists(workspaceId: string) {
const chatService = getEthoraSDKService();
try {
await chatService.createChatRoom(workspaceId);
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data;
const errorMessage =
typeof errorData === 'object' && errorData !== null
? (errorData as { error?: string }).error || ''
: String(errorData || '');
// If room already exists, that's okay
if (
error.response?.status === 422 &&
(errorMessage.includes('already exist') ||
errorMessage.includes('already exists'))
) {
console.log('Chat room already exists, continuing...');
return; // Success - room exists
}
}
// Re-throw if it's a different error
throw error;
}
}
The SDK provides a singleton instance. Reuse it rather than creating multiple instances:
// Good
const chatService = getEthoraSDKService();
// Avoid
const chatService1 = getEthoraSDKService();
const chatService2 = getEthoraSDKService(); // Unnecessary
Create a service wrapper in your application:
// services/chatService.ts
import { getEthoraSDKService } from '@ethora/sdk-backend';
import type { ChatRepository } from '@ethora/sdk-backend';
class ChatServiceWrapper {
private service: ChatRepository;
constructor() {
this.service = getEthoraSDKService();
}
async setupWorkspace(workspaceId: string, userIds: string[]) {
// Your custom logic here
await this.service.createChatRoom(workspaceId);
// ... more setup logic
}
// Expose other methods as needed
getService() {
return this.service;
}
}
export default new ChatServiceWrapper();
Validate environment variables on application startup:
// config/validateEnv.ts
function validateEthoraConfig() {
const required = [
'ETHORA_CHAT_API_URL',
'ETHORA_CHAT_APP_ID',
'ETHORA_CHAT_APP_SECRET',
];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required Ethora environment variables: ${missing.join(', ')}`,
);
}
}
// Call on startup
validateEthoraConfig();
Integrate with your existing logging system:
import { getEthoraSDKService } from '@ethora/sdk-backend';
import { logger } from './utils/logger'; // Your logger
const chatService = getEthoraSDKService();
async function createChatRoomWithLogging(workspaceId: string) {
logger.info(`Creating chat room for workspace: ${workspaceId}`);
try {
const result = await chatService.createChatRoom(workspaceId);
logger.info(`Chat room created successfully: ${workspaceId}`);
return result;
} catch (error) {
logger.error(`Failed to create chat room: ${workspaceId}`, error);
throw error;
}
}
Use TypeScript types from the SDK:
import type { UUID, ApiResponse } from '@ethora/sdk-backend';
async function createUserTyped(
userId: UUID,
userData: {
firstName: string;
lastName: string;
email: string;
},
): Promise<ApiResponse> {
const chatService = getEthoraSDKService();
return await chatService.createUser(userId, userData);
}
Solution: Ensure all required environment variables are set:
ETHORA_CHAT_API_URL=https://api.ethoradev.com
ETHORA_CHAT_APP_ID=your_app_id
ETHORA_CHAT_APP_SECRET=your_app_secret
Solution: Verify your ETHORA_CHAT_APP_SECRET is correct and matches your app ID.
Solution: Handle idempotent operations gracefully:
try {
await chatService.createUser(userId);
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 422) {
// User already exists, continue
console.log('User already exists');
} else {
throw error;
}
}
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.
Solution: Ensure you're using TypeScript 5.0+ and have proper type definitions:
npm install --save-dev typescript@^5.0.0
For issues, questions, or contributions, please refer to the main README.md file.
The project uses strict TypeScript settings. See tsconfig.json for details.
Apache 2.0
For issues and questions, please open an issue on the GitHub repository.
To run tests with logs run from root: TEST_LOG_FILE=logs/chat-repo.log npm test
FAQs
TypeScript SDK for integrating with Ethora chat service backend API
The npm package @ethora/sdk-backend receives a total of 6 weekly downloads. As such, @ethora/sdk-backend popularity was classified as not popular.
We found that @ethora/sdk-backend demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Popular npm packages keyv and cacheable compromised.

Security News
A misconfiguration gave three Anthropic models internet access, and one, believing it was in a simulation, shipped a credential-stealing package to PyPI.

Security News
/Company News
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.