refactor Notifications
This commit is contained in:
parent
05ec62595d
commit
b926597a48
118
app/api/rocket-chat/user-token/route.ts
Normal file
118
app/api/rocket-chat/user-token/route.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getServerSession } from 'next-auth';
|
||||||
|
import { authOptions } from "@/app/api/auth/options";
|
||||||
|
import { logger } from '@/lib/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get RocketChat user token for WebSocket connection
|
||||||
|
* This endpoint returns the user's auth token and userId for real-time connections
|
||||||
|
*/
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.email) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Not authenticated" },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0];
|
||||||
|
if (!baseUrl) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] Failed to get Rocket.Chat base URL');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Server configuration error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use admin token to authenticate
|
||||||
|
const adminHeaders = {
|
||||||
|
'X-Auth-Token': process.env.ROCKET_CHAT_TOKEN!,
|
||||||
|
'X-User-Id': process.env.ROCKET_CHAT_USER_ID!,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get username from email
|
||||||
|
const username = session.user.email.split('@')[0];
|
||||||
|
if (!username) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] No username found in session email');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid user' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all users to find the current user
|
||||||
|
const usersResponse = await fetch(`${baseUrl}/api/v1/users.list`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: adminHeaders
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!usersResponse.ok) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] Failed to get users list');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to get user' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersData = await usersResponse.json();
|
||||||
|
const currentUser = usersData.users?.find((u: any) =>
|
||||||
|
u.username?.toLowerCase() === username.toLowerCase() ||
|
||||||
|
u.emails?.some((e: any) => e.address?.toLowerCase() === session.user.email?.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] User not found in RocketChat', { username });
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'User not found' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user token
|
||||||
|
const secret = process.env.ROCKET_CHAT_CREATE_TOKEN_SECRET;
|
||||||
|
if (!secret) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] ROCKET_CHAT_CREATE_TOKEN_SECRET not configured');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Server configuration error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTokenResponse = await fetch(`${baseUrl}/api/v1/users.createToken`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: adminHeaders,
|
||||||
|
body: JSON.stringify({
|
||||||
|
userId: currentUser._id,
|
||||||
|
secret: secret
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createTokenResponse.ok) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] Failed to create user token');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to create token' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenData = await createTokenResponse.json();
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
userId: currentUser._id,
|
||||||
|
authToken: tokenData.data.authToken,
|
||||||
|
username: currentUser.username,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error('[ROCKET_CHAT_USER_TOKEN] Error', {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Internal server error", message: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { AuthCheck } from "@/components/auth/auth-check";
|
|||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { useBackgroundImage } from "@/components/background-switcher";
|
import { useBackgroundImage } from "@/components/background-switcher";
|
||||||
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
|
import { clearAuthCookies, clearKeycloakCookies } from "@/lib/session";
|
||||||
|
import { useRocketChatCalls } from "@/hooks/use-rocketchat-calls";
|
||||||
|
|
||||||
interface LayoutWrapperProps {
|
interface LayoutWrapperProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -19,6 +20,9 @@ export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: Layou
|
|||||||
const { currentBackground, changeBackground } = useBackgroundImage();
|
const { currentBackground, changeBackground } = useBackgroundImage();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
// Listen for incoming RocketChat calls
|
||||||
|
useRocketChatCalls();
|
||||||
|
|
||||||
// Global listener for logout messages from iframe applications
|
// Global listener for logout messages from iframe applications
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSignInPage) return; // Don't listen on signin page
|
if (isSignInPage) return; // Don't listen on signin page
|
||||||
|
|||||||
150
hooks/use-rocketchat-calls.ts
Normal file
150
hooks/use-rocketchat-calls.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { useSession } from 'next-auth/react';
|
||||||
|
import { RocketChatCallListener, CallEvent } from '@/lib/services/rocketchat-call-listener';
|
||||||
|
import { useWidgetNotification } from './use-widget-notification';
|
||||||
|
import { logger } from '@/lib/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to listen for incoming RocketChat calls and trigger notifications
|
||||||
|
*/
|
||||||
|
export function useRocketChatCalls() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const { triggerNotification } = useWidgetNotification();
|
||||||
|
const listenerRef = useRef<RocketChatCallListener | null>(null);
|
||||||
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
||||||
|
const initializedRef = useRef(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get RocketChat credentials for the current user
|
||||||
|
*/
|
||||||
|
const getRocketChatCredentials = useCallback(async () => {
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user token from RocketChat API
|
||||||
|
const response = await fetch('/api/rocket-chat/messages?refresh=true', {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
logger.error('[useRocketChatCalls] Failed to get RocketChat credentials');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract base URL from environment
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_IFRAME_PAROLE_URL?.split('/channel')[0];
|
||||||
|
if (!baseUrl) {
|
||||||
|
logger.error('[useRocketChatCalls] RocketChat base URL not configured');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's RocketChat ID and token
|
||||||
|
// We need to call the API to get the user token
|
||||||
|
const tokenResponse = await fetch('/api/rocket-chat/user-token', {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenResponse.ok) {
|
||||||
|
logger.error('[useRocketChatCalls] Failed to get user token');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
return {
|
||||||
|
userId: tokenData.userId,
|
||||||
|
authToken: tokenData.authToken,
|
||||||
|
baseUrl,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[useRocketChatCalls] Error getting credentials', { error });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [session?.user?.id]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize call listener
|
||||||
|
*/
|
||||||
|
const initializeListener = useCallback(async () => {
|
||||||
|
if (initializedRef.current || listenerRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = await getRocketChatCredentials();
|
||||||
|
if (!credentials) {
|
||||||
|
logger.warn('[useRocketChatCalls] Could not get credentials, skipping initialization');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const listener = RocketChatCallListener.getInstance();
|
||||||
|
const success = await listener.initialize(
|
||||||
|
credentials.userId,
|
||||||
|
credentials.authToken,
|
||||||
|
credentials.baseUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
listenerRef.current = listener;
|
||||||
|
initializedRef.current = true;
|
||||||
|
|
||||||
|
// Subscribe to call events
|
||||||
|
const unsubscribe = listener.onCall((callEvent: CallEvent) => {
|
||||||
|
logger.info('[useRocketChatCalls] Incoming call detected', {
|
||||||
|
from: callEvent.from.username,
|
||||||
|
roomId: callEvent.roomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger notification
|
||||||
|
triggerNotification({
|
||||||
|
source: 'rocketchat',
|
||||||
|
count: 1, // Increment count for call
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: `call-${callEvent.roomId}-${Date.now()}`,
|
||||||
|
title: `Appel entrant de ${callEvent.from.name || callEvent.from.username}`,
|
||||||
|
message: `Appel vidéo/audio dans ${callEvent.roomName || 'chat'}`,
|
||||||
|
link: `/parole?room=${callEvent.roomId}`,
|
||||||
|
timestamp: callEvent.timestamp,
|
||||||
|
metadata: {
|
||||||
|
type: 'call',
|
||||||
|
from: callEvent.from,
|
||||||
|
roomId: callEvent.roomId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
unsubscribeRef.current = unsubscribe;
|
||||||
|
logger.debug('[useRocketChatCalls] Call listener initialized');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[useRocketChatCalls] Error initializing listener', { error });
|
||||||
|
}
|
||||||
|
}, [getRocketChatCredentials, triggerNotification]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup on unmount
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (session?.user?.id) {
|
||||||
|
initializeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (unsubscribeRef.current) {
|
||||||
|
unsubscribeRef.current();
|
||||||
|
unsubscribeRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listenerRef.current) {
|
||||||
|
listenerRef.current.disconnect();
|
||||||
|
listenerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializedRef.current = false;
|
||||||
|
};
|
||||||
|
}, [session?.user?.id, initializeListener]);
|
||||||
|
}
|
||||||
331
lib/services/rocketchat-call-listener.ts
Normal file
331
lib/services/rocketchat-call-listener.ts
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
/**
|
||||||
|
* RocketChat Call Listener Service
|
||||||
|
*
|
||||||
|
* Listens for incoming calls via RocketChat's DDP/WebSocket real-time API
|
||||||
|
* Uses stream-notify-user with webrtc events to detect incoming calls
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from '@/lib/logger';
|
||||||
|
|
||||||
|
export interface CallEvent {
|
||||||
|
type: 'call-incoming' | 'call-answered' | 'call-ended';
|
||||||
|
from: {
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
roomId: string;
|
||||||
|
roomName?: string;
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallEventHandler = (event: CallEvent) => void;
|
||||||
|
|
||||||
|
export class RocketChatCallListener {
|
||||||
|
private static instance: RocketChatCallListener;
|
||||||
|
private ws: WebSocket | null = null;
|
||||||
|
private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private reconnectAttempts = 0;
|
||||||
|
private maxReconnectAttempts = 10;
|
||||||
|
private reconnectDelay = 3000;
|
||||||
|
private isConnecting = false;
|
||||||
|
private isConnected = false;
|
||||||
|
private callHandlers: Set<CallEventHandler> = new Set();
|
||||||
|
private userId: string | null = null;
|
||||||
|
private authToken: string | null = null;
|
||||||
|
private baseUrl: string | null = null;
|
||||||
|
private subscriptionId: string | null = null;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): RocketChatCallListener {
|
||||||
|
if (!RocketChatCallListener.instance) {
|
||||||
|
RocketChatCallListener.instance = new RocketChatCallListener();
|
||||||
|
}
|
||||||
|
return RocketChatCallListener.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the listener with user credentials
|
||||||
|
*/
|
||||||
|
public async initialize(userId: string, authToken: string, baseUrl: string): Promise<boolean> {
|
||||||
|
this.userId = userId;
|
||||||
|
this.authToken = authToken;
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
|
||||||
|
if (this.isConnected || this.isConnecting) {
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Already connected or connecting');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to RocketChat WebSocket
|
||||||
|
*/
|
||||||
|
private async connect(): Promise<boolean> {
|
||||||
|
if (this.isConnecting) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.baseUrl || !this.userId || !this.authToken) {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] Missing credentials for connection');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConnecting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Convert HTTP URL to WebSocket URL
|
||||||
|
const wsUrl = this.baseUrl
|
||||||
|
.replace('http://', 'ws://')
|
||||||
|
.replace('https://', 'wss://')
|
||||||
|
.replace(/\/$/, '') + '/websocket';
|
||||||
|
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Connecting to WebSocket', { wsUrl });
|
||||||
|
|
||||||
|
this.ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
this.ws.onopen = () => {
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] WebSocket connected');
|
||||||
|
this.isConnecting = false;
|
||||||
|
this.isConnected = true;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
this.authenticate();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
|
this.handleMessage(JSON.parse(event.data));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onerror = (error) => {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] WebSocket error', { error });
|
||||||
|
this.isConnecting = false;
|
||||||
|
this.isConnected = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = () => {
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] WebSocket closed');
|
||||||
|
this.isConnected = false;
|
||||||
|
this.isConnecting = false;
|
||||||
|
this.subscriptionId = null;
|
||||||
|
this.attemptReconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] Connection error', {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
this.isConnecting = false;
|
||||||
|
this.attemptReconnect();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate with RocketChat
|
||||||
|
*/
|
||||||
|
private authenticate(): void {
|
||||||
|
if (!this.ws || !this.authToken || !this.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginMessage = {
|
||||||
|
msg: 'method',
|
||||||
|
method: 'login',
|
||||||
|
id: `login-${Date.now()}`,
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
resume: this.authToken,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Sending login message');
|
||||||
|
this.ws.send(JSON.stringify(loginMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to webrtc events for incoming calls
|
||||||
|
*/
|
||||||
|
private subscribeToCalls(): void {
|
||||||
|
if (!this.ws || !this.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscriptionId = `call-sub-${Date.now()}`;
|
||||||
|
|
||||||
|
const subscribeMessage = {
|
||||||
|
msg: 'sub',
|
||||||
|
id: this.subscriptionId,
|
||||||
|
name: 'stream-notify-user',
|
||||||
|
params: [`${this.userId}/webrtc`, false],
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Subscribing to webrtc events', {
|
||||||
|
subscriptionId: this.subscriptionId,
|
||||||
|
userId: this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.send(JSON.stringify(subscribeMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle incoming WebSocket messages
|
||||||
|
*/
|
||||||
|
private handleMessage(message: any): void {
|
||||||
|
// Handle login response
|
||||||
|
if (message.msg === 'result' && message.id?.startsWith('login-')) {
|
||||||
|
if (message.result?.token) {
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Login successful');
|
||||||
|
this.subscribeToCalls();
|
||||||
|
} else {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] Login failed', { message });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle subscription ready
|
||||||
|
if (message.msg === 'ready' && message.subs?.includes(this.subscriptionId)) {
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Subscription ready');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle call events (webrtc)
|
||||||
|
if (message.msg === 'changed' && message.collection === 'stream-notify-user') {
|
||||||
|
const eventName = message.fields?.eventName;
|
||||||
|
const args = message.fields?.args || [];
|
||||||
|
|
||||||
|
if (eventName?.includes('/webrtc') && args.length > 0) {
|
||||||
|
this.handleCallEvent(args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle call event from RocketChat
|
||||||
|
*/
|
||||||
|
private handleCallEvent(eventData: any): void {
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Received call event', { eventData });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the call event data
|
||||||
|
// RocketChat webrtc events typically contain:
|
||||||
|
// - type: 'call', 'ringing', 'answered', 'ended'
|
||||||
|
// - from: user info
|
||||||
|
// - roomId: room identifier
|
||||||
|
const callType = eventData.type || eventData.action;
|
||||||
|
const from = eventData.from || eventData.caller || {};
|
||||||
|
const roomId = eventData.roomId || eventData.rid;
|
||||||
|
|
||||||
|
if (!callType || !roomId) {
|
||||||
|
logger.warn('[ROCKETCHAT_CALL_LISTENER] Invalid call event data', { eventData });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect incoming call
|
||||||
|
if (callType === 'call' || callType === 'ringing' || eventData.action === 'ringing') {
|
||||||
|
const callEvent: CallEvent = {
|
||||||
|
type: 'call-incoming',
|
||||||
|
from: {
|
||||||
|
userId: from._id || from.userId || '',
|
||||||
|
username: from.username || '',
|
||||||
|
name: from.name || from.username,
|
||||||
|
},
|
||||||
|
roomId,
|
||||||
|
roomName: eventData.roomName || from.name,
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info('[ROCKETCHAT_CALL_LISTENER] Incoming call detected', {
|
||||||
|
from: callEvent.from.username,
|
||||||
|
roomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify all handlers
|
||||||
|
this.callHandlers.forEach((handler) => {
|
||||||
|
try {
|
||||||
|
handler(callEvent);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] Error in call handler', { error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] Error handling call event', {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
eventData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a handler for call events
|
||||||
|
*/
|
||||||
|
public onCall(handler: CallEventHandler): () => void {
|
||||||
|
this.callHandlers.add(handler);
|
||||||
|
|
||||||
|
// Return unsubscribe function
|
||||||
|
return () => {
|
||||||
|
this.callHandlers.delete(handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to reconnect
|
||||||
|
*/
|
||||||
|
private attemptReconnect(): void {
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
logger.error('[ROCKETCHAT_CALL_LISTENER] Max reconnect attempts reached');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.reconnectTimeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
const delay = this.reconnectDelay * this.reconnectAttempts;
|
||||||
|
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Scheduling reconnect', {
|
||||||
|
attempt: this.reconnectAttempts,
|
||||||
|
delay,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.reconnectTimeout = setTimeout(() => {
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
this.connect();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect and cleanup
|
||||||
|
*/
|
||||||
|
public disconnect(): void {
|
||||||
|
if (this.reconnectTimeout) {
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConnected = false;
|
||||||
|
this.isConnecting = false;
|
||||||
|
this.subscriptionId = null;
|
||||||
|
this.callHandlers.clear();
|
||||||
|
|
||||||
|
logger.debug('[ROCKETCHAT_CALL_LISTENER] Disconnected');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if connected
|
||||||
|
*/
|
||||||
|
public get connected(): boolean {
|
||||||
|
return this.isConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user