Fix: Address instance deletion, disappearing instances, and update errors

- Fixed instance deletion failures.
- Resolved the issue of instances appearing and disappearing in a loop.
- Addressed errors during instance updates.
- Check for missing information.
This commit is contained in:
gpt-engineer-app[bot] 2025-05-19 18:07:25 +00:00
parent f26e41707a
commit dec494083c
7 changed files with 264 additions and 69 deletions

View File

@ -56,6 +56,7 @@ const CreateInstanceForm = ({ onInstanceCreated }: CreateInstanceFormProps) => {
setLoading(true);
try {
console.log(`Creating instance with name ${instanceName} and number ${phoneNumber}`);
const data = await createWhatsAppInstance(instanceName, phoneNumber);
console.log('API response for create instance:', data);
@ -63,7 +64,7 @@ const CreateInstanceForm = ({ onInstanceCreated }: CreateInstanceFormProps) => {
// Create new instance object with user ID
const newInstance: WhatsAppInstance = {
instanceName,
instanceId: data.instance?.instanceId || instanceName, // Use instanceName as fallback ID
instanceId: instanceName, // Use instanceName as the ID for consistency
phoneNumber,
userId: currentUserId, // Associate with current user
status: data.instance?.status || 'created',

View File

@ -141,6 +141,27 @@ const InstanceCard = ({
);
};
// Determina o estado de conexão a ser exibido
const getConnectionStatus = () => {
// Se o status é 'disconnected', 'error', 'destroyed', ou não está definido, consideramos como desconectado
if (instance.status === 'disconnected' ||
instance.status === 'error' ||
instance.status === 'destroyed' ||
!instance.status) {
return 'closed';
}
// Se o connectionState já está definido como 'open', manter
if (instance.connectionState === 'open') {
return 'open';
}
// Em outros casos, usar o valor atual do connectionState
return instance.connectionState || 'closed';
};
const connectionStatus = getConnectionStatus();
return (
<>
<Card className="h-full">
@ -154,11 +175,16 @@ const InstanceCard = ({
<span>{instance.phoneNumber}</span>
</div>
<div className="flex items-center text-sm">
{instance.connectionState === 'open' ? (
{connectionStatus === 'open' ? (
<Badge variant="outline" className="flex items-center gap-1 text-green-500 border-green-300">
<span className="inline-block w-2 h-2 rounded-full bg-green-500"></span>
Status: Conectado
</Badge>
) : connectionStatus === 'connecting' ? (
<Badge variant="outline" className="flex items-center gap-1 text-orange-500 border-orange-300">
<span className="inline-block w-2 h-2 rounded-full bg-orange-500"></span>
Status: Conectando
</Badge>
) : (
<Badge variant="outline" className="flex items-center gap-1 text-red-500 border-red-300">
<span className="inline-block w-2 h-2 rounded-full bg-red-500"></span>

View File

@ -8,7 +8,7 @@ interface InstanceStatsProps {
const InstanceStats = ({ instances }: InstanceStatsProps) => {
const totalInstances = instances.length;
const connectedInstances = instances.filter(i => i.connectionState === 'open').length;
const disconnectedInstances = instances.filter(i => i.connectionState === 'closed').length;
const disconnectedInstances = instances.filter(i => i.connectionState === 'closed' || i.status === 'disconnected').length;
const connectingInstances = instances.filter(i => i.connectionState === 'connecting').length;
if (totalInstances === 0) {

View File

@ -22,6 +22,7 @@ export const useWhatsAppActions = (
// Handler for quando uma instância é reiniciada
const handleRestartInstance = async (instance: WhatsAppInstance) => {
try {
console.log(`Attempting to restart instance ${instance.instanceName}`);
await restartInstance(instance.instanceName);
// Atualiza o estado da instância para "connecting"
@ -29,6 +30,7 @@ export const useWhatsAppActions = (
...instance,
connectionState: 'connecting' as const
};
console.log(`Instance ${instance.instanceName} restart initiated, setting state to connecting`, updatedInstance);
updateInstance(updatedInstance);
toast({
@ -52,13 +54,16 @@ export const useWhatsAppActions = (
// Handler for quando uma instância é desconectada
const handleLogoutInstance = async (instance: WhatsAppInstance) => {
try {
console.log(`Attempting to logout instance ${instance.instanceName}`);
await logoutInstance(instance.instanceName);
// Atualiza o estado da instância para "closed"
const updatedInstance = {
...instance,
connectionState: 'closed' as const
connectionState: 'closed' as const,
status: 'disconnected'
};
console.log(`Instance ${instance.instanceName} logout successful, updating instance state`, updatedInstance);
updateInstance(updatedInstance);
toast({
@ -79,15 +84,21 @@ export const useWhatsAppActions = (
// Handler for quando a presença é alterada
const handleSetPresence = async (instance: WhatsAppInstance, presence: 'online' | 'offline') => {
try {
console.log(`Setting presence to ${presence} for instance ${instance.instanceName}`);
await setInstancePresence(instance.instanceName, presence);
const updatedInstance = {
...instance,
presence: presence
};
console.log(`Updated instance with new presence status`, updatedInstance);
updateInstance(updatedInstance);
toast({
title: "Sucesso",
description: `Instância ${instance.instanceName} agora está ${presence === 'online' ? 'Online' : 'Offline'}`
});
// Não precisamos alterar o estado da instância aqui, pois isso não afeta o connectionState
} catch (error) {
console.error(`Error setting presence to ${presence} for instance ${instance.instanceName}:`, error);
toast({
@ -100,11 +111,12 @@ export const useWhatsAppActions = (
// Handler for when an instance is deleted
const handleDeleteInstance = async (instanceId: string, instanceName: string) => {
console.log(`Deleting instance with ID: ${instanceId}`);
console.log(`Deleting instance with ID: ${instanceId}, name: ${instanceName}`);
try {
// Call API to delete instance
await deleteInstance(instanceName);
const response = await deleteInstance(instanceName);
console.log(`Deletion API response for instance ${instanceName}:`, response);
// Remove from local state
removeInstance(instanceId);
@ -112,6 +124,7 @@ export const useWhatsAppActions = (
// If we're viewing QR code for this instance, close the dialog
if (activeInstance?.instanceId === instanceId) {
setQrDialogOpen(false);
setActiveInstance(null);
}
toast({
@ -120,9 +133,19 @@ export const useWhatsAppActions = (
});
} catch (error) {
console.error(`Error deleting instance with ID ${instanceId}:`, error);
// Even if the API call fails, we still want to remove the instance from local storage
// This handles the case when instances are in inconsistent state with the server
removeInstance(instanceId);
if (activeInstance?.instanceId === instanceId) {
setQrDialogOpen(false);
setActiveInstance(null);
}
toast({
title: "Erro",
description: "Falha ao excluir a instância. Tente novamente.",
title: "Aviso",
description: "Instância removida localmente, mas pode haver falha na comunicação com o servidor.",
variant: "destructive",
});
}
@ -130,6 +153,7 @@ export const useWhatsAppActions = (
// Handler for when QR code dialog is requested
const handleViewQrCode = async (instance: WhatsAppInstance) => {
console.log(`Opening QR code dialog for instance: ${instance.instanceName}`);
setActiveInstance(instance);
setQrDialogOpen(true);

View File

@ -1,5 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useToast } from '@/hooks/use-toast';
import { WhatsAppInstance } from '@/types/whatsAppTypes';
import {
@ -37,26 +36,50 @@ export const useWhatsAppInstances = () => {
}, [instances, currentUserId]);
// Function to check connection status for all instances
const checkAllInstancesStatus = async () => {
const checkAllInstancesStatus = useCallback(async () => {
if (instances.length === 0) return;
console.log(`Checking connection status for ${instances.length} instances`);
try {
const updatedInstances = await Promise.all(
instances.map(async (instance) => {
try {
console.log(`Checking status for ${instance.instanceName}`);
const state = await fetchConnectionState(instance.instanceName);
return { ...instance, connectionState: state };
console.log(`Status for ${instance.instanceName}: ${state}`);
// Only update connectionState if it's different
if (instance.connectionState !== state) {
return { ...instance, connectionState: state };
}
return instance;
} catch (error) {
console.error(`Error checking status for ${instance.instanceName}:`, error);
return { ...instance, connectionState: 'closed' as const };
// If the instance doesn't exist on the server, mark it as closed
if (error instanceof Error && error.message.includes("does not exist")) {
return { ...instance, connectionState: 'closed', status: 'disconnected' };
}
// Keep the current state in case of other errors
return instance;
}
})
);
setInstances(updatedInstances);
// Only update state if there are actual changes
const hasChanges = JSON.stringify(updatedInstances) !== JSON.stringify(instances);
if (hasChanges) {
console.log('Updated instances after status check:', updatedInstances);
setInstances(updatedInstances);
} else {
console.log('No changes in instance status');
}
} catch (error) {
console.error("Error checking instances status:", error);
}
};
}, [instances]);
// Handler para atualizar a lista de instâncias do servidor
const refreshInstances = async () => {
@ -71,35 +94,47 @@ export const useWhatsAppInstances = () => {
setIsRefreshing(true);
try {
console.log("Fetching instances from server...");
const response = await fetchAllInstances();
console.log("Fetched instances from server:", response);
if (response.instances && Array.isArray(response.instances)) {
// Filtra instâncias para mostrar apenas as do usuário atual
// E mapeia para o formato correto com userId
// Mapeia as instâncias do servidor para o formato correto com userId
const serverInstances: WhatsAppInstance[] = response.instances
.filter(serverInstance => {
// Implemente sua lógica de filtro para o usuário atual (se necessário)
// Por padrão, vamos assumir que todas as instâncias são do usuário atual
return true;
})
.map(serverInstance => ({
instanceName: serverInstance.instanceName,
instanceId: serverInstance.instanceName,
instanceId: serverInstance.instanceName, // Using instanceName as the ID for consistency
phoneNumber: serverInstance.number || 'Desconhecido',
userId: currentUserId,
connectionState: serverInstance.state || 'closed',
status: serverInstance.status || 'unknown'
}));
// Mesclando instâncias do servidor com as locais (para não perder dados locais)
const localInstanceIds = new Set(instances.map(i => i.instanceId));
const newInstances = [
...instances,
...serverInstances.filter(i => !localInstanceIds.has(i.instanceId))
];
console.log("Mapped server instances:", serverInstances);
// Identifica instâncias novas que não existem localmente
const localInstanceIds = new Set(instances.map(i => i.instanceId));
const newServerInstances = serverInstances.filter(i => !localInstanceIds.has(i.instanceId));
// Atualiza instâncias existentes com dados do servidor
const updatedExistingInstances = instances.map(localInstance => {
const serverMatch = serverInstances.find(si => si.instanceId === localInstance.instanceId);
if (serverMatch) {
return {
...localInstance,
connectionState: serverMatch.connectionState,
status: serverMatch.status
};
}
return localInstance;
});
// Combina tudo
const allInstances = [...updatedExistingInstances, ...newServerInstances];
console.log("Combined instances after refresh:", allInstances);
setInstances(allInstances);
setInstances(newInstances);
toast({
title: "Sucesso",
description: `${serverInstances.length} instâncias encontradas no servidor`,
@ -120,18 +155,34 @@ export const useWhatsAppInstances = () => {
} finally {
setIsRefreshing(false);
// Verificar status após atualizar a lista
checkAllInstancesStatus();
await checkAllInstancesStatus();
}
};
// Add a new instance
const addInstance = (newInstance: WhatsAppInstance) => {
console.log("New instance created, adding to instances list:", newInstance);
setInstances(prevInstances => [...prevInstances, newInstance]);
// Verifica se já existe uma instância com o mesmo ID
const existingIndex = instances.findIndex(inst => inst.instanceId === newInstance.instanceId);
if (existingIndex >= 0) {
// Atualiza a instância existente
console.log(`Instance with ID ${newInstance.instanceId} already exists, updating it`);
setInstances(prevInstances =>
prevInstances.map((instance, index) =>
index === existingIndex ? newInstance : instance
)
);
} else {
// Adiciona nova instância
setInstances(prevInstances => [...prevInstances, newInstance]);
}
};
// Remove an instance
const removeInstance = (instanceId: string) => {
console.log(`Removing instance with ID: ${instanceId}`);
setInstances(prevInstances => {
const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId);
console.log("Updated instances after deletion:", filtered);
@ -141,13 +192,17 @@ export const useWhatsAppInstances = () => {
// Update an instance
const updateInstance = (updatedInstance: WhatsAppInstance) => {
setInstances(prevInstances =>
prevInstances.map(instance =>
console.log(`Updating instance: ${updatedInstance.instanceName}`, updatedInstance);
setInstances(prevInstances => {
const newInstances = prevInstances.map(instance =>
instance.instanceId === updatedInstance.instanceId
? updatedInstance
: instance
)
);
);
console.log("Updated instances:", newInstances);
return newInstances;
});
};
return {

View File

@ -8,8 +8,10 @@ import InstanceStats from '@/components/whatsapp/InstanceStats';
import QrCodeDialog from '@/components/whatsapp/QrCodeDialog';
import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances';
import { useWhatsAppActions } from '@/hooks/useWhatsAppActions';
import { useToast } from '@/hooks/use-toast';
const WhatsApp = () => {
const { toast } = useToast();
const {
instances,
isRefreshing,
@ -33,17 +35,20 @@ const WhatsApp = () => {
// Set up periodic status checks
useEffect(() => {
console.log("Setting up periodic status checks, current instances:", instances.length);
// Check status initially
if (instances.length > 0) {
checkAllInstancesStatus();
}
// Set up interval for periodic checks (every 60 seconds)
// Set up interval for periodic checks (every 30 seconds)
const interval = setInterval(() => {
if (instances.length > 0) {
console.log("Running periodic status check");
checkAllInstancesStatus();
}
}, 60000); // 60 seconds
}, 30000); // 30 seconds
// Clean up interval when component unmounts
return () => {
@ -53,6 +58,21 @@ const WhatsApp = () => {
};
}, [instances.length, checkAllInstancesStatus]);
// Run refresh instances on initial load to get server instances
useEffect(() => {
const initialLoad = async () => {
if (instances.length === 0) {
try {
await refreshInstances();
} catch (error) {
console.error("Error on initial instance refresh:", error);
}
}
};
initialLoad();
}, []);
// Handler for when a new instance is created
const handleInstanceCreated = async (newInstance: WhatsAppInstance) => {
console.log('New instance to be added:', newInstance);
@ -64,14 +84,31 @@ const WhatsApp = () => {
}
// Trigger a status check for all instances
await checkAllInstancesStatus();
try {
await checkAllInstancesStatus();
} catch (error) {
console.error("Error checking status after instance creation:", error);
toast({
title: "Aviso",
description: "Instância criada, mas não foi possível verificar o status. Tente atualizar a lista manualmente.",
variant: "default",
});
}
};
// Handler for when an instance is deleted
const handleDeleteInstanceWrapper = (instanceId: string) => {
console.log(`Instance deletion requested for ID: ${instanceId}`);
const instanceToDelete = instances.find(i => i.instanceId === instanceId);
if (instanceToDelete) {
handleDeleteInstance(instanceId, instanceToDelete.instanceName);
} else {
console.error(`Instance with ID ${instanceId} not found for deletion`);
toast({
title: "Erro",
description: "Instância não encontrada para exclusão",
variant: "destructive",
});
}
};

View File

@ -11,6 +11,7 @@ export const createWhatsAppInstance = async (
instanceName: string,
phoneNumber: string
): Promise<any> => {
console.log(`Creating new WhatsApp instance: ${instanceName}, ${phoneNumber}`);
const response = await fetch(`https://${SERVER_URL}/instance/create`, {
method: 'POST',
headers: {
@ -27,13 +28,17 @@ export const createWhatsAppInstance = async (
if (!response.ok) {
const errorData = await response.json();
console.error('Error creating instance:', errorData);
throw new Error(errorData.message || 'Erro ao criar instância');
}
return response.json();
const data = await response.json();
console.log('Create instance API response:', data);
return data;
};
export const fetchQrCode = async (instanceName: string): Promise<any> => {
console.log(`Fetching QR code for instance: ${instanceName}`);
const response = await fetch(`https://${SERVER_URL}/instance/connect/${encodeURIComponent(instanceName)}`, {
method: 'GET',
headers: {
@ -44,14 +49,18 @@ export const fetchQrCode = async (instanceName: string): Promise<any> => {
if (!response.ok) {
const errorData = await response.json();
console.error('Error fetching QR code:', errorData);
throw new Error(errorData.message || 'Erro ao obter QR Code');
}
return response.json();
const data = await response.json();
console.log('QR code fetch response:', data);
return data;
};
export const fetchConnectionState = async (instanceName: string): Promise<'open' | 'closed' | 'connecting'> => {
try {
console.log(`Fetching connection state for: ${instanceName}`);
const response = await fetch(`https://${SERVER_URL}/instance/connectionState/${encodeURIComponent(instanceName)}`, {
method: 'GET',
headers: {
@ -61,13 +70,25 @@ export const fetchConnectionState = async (instanceName: string): Promise<'open'
});
if (!response.ok) {
const errorData = await response.json();
console.error(`Connection state error for ${instanceName}:`, errorData);
// If instance doesn't exist, throw a specific error
if (response.status === 404) {
throw new Error(`The "${instanceName}" instance does not exist`);
}
return 'closed';
}
const data = await response.json();
console.log(`Connection state for ${instanceName}:`, data);
return data.instance?.state || 'closed';
} catch (error) {
console.error("Error fetching connection state:", error);
console.error(`Error fetching connection state for ${instanceName}:`, error);
if (error instanceof Error && error.message.includes("does not exist")) {
throw error; // Rethrow specific "does not exist" errors
}
return 'closed';
}
};
@ -75,6 +96,7 @@ export const fetchConnectionState = async (instanceName: string): Promise<'open'
// Nova função para listar todas as instâncias
export const fetchAllInstances = async (): Promise<any> => {
try {
console.log('Fetching all instances');
const response = await fetch(`https://${SERVER_URL}/fetch-instances`, {
method: 'GET',
headers: {
@ -84,10 +106,14 @@ export const fetchAllInstances = async (): Promise<any> => {
});
if (!response.ok) {
throw new Error('Erro ao buscar instâncias');
const errorData = await response.json();
console.error('Error fetching instances:', errorData);
throw new Error(errorData.message || 'Erro ao buscar instâncias');
}
return response.json();
const data = await response.json();
console.log('All instances response:', data);
return data;
} catch (error) {
console.error("Error fetching all instances:", error);
throw error;
@ -97,6 +123,7 @@ export const fetchAllInstances = async (): Promise<any> => {
// Função para reiniciar uma instância
export const restartInstance = async (instanceName: string): Promise<any> => {
try {
console.log(`Restarting instance: ${instanceName}`);
const response = await fetch(`https://${SERVER_URL}/instance/restart/${encodeURIComponent(instanceName)}`, {
method: 'PUT',
headers: {
@ -107,10 +134,13 @@ export const restartInstance = async (instanceName: string): Promise<any> => {
if (!response.ok) {
const errorData = await response.json();
console.error(`Error restarting instance ${instanceName}:`, errorData);
throw new Error(errorData.message || 'Erro ao reiniciar instância');
}
return response.json();
const data = await response.json();
console.log(`Restart response for ${instanceName}:`, data);
return data;
} catch (error) {
console.error(`Error restarting instance ${instanceName}:`, error);
throw error;
@ -120,6 +150,7 @@ export const restartInstance = async (instanceName: string): Promise<any> => {
// Função para desconectar (logout) uma instância
export const logoutInstance = async (instanceName: string): Promise<any> => {
try {
console.log(`Logging out instance: ${instanceName}`);
const response = await fetch(`https://${SERVER_URL}/instance/logout/${encodeURIComponent(instanceName)}`, {
method: 'DELETE',
headers: {
@ -130,10 +161,13 @@ export const logoutInstance = async (instanceName: string): Promise<any> => {
if (!response.ok) {
const errorData = await response.json();
console.error(`Error logging out instance ${instanceName}:`, errorData);
throw new Error(errorData.message || 'Erro ao desconectar instância');
}
return response.json();
const data = await response.json();
console.log(`Logout response for ${instanceName}:`, data);
return data;
} catch (error) {
console.error(`Error logging out instance ${instanceName}:`, error);
throw error;
@ -143,6 +177,7 @@ export const logoutInstance = async (instanceName: string): Promise<any> => {
// Função para excluir uma instância
export const deleteInstance = async (instanceName: string): Promise<any> => {
try {
console.log(`Deleting instance: ${instanceName}`);
const response = await fetch(`https://${SERVER_URL}/instance/${encodeURIComponent(instanceName)}`, {
method: 'DELETE',
headers: {
@ -153,10 +188,13 @@ export const deleteInstance = async (instanceName: string): Promise<any> => {
if (!response.ok) {
const errorData = await response.json();
console.error(`Error deleting instance ${instanceName}:`, errorData);
throw new Error(errorData.message || 'Erro ao excluir instância');
}
return response.json();
const data = await response.json();
console.log(`Delete response for ${instanceName}:`, data);
return data;
} catch (error) {
console.error(`Error deleting instance ${instanceName}:`, error);
throw error;
@ -166,6 +204,7 @@ export const deleteInstance = async (instanceName: string): Promise<any> => {
// Função para definir presença online/offline
export const setInstancePresence = async (instanceName: string, presence: 'online' | 'offline'): Promise<any> => {
try {
console.log(`Setting presence to ${presence} for: ${instanceName}`);
const response = await fetch(`https://${SERVER_URL}/instance/setPresence/${encodeURIComponent(instanceName)}`, {
method: 'POST',
headers: {
@ -177,10 +216,13 @@ export const setInstancePresence = async (instanceName: string, presence: 'onlin
if (!response.ok) {
const errorData = await response.json();
console.error(`Error setting presence for ${instanceName}:`, errorData);
throw new Error(errorData.message || `Erro ao definir presença para ${presence}`);
}
return response.json();
const data = await response.json();
console.log(`Set presence response for ${instanceName}:`, data);
return data;
} catch (error) {
console.error(`Error setting presence to ${presence} for instance ${instanceName}:`, error);
throw error;
@ -192,19 +234,24 @@ export const saveInstancesToLocalStorage = (
currentUserId: string
): void => {
try {
console.log(`Saving ${instances.length} instances for user ${currentUserId}`);
// Get existing instances from localStorage
const savedInstancesStr = localStorage.getItem(STORAGE_KEY);
let allInstances: WhatsAppInstance[] = [];
if (savedInstancesStr) {
// Parse saved instances
const savedInstances = JSON.parse(savedInstancesStr);
// If it's an array, filter out current user's old instances
if (Array.isArray(savedInstances)) {
allInstances = savedInstances.filter(
(instance: WhatsAppInstance) => instance.userId !== currentUserId
);
try {
// Parse saved instances
const savedInstances = JSON.parse(savedInstancesStr);
// If it's an array, filter out current user's old instances
if (Array.isArray(savedInstances)) {
allInstances = savedInstances.filter(
(instance: WhatsAppInstance) => instance.userId !== currentUserId
);
}
} catch (parseError) {
console.error('Error parsing stored instances, resetting:', parseError);
}
}
@ -223,21 +270,26 @@ export const loadInstancesFromLocalStorage = (
userId: string
): WhatsAppInstance[] => {
try {
console.log(`Loading instances for user: ${userId}`);
const savedInstancesStr = localStorage.getItem(STORAGE_KEY);
console.log('Raw saved instances from localStorage:', savedInstancesStr);
if (savedInstancesStr && userId) {
// Parse saved instances
const allInstances = JSON.parse(savedInstancesStr);
// If it's an array, filter by user ID
if (Array.isArray(allInstances)) {
// Filter instances to only show those belonging to the current user
const userInstances = allInstances.filter(
(instance: WhatsAppInstance) => instance.userId === userId
);
console.log(`Found ${userInstances.length} instances for user ${userId}:`, userInstances);
return userInstances;
try {
// Parse saved instances
const allInstances = JSON.parse(savedInstancesStr);
// If it's an array, filter by user ID
if (Array.isArray(allInstances)) {
// Filter instances to only show those belonging to the current user
const userInstances = allInstances.filter(
(instance: WhatsAppInstance) => instance.userId === userId
);
console.log(`Found ${userInstances.length} instances for user ${userId}:`, userInstances);
return userInstances;
}
} catch (parseError) {
console.error('Error parsing stored instances:', parseError);
}
}
console.log(`No instances found for user ${userId}`);