mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Clean up and fix issues
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Loading...</title>
|
||||
<link rel="icon" href="./src/images/logo.png" />
|
||||
<link rel="icon" href="./src/images/logo.svg" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isElectron } from "@/core/electron/electron";
|
||||
import { websocket } from "@/core/websocket";
|
||||
import type { NewMessageWebSocketMessage, WebSocketMessage } from "@/core/types";
|
||||
import serviceWorker from "./service-worker?worker&url";
|
||||
import logo from "@/images/logo.svg";
|
||||
|
||||
export interface PushSubscriptionData {
|
||||
endpoint: string;
|
||||
@@ -111,7 +112,7 @@ async function showMessageNotification(message: any): Promise<void> {
|
||||
body: message.content.length > 100
|
||||
? message.content.substring(0, 100) + "..."
|
||||
: message.content,
|
||||
icon: message.profile_picture || "/logo.png",
|
||||
icon: message.profile_picture || logo,
|
||||
tag: `message_${message.id}`,
|
||||
data: {
|
||||
type: "public_message",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
import logo from "@/images/logo.svg";
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
interface NotificationPayload {
|
||||
@@ -36,8 +38,8 @@ self.addEventListener("push", function(event: ExtendableEvent) {
|
||||
|
||||
const options: NotificationOptions = {
|
||||
body: data.body,
|
||||
icon: data.icon || "/logo.png",
|
||||
badge: "/logo.png",
|
||||
icon: data.icon || logo,
|
||||
badge: logo,
|
||||
image: data.image,
|
||||
tag: data.tag || "message",
|
||||
data: data.data,
|
||||
|
||||
+201
-78
@@ -44,6 +44,18 @@ let globalMessageHandler: ((response: WebSocketMessage<any>) => void) | null = n
|
||||
*/
|
||||
let callSignalingHandler: CallSignalingHandler | null = null;
|
||||
|
||||
/**
|
||||
* Reconnection state
|
||||
*/
|
||||
let reconnectAttempts = 0;
|
||||
const MAX_RECONNECT_DELAY = 30000; // 30 seconds max delay
|
||||
const INITIAL_RECONNECT_DELAY = 1000; // Start with 1 second
|
||||
let isReconnecting = false;
|
||||
let messageHandler: ((e: MessageEvent) => void) | null = null;
|
||||
let errorHandler: ((e: Event) => void) | null = null;
|
||||
let closeHandler: ((e: CloseEvent) => void) | null = null;
|
||||
let openHandler: ((e: Event) => void) | null = null;
|
||||
|
||||
/**
|
||||
* Set the global WebSocket message handler
|
||||
* @param handler - Function to handle WebSocket messages
|
||||
@@ -60,97 +72,208 @@ export function setCallSignalingHandler(handler: CallSignalingHandler | null): v
|
||||
callSignalingHandler = handler;
|
||||
}
|
||||
|
||||
export function request<Request, Response = any>(payload: WebSocketMessage<Request>): Promise<WebSocketMessage<Response>> {
|
||||
console.log("WebSocket request:", payload);
|
||||
return new Promise((resolve, reject) => {
|
||||
function requestInner() {
|
||||
let listener: ((e: MessageEvent) => void) | null = null;
|
||||
listener = (e) => {
|
||||
resolve(JSON.parse(e.data));
|
||||
websocket.removeEventListener("message", listener!);
|
||||
/**
|
||||
* Clean up all event listeners from the current WebSocket instance
|
||||
* @private
|
||||
*/
|
||||
function cleanupWebSocket(): void {
|
||||
if (websocket) {
|
||||
if (messageHandler) {
|
||||
websocket.removeEventListener("message", messageHandler);
|
||||
}
|
||||
if (errorHandler) {
|
||||
websocket.removeEventListener("error", errorHandler);
|
||||
}
|
||||
if (closeHandler) {
|
||||
websocket.removeEventListener("close", closeHandler);
|
||||
}
|
||||
if (openHandler) {
|
||||
websocket.removeEventListener("open", openHandler);
|
||||
}
|
||||
|
||||
// Close if still connected
|
||||
if (websocket.readyState === WebSocket.OPEN || websocket.readyState === WebSocket.CONNECTING) {
|
||||
try {
|
||||
websocket.close();
|
||||
} catch (e) {
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
websocket.addEventListener("message", listener);
|
||||
websocket.send(JSON.stringify(payload))
|
||||
|
||||
setTimeout(() => reject("Request timed out"), 10000);
|
||||
}
|
||||
|
||||
if (websocket.readyState == 0) {
|
||||
websocket.addEventListener("open", requestInner);
|
||||
setTimeout(() => reject("Request timed out"), 10000);
|
||||
} else {
|
||||
requestInner();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will wait 3 seconds and them attempts to reconnect the WebSocket.
|
||||
* If it fails, tries again in an endless loop until the connection is established
|
||||
* again.
|
||||
*
|
||||
* Calculate exponential backoff delay
|
||||
* @param attempt - Current reconnection attempt number
|
||||
* @returns Delay in milliseconds
|
||||
* @private
|
||||
*/
|
||||
async function onError() {
|
||||
console.warn("WebSocket disconnected, retrying in 3 seconds...");
|
||||
await delay(3000);
|
||||
websocket = create();
|
||||
function getReconnectDelay(attempt: number): number {
|
||||
const delay = INITIAL_RECONNECT_DELAY * Math.pow(2, attempt);
|
||||
return Math.min(delay, MAX_RECONNECT_DELAY);
|
||||
}
|
||||
|
||||
let listener: () => void | null;
|
||||
listener = () => {
|
||||
console.log("WebSocket successfully reconnected!");
|
||||
websocket.removeEventListener("open", listener);
|
||||
/**
|
||||
* Handle WebSocket reconnection with exponential backoff
|
||||
* @private
|
||||
*/
|
||||
async function reconnect(): Promise<void> {
|
||||
if (isReconnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
websocket.addEventListener("open", listener);
|
||||
websocket.addEventListener("error", onError);
|
||||
isReconnecting = true;
|
||||
|
||||
// Clean up old connection
|
||||
cleanupWebSocket();
|
||||
|
||||
const delayMs = getReconnectDelay(reconnectAttempts);
|
||||
reconnectAttempts++;
|
||||
|
||||
await delay(delayMs);
|
||||
|
||||
try {
|
||||
websocket = create();
|
||||
setupEventHandlers();
|
||||
} catch (error) {
|
||||
// If creation fails, try again
|
||||
isReconnecting = false;
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup event handlers for the WebSocket connection
|
||||
* @private
|
||||
*/
|
||||
function setupEventHandlers(): void {
|
||||
// Message handler
|
||||
messageHandler = (e: MessageEvent) => {
|
||||
try {
|
||||
const response: WebSocketMessage<any> = JSON.parse(e.data);
|
||||
|
||||
// Handle call signaling messages
|
||||
if (callSignalingHandler && response.type === "call_signaling" && response.data) {
|
||||
callSignalingHandler.handleWebSocketMessage(response.data);
|
||||
}
|
||||
|
||||
// Handle status and typing messages
|
||||
if (response.type === "statusUpdate") {
|
||||
onlineStatusManager.handleStatusUpdate(response as any);
|
||||
} else if (response.type === "typing") {
|
||||
typingManager.handleTyping(response as any);
|
||||
} else if (response.type === "stopTyping") {
|
||||
typingManager.handleStopTyping(response as any);
|
||||
} else if (response.type === "dmTyping") {
|
||||
typingManager.handleDmTyping(response as any);
|
||||
} else if (response.type === "stopDmTyping") {
|
||||
typingManager.handleStopDmTyping(response as any);
|
||||
} else if (response.type === "suspended") {
|
||||
// Handle account suspension
|
||||
const { setSuspended } = useAppState.getState();
|
||||
const reason = response.data?.reason || "No reason provided";
|
||||
setSuspended(reason);
|
||||
// Close WebSocket connection
|
||||
websocket.close();
|
||||
} else if (response.type === "account_deleted") {
|
||||
// Handle account deletion - silent logout
|
||||
const { logout } = useAppState.getState();
|
||||
logout();
|
||||
// Close WebSocket connection
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
// Route message to global handler if set
|
||||
if (globalMessageHandler) {
|
||||
globalMessageHandler(response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing WebSocket message:", error);
|
||||
}
|
||||
};
|
||||
websocket.addEventListener("message", messageHandler);
|
||||
|
||||
// Open handler
|
||||
openHandler = () => {
|
||||
reconnectAttempts = 0; // Reset on successful connection
|
||||
isReconnecting = false;
|
||||
};
|
||||
websocket.addEventListener("open", openHandler);
|
||||
|
||||
// Error handler
|
||||
errorHandler = () => {
|
||||
// Don't reconnect immediately on error - let close handler handle it
|
||||
// This prevents double reconnection attempts
|
||||
};
|
||||
websocket.addEventListener("error", errorHandler);
|
||||
|
||||
// Close handler
|
||||
closeHandler = (e: CloseEvent) => {
|
||||
// Don't reconnect if it was a clean close (e.g., logout, suspension)
|
||||
if (e.code === 1000 || e.code === 1001) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconnect for unexpected closes
|
||||
if (!isReconnecting) {
|
||||
reconnect();
|
||||
}
|
||||
};
|
||||
websocket.addEventListener("close", closeHandler);
|
||||
}
|
||||
|
||||
export function request<Request, Response = any>(payload: WebSocketMessage<Request>): Promise<WebSocketMessage<Response>> {
|
||||
console.log("WebSocket request:", payload);
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
reject(new Error("Request timed out"));
|
||||
}, 10000);
|
||||
|
||||
function requestInner() {
|
||||
if (websocket.readyState !== WebSocket.OPEN) {
|
||||
clearTimeout(timeoutId);
|
||||
reject(new Error("WebSocket is not open"));
|
||||
return;
|
||||
}
|
||||
|
||||
const listener = (e: MessageEvent) => {
|
||||
clearTimeout(timeoutId);
|
||||
try {
|
||||
resolve(JSON.parse(e.data));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
websocket.removeEventListener("message", listener);
|
||||
};
|
||||
|
||||
websocket.addEventListener("message", listener);
|
||||
|
||||
try {
|
||||
websocket.send(JSON.stringify(payload));
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
websocket.removeEventListener("message", listener);
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (websocket.readyState === WebSocket.CONNECTING) {
|
||||
const openListener = () => {
|
||||
websocket.removeEventListener("open", openListener);
|
||||
requestInner();
|
||||
};
|
||||
websocket.addEventListener("open", openListener);
|
||||
} else if (websocket.readyState === WebSocket.OPEN) {
|
||||
requestInner();
|
||||
} else {
|
||||
clearTimeout(timeoutId);
|
||||
reject(new Error("WebSocket is closed"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --------------
|
||||
// Initialization
|
||||
// --------------
|
||||
|
||||
websocket.addEventListener("message", (e) => {
|
||||
try {
|
||||
const response: WebSocketMessage<any> = JSON.parse(e.data);
|
||||
|
||||
// Handle call signaling messages
|
||||
if (callSignalingHandler && response.type === "call_signaling" && response.data) {
|
||||
callSignalingHandler.handleWebSocketMessage(response.data);
|
||||
}
|
||||
|
||||
// Handle status and typing messages
|
||||
if (response.type === "statusUpdate") {
|
||||
onlineStatusManager.handleStatusUpdate(response as any);
|
||||
} else if (response.type === "typing") {
|
||||
typingManager.handleTyping(response as any);
|
||||
} else if (response.type === "stopTyping") {
|
||||
typingManager.handleStopTyping(response as any);
|
||||
} else if (response.type === "dmTyping") {
|
||||
typingManager.handleDmTyping(response as any);
|
||||
} else if (response.type === "stopDmTyping") {
|
||||
typingManager.handleStopDmTyping(response as any);
|
||||
} else if (response.type === "suspended") {
|
||||
// Handle account suspension
|
||||
const { setSuspended } = useAppState.getState();
|
||||
const reason = response.data?.reason || "No reason provided";
|
||||
setSuspended(reason);
|
||||
// Close WebSocket connection
|
||||
websocket.close();
|
||||
} else if (response.type === "account_deleted") {
|
||||
// Handle account deletion - silent logout
|
||||
const { logout } = useAppState.getState();
|
||||
logout();
|
||||
// Close WebSocket connection
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
// Route message to global handler if set
|
||||
if (globalMessageHandler) {
|
||||
globalMessageHandler(response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing WebSocket message:", error);
|
||||
}
|
||||
});
|
||||
websocket.addEventListener("error", onError);
|
||||
setupEventHandlers();
|
||||
@@ -53,7 +53,7 @@ button, input {
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: #ff9800; // Orange color for warnings
|
||||
color: $color-dark-tertiary; // Purple-themed warning color
|
||||
}
|
||||
|
||||
&.small mdui-icon {
|
||||
|
||||
@@ -54,11 +54,6 @@ $color-dark-surface-container-highest: rgb(55 51 57);
|
||||
$color-dark-surface-primary-container-lightened: color.adjust($color-dark-primary-container, $lightness: 5%);
|
||||
$color-dark-surface-container-lightened: color.adjust($color-dark-surface-container, $lightness: 5%);
|
||||
|
||||
// custom colors
|
||||
$color-1: rgb(82, 109, 246);
|
||||
$color-2: rgb(65, 11, 113);
|
||||
$color-4: rgb(95, 26, 198);
|
||||
$color-3: rgb(49, 71, 179);
|
||||
// Light
|
||||
$color-light-primary: rgb(31 101 134);
|
||||
$color-light-surface-tint: rgb(31 101 134);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
user-select: none;
|
||||
|
||||
.logo {
|
||||
$size: 35px;
|
||||
|
||||
Reference in New Issue
Block a user