zeno-db
Version:
A lightweight, offline-first client-side database with automatic sync capabilities
217 lines (216 loc) • 9.11 kB
JavaScript
"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;