UNPKG

zeno-db

Version:

A lightweight, offline-first client-side database with automatic sync capabilities

217 lines (216 loc) 9.11 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketSync = void 0; class WebSocketSync { constructor(config) { this.ws = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectTimeout = 1000; this.pendingChanges = []; this.isConnected = false; this.reconnectTimer = null; this.networkCheckInterval = null; this.url = config.url; this.setupNetworkCheck(); } setupNetworkCheck() { // Clear any existing interval if (this.networkCheckInterval) { clearInterval(this.networkCheckInterval); } // Check network status every 5 seconds this.networkCheckInterval = setInterval(() => { if (!this.isConnected && navigator.onLine) { console.log('[WebSocketSync] Network is back online, attempting to reconnect...'); this.reconnectAttempts = 0; // Reset attempts when network is back this.connect(); } }, 5000); // Listen for online/offline events window.addEventListener('online', () => { console.log('[WebSocketSync] Browser reports online status'); this.reconnectAttempts = 0; // Reset attempts when network is back this.connect(); }); window.addEventListener('offline', () => { console.log('[WebSocketSync] Browser reports offline status'); this.isConnected = false; if (this.ws) { this.ws.close(); this.ws = null; } }); } connect() { return __awaiter(this, void 0, void 0, function* () { var _a; // Clear any existing reconnect timer if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.CONNECTING) { console.log('[WebSocketSync] Connection already in progress'); return; } return new Promise((resolve, reject) => { try { this.ws = new WebSocket(this.url); this.ws.onopen = () => { this.reconnectAttempts = 0; this.isConnected = true; console.log('[WebSocketSync] Connected to server'); // Send any pending changes that occurred while offline this.processPendingChanges(); if (this.onConnectedCallback) { this.onConnectedCallback(); } resolve(); }; this.ws.onerror = (error) => { console.error('[WebSocketSync] WebSocket error:', error); this.isConnected = false; reject(error); }; this.ws.onclose = () => { console.log('[WebSocketSync] Connection closed'); this.isConnected = false; if (this.onDisconnectedCallback) { this.onDisconnectedCallback(); } if (this.reconnectAttempts < this.maxReconnectAttempts && navigator.onLine) { this.reconnectAttempts++; const delay = this.reconnectTimeout * Math.pow(2, this.reconnectAttempts - 1); console.log(`[WebSocketSync] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`); // Store the timer so we can clear it if needed this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.connect(); }, delay); } else if (!navigator.onLine) { console.log('[WebSocketSync] Network is offline, waiting for connection...'); } else { console.error('[WebSocketSync] Max reconnection attempts reached'); } }; this.ws.onmessage = (event) => { try { const change = JSON.parse(event.data); if (this.onReceiveCallback) { this.onReceiveCallback(change); } } catch (error) { console.error('[WebSocketSync] Error parsing WebSocket message:', error); } }; } catch (error) { reject(error); } }); }); } processPendingChanges() { return __awaiter(this, void 0, void 0, function* () { console.log(`[WebSocketSync] Processing ${this.pendingChanges.length} pending changes`); while (this.pendingChanges.length > 0) { const change = this.pendingChanges.shift(); if (change) { try { const success = yield this.send(change); if (!success) { // If send fails, put the change back at the start of the queue this.pendingChanges.unshift(change); break; } } catch (error) { console.error('[WebSocketSync] Error processing pending change:', error); this.pendingChanges.unshift(change); break; } } } }); } send(change) { return __awaiter(this, void 0, void 0, function* () { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { console.log('[WebSocketSync] Not connected, queueing change:', change); this.pendingChanges.push(change); return false; } try { this.ws.send(JSON.stringify(change)); return true; } catch (error) { console.error('[WebSocketSync] Error sending change:', error); this.pendingChanges.push(change); return false; } }); } onConnected(callback) { var _a; this.onConnectedCallback = callback; // If already connected, call the callback immediately if (this.isConnected && ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) { callback(); } } onDisconnected(callback) { this.onDisconnectedCallback = callback; } onReceive(callback) { this.onReceiveCallback = callback; } close() { return __awaiter(this, void 0, void 0, function* () { // Clear all timers if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; } if (this.networkCheckInterval) { clearInterval(this.networkCheckInterval); this.networkCheckInterval = null; } if (this.ws) { this.ws.close(); this.ws = null; this.isConnected = false; } }); } disconnect() { return __awaiter(this, void 0, void 0, function* () { if (this.ws) { this.ws.close(); this.ws = null; } }); } sendChange(change) { return __awaiter(this, void 0, void 0, function* () { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { throw new Error('WebSocket is not connected'); } this.ws.send(JSON.stringify(change)); }); } } exports.WebSocketSync = WebSocketSync;