const axios = require("axios"); const WebSocket = require("ws"); const EventEmitter = require("events"); const API_BASE_URL = "https://kvs.wireway.ch/v2"; class WireKVSDatabase extends EventEmitter { constructor(id, accessKey) { super(); this.id = id; this.accessKey = accessKey; this.ws = null; this.isConnected = false; this.keepAliveInterval = null; this._eventHandlers = new Map(); this._setupWebSocket(); } _setupWebSocket() { const wsUrl = `wss://kvs.wireway.ch/events/${ this.id }?accessKey=${encodeURIComponent(this.accessKey)}`; this.ws = new WebSocket(wsUrl); this.ws.on("open", () => { this.isConnected = true; this.emit("connected"); }); this.ws.on("close", () => { this.isConnected = false; this.emit("disconnected"); if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; } if (!this._disconnecting) { setTimeout(() => this._setupWebSocket(), 5000); } }); this.ws.on("message", (data) => { try { const message = JSON.parse(data); if (message.type === "ping") { return; } if (message.type === "error") { console.error("WebSocket error from server:", message.message); this.emit("error", new Error(message.message)); if ( message.message === "KVSDB not found" || message.message === "Forbidden" ) { this.disconnect(); return; } } this.emit(message.type, message); } catch (error) { console.error("Error parsing WebSocket message:", error); } }); this.ws.on("error", (error) => { console.error("WebSocket error:", error); this.emit("error", error); }); } on(event, handler) { super.on(event, handler); if (!this._eventHandlers.has(event)) { this._eventHandlers.set(event, new Set()); } this._eventHandlers.get(event).add(handler); return this; } off(event, handler) { if (handler) { super.removeListener(event, handler); const handlers = this._eventHandlers.get(event); if (handlers) { handlers.delete(handler); } } else { const handlers = this._eventHandlers.get(event); if (handlers) { handlers.forEach((h) => super.removeListener(event, h)); handlers.clear(); } } return this; } disconnect() { this._disconnecting = true; this._eventHandlers.forEach((handlers, event) => { handlers.forEach((handler) => super.removeListener(event, handler)); }); this._eventHandlers.clear(); if (this.ws) { if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval); this.keepAliveInterval = null; } this.ws.close(); this.ws = null; } this.isConnected = false; } async getAllEntries() { const response = await axios.get(`${API_BASE_URL}/database/${this.id}`, { headers: { Authorization: this.accessKey }, }); return response.data; } async get(key) { const response = await axios.get( `${API_BASE_URL}/database/${this.id}/${key}`, { headers: { Authorization: this.accessKey }, } ); return response.data; } async set(key, value) { await axios.post(`${API_BASE_URL}/database/${this.id}/${key}`, value, { headers: { Authorization: this.accessKey }, }); } async update(key, value) { await axios.patch(`${API_BASE_URL}/database/${this.id}/${key}`, value, { headers: { Authorization: this.accessKey }, }); } async delete(key) { await axios.delete(`${API_BASE_URL}/database/${this.id}/${key}`, { headers: { Authorization: this.accessKey }, }); } async search(query) { const response = await axios.post( `${API_BASE_URL}/databaseSearch/${this.id}`, query, { headers: { Authorization: this.accessKey }, } ); return response.data; } } class WireKVS { constructor(token) { this.token = token; } static connect(id, accessKey) { return new WireKVSDatabase(id, accessKey); } async listDatabases() { const response = await axios.get(`${API_BASE_URL}/databases`, { headers: { Authorization: this.token }, }); return response.data; } async createDatabase({ name, allowPublicWrites = false, allowPublicReads = false, allowPublicModifications = false, allowSpecificPublicReads = false, }) { const response = await axios.post( `${API_BASE_URL}/database`, { name, allowPublicWrites, allowPublicReads, allowPublicModifications, allowSpecificPublicReads, }, { headers: { Authorization: this.token }, } ); return response.data; } async updateDatabase( id, { name, allowPublicWrites, allowPublicReads, allowPublicModifications, allowSpecificPublicReads, } ) { const response = await axios.patch( `${API_BASE_URL}/database/${id}`, { name, allowPublicWrites, allowPublicReads, allowPublicModifications, allowSpecificPublicReads, }, { headers: { Authorization: this.token }, } ); return response.data; } async deleteDatabase(id) { await axios.delete(`${API_BASE_URL}/database/${id}`, { headers: { Authorization: this.token }, }); } async truncateDatabase(id) { await axios.delete(`${API_BASE_URL}/databaseTruncate/${id}`, { headers: { Authorization: this.token }, }); } database(id, accessKey) { return new WireKVSDatabase(id, accessKey); } } module.exports = WireKVS;