refactor: enhance chat components with type safety and response functionality
- Updated type definitions in ChatWindow and MessageList components for better type safety. - Improved MessageInput to handle message responses, including a preview feature for replying to messages. - Enhanced the chat message handling logic to support message references and improve user interaction. - Refactored notification utility functions to support push notifications and rate limiting for email sending. - Updated backend schema to accommodate new features related to message responses and notifications.
This commit is contained in:
@@ -1,66 +1,207 @@
|
||||
/**
|
||||
* Solicita permissão para notificações desktop
|
||||
*/
|
||||
export async function requestNotificationPermission(): Promise<NotificationPermission> {
|
||||
if (!("Notification" in window)) {
|
||||
console.warn("Este navegador não suporta notificações desktop");
|
||||
return "denied";
|
||||
}
|
||||
|
||||
if (Notification.permission === "granted") {
|
||||
return "granted";
|
||||
}
|
||||
|
||||
if (Notification.permission !== "denied") {
|
||||
return await Notification.requestPermission();
|
||||
}
|
||||
|
||||
return Notification.permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostra uma notificação desktop
|
||||
*/
|
||||
export function showNotification(title: string, options?: NotificationOptions): Notification | null {
|
||||
if (!("Notification" in window)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Notification.permission !== "granted") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Notification(title, {
|
||||
icon: "/favicon.png",
|
||||
badge: "/favicon.png",
|
||||
...options,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erro ao exibir notificação:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toca o som de notificação
|
||||
*/
|
||||
export function playNotificationSound() {
|
||||
try {
|
||||
const audio = new Audio("/sounds/notification.mp3");
|
||||
audio.volume = 0.5;
|
||||
audio.play().catch((err) => {
|
||||
console.warn("Não foi possível reproduzir o som de notificação:", err);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erro ao tocar som de notificação:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o usuário está na aba ativa
|
||||
*/
|
||||
export function isTabActive(): boolean {
|
||||
return !document.hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solicita permissão para notificações desktop
|
||||
*/
|
||||
export async function requestNotificationPermission(): Promise<NotificationPermission> {
|
||||
if (!("Notification" in window)) {
|
||||
console.warn("Este navegador não suporta notificações desktop");
|
||||
return "denied";
|
||||
}
|
||||
|
||||
if (Notification.permission === "granted") {
|
||||
return "granted";
|
||||
}
|
||||
|
||||
if (Notification.permission !== "denied") {
|
||||
return await Notification.requestPermission();
|
||||
}
|
||||
|
||||
return Notification.permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostra uma notificação desktop
|
||||
*/
|
||||
export function showNotification(title: string, options?: NotificationOptions): Notification | null {
|
||||
if (!("Notification" in window)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Notification.permission !== "granted") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Notification(title, {
|
||||
icon: "/favicon.png",
|
||||
badge: "/favicon.png",
|
||||
...options,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erro ao exibir notificação:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toca o som de notificação
|
||||
*/
|
||||
export function playNotificationSound() {
|
||||
try {
|
||||
const audio = new Audio("/sounds/notification.mp3");
|
||||
audio.volume = 0.5;
|
||||
audio.play().catch((err) => {
|
||||
console.warn("Não foi possível reproduzir o som de notificação:", err);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erro ao tocar som de notificação:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o usuário está na aba ativa
|
||||
*/
|
||||
export function isTabActive(): boolean {
|
||||
return !document.hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registrar service worker para push notifications
|
||||
*/
|
||||
export async function registrarServiceWorker(): Promise<ServiceWorkerRegistration | null> {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
console.warn("Service Workers não são suportados neste navegador");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register("/sw.js", {
|
||||
scope: "/",
|
||||
});
|
||||
console.log("Service Worker registrado:", registration);
|
||||
return registration;
|
||||
} catch (error) {
|
||||
console.error("Erro ao registrar Service Worker:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Solicitar subscription de push notification
|
||||
*/
|
||||
export async function solicitarPushSubscription(): Promise<PushSubscription | null> {
|
||||
// Registrar service worker primeiro
|
||||
const registration = await registrarServiceWorker();
|
||||
if (!registration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verificar se push está disponível
|
||||
if (!("PushManager" in window)) {
|
||||
console.warn("Push notifications não são suportadas neste navegador");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Solicitar permissão
|
||||
const permission = await requestNotificationPermission();
|
||||
if (permission !== "granted") {
|
||||
console.warn("Permissão para notificações negada");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Obter subscription existente ou criar nova
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (!subscription) {
|
||||
// VAPID public key deve vir do backend ou config
|
||||
// Por enquanto, usando uma chave pública de exemplo
|
||||
// Em produção, isso deve vir de uma variável de ambiente ou API
|
||||
const vapidPublicKey = import.meta.env.VITE_VAPID_PUBLIC_KEY || "";
|
||||
|
||||
if (!vapidPublicKey) {
|
||||
console.warn("VAPID public key não configurada");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Converter chave para formato Uint8Array
|
||||
const applicationServerKey = urlBase64ToUint8Array(vapidPublicKey);
|
||||
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey,
|
||||
});
|
||||
}
|
||||
|
||||
return subscription;
|
||||
} catch (error) {
|
||||
console.error("Erro ao obter subscription:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter chave VAPID de base64 URL-safe para Uint8Array
|
||||
*/
|
||||
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter PushSubscription para formato serializável
|
||||
*/
|
||||
export function subscriptionToJSON(subscription: PushSubscription): {
|
||||
endpoint: string;
|
||||
keys: { p256dh: string; auth: string };
|
||||
} {
|
||||
const key = subscription.getKey("p256dh");
|
||||
const auth = subscription.getKey("auth");
|
||||
|
||||
if (!key || !auth) {
|
||||
throw new Error("Chaves de subscription não encontradas");
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: arrayBufferToBase64(key),
|
||||
auth: arrayBufferToBase64(auth),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter ArrayBuffer para base64
|
||||
*/
|
||||
function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let binary = "";
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remover subscription de push notification
|
||||
*/
|
||||
export async function removerPushSubscription(): Promise<boolean> {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (subscription) {
|
||||
await subscription.unsubscribe();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user