From 619f50d794ca8645d46b066279b6e0fdddd6ca3c Mon Sep 17 00:00:00 2001 From: pavel Date: Thu, 26 Feb 2026 23:36:27 +0100 Subject: [PATCH 1/3] fix slider --- desktop/app.js | 12 ++++++++---- static/app.js | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/desktop/app.js b/desktop/app.js index a0d5a5a..0cb3112 100644 --- a/desktop/app.js +++ b/desktop/app.js @@ -28,6 +28,7 @@ const state = { viewMode: 'chat', // 'chat' or 'video' iceServers: [{ urls: "stun:stun.l.google.com:19302" }], peerGainNodes: new Map(), // userId -> GainNode + visibleVolumeSliders: new Set(), // userIds whose sliders are visible }, voicePresencePollId: null, onlineUsers: new Set(), @@ -416,8 +417,9 @@ function renderChannels() { let sliderHtml = ''; if (p.user_id !== state.me.id) { const vol = state.userVolumes.get(p.user_id) ?? 1.0; + const isVisible = state.voice.visibleVolumeSliders.has(p.user_id); sliderHtml = ` -
+
${Math.round(vol * 100)}% @@ -454,10 +456,12 @@ function renderChannels() { pRow.addEventListener('contextmenu', (e) => { if (p.user_id === state.me.id) return; e.preventDefault(); - const control = pRow.querySelector('.user-volume-control'); - if (control) { - control.classList.toggle('show-volume'); + if (state.voice.visibleVolumeSliders.has(p.user_id)) { + state.voice.visibleVolumeSliders.delete(p.user_id); + } else { + state.voice.visibleVolumeSliders.add(p.user_id); } + renderChannels(); }); pList.appendChild(pRow); diff --git a/static/app.js b/static/app.js index c07063f..c76a314 100644 --- a/static/app.js +++ b/static/app.js @@ -28,6 +28,7 @@ const state = { viewMode: 'chat', // 'chat' or 'video' iceServers: [{ urls: "stun:stun.l.google.com:19302" }], peerGainNodes: new Map(), // userId -> GainNode + visibleVolumeSliders: new Set(), // userIds whose sliders are currently visible }, voicePresencePollId: null, chatWs: null, @@ -371,8 +372,9 @@ function renderChannels() { let sliderHtml = ''; if (p.user_id !== state.me.id) { const vol = state.userVolumes.get(p.user_id) ?? 1.0; + const isVisible = state.voice.visibleVolumeSliders.has(p.user_id); sliderHtml = ` -
+
${Math.round(vol * 100)}% @@ -409,10 +411,12 @@ function renderChannels() { pRow.addEventListener('contextmenu', (e) => { if (p.user_id === state.me.id) return; e.preventDefault(); - const control = pRow.querySelector('.user-volume-control'); - if (control) { - control.classList.toggle('show-volume'); + if (state.voice.visibleVolumeSliders.has(p.user_id)) { + state.voice.visibleVolumeSliders.delete(p.user_id); + } else { + state.voice.visibleVolumeSliders.add(p.user_id); } + renderChannels(); }); pList.appendChild(pRow); From 8847f3fcf6655fa6aca0adeef06051b0053ebf1d Mon Sep 17 00:00:00 2001 From: pavel Date: Thu, 26 Feb 2026 23:43:43 +0100 Subject: [PATCH 2/3] refresh browser on connection loss --- static/app.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/static/app.js b/static/app.js index c76a314..89614a0 100644 --- a/static/app.js +++ b/static/app.js @@ -698,8 +698,9 @@ function initChatWs() { }; ws.onclose = () => { - console.log("Chat WS closed, reconnecting..."); - setTimeout(initChatWs, 3000); + console.log("Chat WS closed, refreshing page for resilience..."); + // Automatically refresh on connection loss instead of just background reconnecting + location.reload(); }; } @@ -1187,6 +1188,7 @@ async function joinVoice() { state.voice.ws = ws; state.voice.joinedChannelId = state.selectedVoiceChannelId; + localStorage.setItem("active_voice_channel", state.selectedVoiceChannelId); // Start mic setup in parallel so channel join is not blocked by device init. createLocalVoiceStream() @@ -1318,6 +1320,7 @@ async function joinVoice() { } async function leaveVoice() { + localStorage.removeItem("active_voice_channel"); if (state.voice.ws) state.voice.ws.close(); } @@ -1900,6 +1903,18 @@ async function init() { initChatWs(); startVoicePresencePolling(); + + // Auto-reconnect to voice if previously joined + const savedVoiceChannel = localStorage.getItem("active_voice_channel"); + if (savedVoiceChannel) { + console.log("Auto-reconnecting to voice channel:", savedVoiceChannel); + state.selectedVoiceChannelId = savedVoiceChannel; + // Wait for components to be ready + setTimeout(() => { + joinVoice().catch(err => console.error("Auto-reconnect failed", err)); + }, 800); + } + lucide.createIcons(); } catch (err) { console.error("init failed", err); From 832de038ca54570ccfc3fbb785c162844a61919b Mon Sep 17 00:00:00 2001 From: pavel Date: Thu, 26 Feb 2026 23:58:11 +0100 Subject: [PATCH 3/3] auto update desktop app --- .forgejo/workflows/pipeline.yaml | 22 +++ desktop/app.js | 322 +++++++++++++++++++++++++++++++ desktop/index.html | 7 +- desktop/preload.js | 8 +- desktop/styles.css | 29 +++ main.js | 39 ++++ package-lock.json | 105 +++++++++- package.json | 3 + 8 files changed, 526 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/pipeline.yaml b/.forgejo/workflows/pipeline.yaml index 30aa0d7..9e241e8 100644 --- a/.forgejo/workflows/pipeline.yaml +++ b/.forgejo/workflows/pipeline.yaml @@ -11,6 +11,25 @@ jobs: with: node-version: 24 - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Update version from tag + run: | + # Extract version from tag (e.g. v1.2.3 -> 1.2.3) + VERSION=${GITHUB_REF_NAME#v} + echo "Bumping version to $VERSION" + npm version $VERSION --no-git-tag-version + + # Configure Git + git config user.name "Forgejo Actions" + git config user.email "actions@noreply.flegr.me" + + # Commit and push back to main if version changed + if [ -n "$(git status --porcelain package.json)" ]; then + git add package.json package-lock.json + git commit -m "chore: bump version to $VERSION [skip ci]" + git push origin HEAD:main + fi - run: npm ci - run: npm run dist:linux - name: Build Windows installer with 32-bit Wine prefix @@ -36,6 +55,9 @@ jobs: copy_first '*.AppImage' 'chattz-linux.AppImage' copy_first '*.exe' 'chattz-windows.exe' copy_first '*.msi' 'chattz-windows.msi' + # Metadata files for electron-updater + copy_first 'latest-linux.yml' 'latest-linux.yml' + copy_first 'latest.yml' 'latest.yml' - name: Cache Cargo registry uses: actions/cache@v4 with: diff --git a/desktop/app.js b/desktop/app.js index 0cb3112..d260cfa 100644 --- a/desktop/app.js +++ b/desktop/app.js @@ -112,6 +112,9 @@ const el = { userName: document.getElementById("user-name"), userAvatar: document.getElementById("user-avatar"), logoutBtn: document.getElementById("logout-btn"), + updateNotifier: document.getElementById("update-notifier"), + updateDownloadBtn: document.getElementById("update-download-btn"), + updateInstallBtn: document.getElementById("update-install-btn"), // Voice Connection voiceConnection: document.getElementById("voice-connection"), @@ -1940,6 +1943,324 @@ async function init() { await loadGuilds(); await loadDMConversations(); + } catch (err) { + console.error("presence sync failed", err); + } +} + +function initUpdater() { + if (!window.electronAPI) return; + + window.electronAPI.onUpdateAvailable((info) => { + console.log("Update available:", info.version); + el.updateNotifier.classList.remove("hidden"); + el.updateDownloadBtn.classList.remove("hidden"); + el.updateInstallBtn.classList.add("hidden"); + }); + + window.electronAPI.onUpdateDownloaded((info) => { + console.log("Update downloaded:", info.version); + el.updateNotifier.classList.remove("hidden"); + el.updateDownloadBtn.classList.add("hidden"); + el.updateInstallBtn.classList.remove("hidden"); + el.updateInstallBtn.title = `Install Update ${info.version}`; + }); + + window.electronAPI.onUpdateError((err) => { + console.error("Update error:", err); + // Optionally hide indicator on error + // el.updateNotifier.classList.add("hidden"); + }); + + el.updateDownloadBtn.onclick = () => { + el.updateDownloadBtn.disabled = true; + el.updateDownloadBtn.style.opacity = "0.5"; + window.electronAPI.downloadUpdate(); + }; + + el.updateInstallBtn.onclick = () => { + window.electronAPI.quitAndInstall(); + }; +} + +async function init() { + lucide.createIcons(); + + try { + const searchParams = new URLSearchParams(location.search); + const jwtToken = searchParams.get("token"); + const refreshToken = searchParams.get("refresh_token"); + + if (jwtToken || refreshToken) { + if (jwtToken) localStorage.setItem("chattz_token", jwtToken); + if (refreshToken) localStorage.setItem("chattz_refresh_token", refreshToken); + + searchParams.delete("token"); + searchParams.delete("refresh_token"); + const nextQuery = searchParams.toString(); + const nextUrl = `${location.pathname}${nextQuery ? `?${nextQuery}` : ""}`; + history.replaceState(null, "", nextUrl); + } + } catch (e) { + console.error("Failed to parse token from URL:", e); + } + + el.loginBtn.onclick = () => { location.href = "/auth/login"; }; + + el.logoutBtn.onclick = async () => { + await leaveVoice(); + localStorage.removeItem("chattz_token"); + localStorage.removeItem("chattz_refresh_token"); + try { await api("/auth/logout", { method: "POST" }); } catch (e) { } + location.reload(); + }; + + el.addGuildBtn.onclick = () => { el.modalContainer.classList.remove("hidden"); }; + el.createInviteBtn.onclick = createInviteLink; + el.modalCancel.onclick = () => { el.modalContainer.classList.add("hidden"); }; + + el.addTextBtn.onclick = () => { + if (!state.selectedGuildId) { + alert("Create or select a server first."); + return; + } + el.modalTitle.textContent = "Create Text Channel"; + el.channelModal.classList.remove("hidden"); + }; + + el.addVoiceBtn.onclick = () => { + if (!state.selectedGuildId) { + alert("Create or select a server first."); + return; + } + el.modalTitle.textContent = "Create Voice Channel"; + el.channelModal.classList.remove("hidden"); + }; + + el.channelModalCancel.onclick = () => { el.channelModal.classList.add("hidden"); }; + + el.guildForm.onsubmit = async (e) => { + e.preventDefault(); + const name = el.guildName.value.trim(); + if (!name) return; + try { + const guild = await api("/guilds", { + method: "POST", + body: JSON.stringify({ name }), + }); + el.guildName.value = ""; + el.modalContainer.classList.add("hidden"); + state.guilds.push(guild); + state.selectedGuildId = guild.id; + state.selectedTextChannelId = null; + state.selectedVoiceChannelId = null; + state.selectedDmUserId = null; + state.selectedDmDisplayName = null; + localStorage.setItem(LAST_GUILD_STORAGE_KEY, guild.id); + renderGuilds(); + await loadChannels(); + await loadGuildMembers(); + await refreshVoicePresence(); + renderMessages([]); + updateHeaderLabels(); + } catch (err) { alert(err.message); } + }; + + el.channelForm.onsubmit = async (e) => { + e.preventDefault(); + if (!state.selectedGuildId) { + alert("Select a server first."); + return; + } + const kindInput = document.querySelector('input[name="channel-kind"]:checked'); + const kind = kindInput ? kindInput.value : "text"; + const channelName = el.channelName.value.trim(); + if (!channelName) { + alert("Channel name is required."); + return; + } + try { + const created = await api("/channels", { + method: "POST", + body: JSON.stringify({ + guild_id: state.selectedGuildId, + name: channelName, + kind: kind, + }), + }); + el.channelName.value = ""; + el.channelModal.classList.add("hidden"); + await loadChannels(); + if (created.kind === "text") { + state.selectedTextChannelId = created.id; + state.selectedVoiceChannelId = null; + state.selectedDmUserId = null; + state.selectedDmDisplayName = null; + renderChannels(); + renderDMs(); + updateHeaderLabels(); + await api(`/channels/${created.id}/messages?limit=100`).then(renderMessages); + } else { + state.selectedVoiceChannelId = created.id; + state.selectedTextChannelId = null; + state.selectedDmUserId = null; + state.selectedDmDisplayName = null; + renderChannels(); + renderDMs(); + updateHeaderLabels(); + await joinVoice(); + } + } catch (err) { alert(err.message); } + }; + + el.messageForm.onsubmit = async (e) => { + e.preventDefault(); + const body = el.messageBody.value.trim(); + if (!body) return; + try { + if (state.selectedTextChannelId) { + await api(`/channels/${state.selectedTextChannelId}/messages`, { + method: "POST", + body: JSON.stringify({ body }), + }); + } else if (state.selectedDmUserId) { + await api(`/dms/${state.selectedDmUserId}/messages`, { + method: "POST", + body: JSON.stringify({ body }), + }); + } else { + return; + } + el.messageBody.value = ""; + const messages = state.selectedTextChannelId + ? await api(`/channels/${state.selectedTextChannelId}/messages?limit=100`) + : await api(`/dms/${state.selectedDmUserId}/messages?limit=100`); + renderMessages(messages); + await loadDMConversations(); + renderDMs(); + } catch (err) { alert(err.message); } + }; + + el.voiceVideoBtn.onclick = toggleVideo; + el.voiceScreenBtn.onclick = toggleScreenShare; + el.voiceMuteBtn.onclick = toggleMute; + el.voiceLeaveBtn.onclick = leaveVoice; + + el.addSoundBtn.onclick = () => { + el.soundModal.classList.remove("hidden"); + }; + el.soundModalCancel.onclick = () => { + el.soundModal.classList.add("hidden"); + }; + + el.soundForm.onsubmit = async (e) => { + e.preventDefault(); + if (!state.selectedGuildId) return; + + const formData = new FormData(); + formData.append('name', el.soundName.value); + formData.append('icon', el.soundIcon.value); + formData.append('file', el.soundFile.files[0]); + + el.soundSubmitBtn.disabled = true; + el.soundSubmitBtn.textContent = 'Uploading...'; + + try { + // Need to use fetch directly because api() might not handle FormData correctly depending on implementation + const response = await fetch(`/guilds/${state.selectedGuildId}/sounds`, { + method: 'POST', + body: formData + }); + if (!response.ok) { + const err = await response.json(); + throw new Error(err.error || 'Upload failed'); + } + el.soundModal.classList.add("hidden"); + el.soundForm.reset(); + await loadSounds(); + } catch (err) { + alert(err.message); + } finally { + el.soundSubmitBtn.disabled = false; + el.soundSubmitBtn.textContent = 'Add Sound'; + } + }; + + // --- GIF Picker Logic --- + const TENOR_API_KEY = "exTiFGKJ0CzESIHzVQWy3pRO8I1MAdpRomg95DBSu2sg6e7YcHgThMI4giGAx8D0"; // Placeholder / Public key if available, otherwise "YOUR_API_KEY" + const TENOR_CLIENT_KEY = "pavel-discord"; + + el.gifBtn.onclick = () => { + el.gifPicker.classList.remove("hidden"); + searchGifs(""); // Initial featured search + }; + + el.gifPickerClose.onclick = () => { + el.gifPicker.classList.add("hidden"); + }; + + let searchTimeout = null; + el.gifSearchInput.oninput = () => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + searchGifs(el.gifSearchInput.value); + }, 500); + }; + + async function searchGifs(query) { + el.gifResults.innerHTML = '
Searching...
'; + const baseUrl = "https://api.klipy.com/v2"; + const endpoint = query + ? `${baseUrl}/search?q=${encodeURIComponent(query)}&key=${TENOR_API_KEY}&client_key=${TENOR_CLIENT_KEY}&limit=20` + : `${baseUrl}/featured?key=${TENOR_API_KEY}&client_key=${TENOR_CLIENT_KEY}&limit=20`; + + try { + const res = await fetch(endpoint); + const data = await res.json(); + renderGifs(data.results); + } catch (err) { + el.gifResults.innerHTML = '
Error loading GIFs
'; + } + } + + function renderGifs(gifs) { + el.gifResults.innerHTML = ""; + if (!gifs || gifs.length === 0) { + el.gifResults.innerHTML = '
No GIFs found
'; + return; + } + + gifs.forEach(gif => { + // Use tinygif for preview, standard gif for sending + const previewUrl = gif.media_formats.tinygif.url; + const fullUrl = gif.media_formats.gif.url; + + const item = document.createElement("div"); + item.className = "gif-item"; + + const img = document.createElement("img"); + img.src = previewUrl; + img.loading = "lazy"; + img.onclick = () => { + el.messageBody.value = fullUrl; + el.gifPicker.classList.add("hidden"); + el.messageForm.dispatchEvent(new Event('submit')); + }; + + item.appendChild(img); + el.gifResults.appendChild(item); + }); + } + + try { + state.me = await api("/me"); + el.userName.textContent = state.me.display_name; + el.userAvatar.textContent = state.me.display_name[0].toUpperCase(); + + state.guilds = await api("/guilds"); + renderGuilds(); + + await loadDMConversations(); try { const online = await api("/presence"); @@ -1964,6 +2285,7 @@ async function init() { initChatWs(); startVoicePresencePolling(); + initUpdater(); lucide.createIcons(); } catch (err) { console.error("init failed", err); diff --git a/desktop/index.html b/desktop/index.html index 28521c9..ac9fa8d 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -114,6 +114,11 @@
Online
@@ -263,4 +268,4 @@ - + \ No newline at end of file diff --git a/desktop/preload.js b/desktop/preload.js index 4c4f19c..ae9d90e 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -2,5 +2,11 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { copyToClipboard: (text) => ipcRenderer.invoke('clipboard-write', text), - getConfig: () => ipcRenderer.invoke('get-config') + getConfig: () => ipcRenderer.invoke('get-config'), + checkForUpdates: () => ipcRenderer.send('check-for-updates'), + downloadUpdate: () => ipcRenderer.send('download-update'), + quitAndInstall: () => ipcRenderer.send('quit-and-install'), + onUpdateAvailable: (callback) => ipcRenderer.on('update-available', (event, info) => callback(info)), + onUpdateDownloaded: (callback) => ipcRenderer.on('update-downloaded', (event, info) => callback(info)), + onUpdateError: (callback) => ipcRenderer.on('update-error', (event, error) => callback(error)) }); diff --git a/desktop/styles.css b/desktop/styles.css index 5d4e40a..fda1104 100644 --- a/desktop/styles.css +++ b/desktop/styles.css @@ -621,6 +621,35 @@ select { box-shadow: 0 0 4px var(--brand-glow); } +.update-notifier { + display: flex; + align-items: center; + margin-right: 8px; +} + +.update-notifier button { + background: none; + border: none; + padding: 4px; + color: var(--brand); + cursor: pointer; + transition: transform 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.update-notifier button:hover { + transform: scale(1.2); + color: var(--brand-glow); +} + +.update-notifier button i { + width: 16px; + height: 16px; +} + + .user-info { flex: 1; min-width: 0; diff --git a/main.js b/main.js index 96e11b4..0f0878d 100644 --- a/main.js +++ b/main.js @@ -1,4 +1,5 @@ const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron'); +const { autoUpdater } = require('electron-updater'); const path = require('path'); const url = require('url'); @@ -121,6 +122,44 @@ app.commandLine.appendSwitch('log-level', '3'); // Silences standard Chromium wa app.whenReady().then(() => { createWindow(); + // Configure Auto-Updater + autoUpdater.autoDownload = false; + autoUpdater.logger = console; + + autoUpdater.on('update-available', (info) => { + const wins = BrowserWindow.getAllWindows(); + if (wins.length > 0) wins[0].webContents.send('update-available', info); + }); + + autoUpdater.on('update-downloaded', (info) => { + const wins = BrowserWindow.getAllWindows(); + if (wins.length > 0) wins[0].webContents.send('update-downloaded', info); + }); + + autoUpdater.on('error', (err) => { + const wins = BrowserWindow.getAllWindows(); + if (wins.length > 0) wins[0].webContents.send('update-error', err.message); + }); + + ipcMain.on('check-for-updates', () => { + autoUpdater.checkForUpdatesAndNotify().catch(err => { + console.error("Manual update check failed", err); + }); + }); + + ipcMain.on('download-update', () => { + autoUpdater.downloadUpdate(); + }); + + ipcMain.on('quit-and-install', () => { + autoUpdater.quitAndInstall(); + }); + + // Check once on startup + setTimeout(() => { + autoUpdater.checkForUpdatesAndNotify().catch(() => { }); + }, 5000); + app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); diff --git a/package-lock.json b/package-lock.json index 7efcf1c..7c39f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "chattz-electron", "version": "0.1.0", + "dependencies": { + "electron-updater": "^6.8.3" + }, "devDependencies": { "electron": "^34.5.8", "electron-builder": "^25.1.8" @@ -1091,7 +1094,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/assert-plus": { @@ -1846,7 +1848,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2369,6 +2370,82 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-updater": { + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz", + "integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.5.1", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "~7.7.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", + "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2904,7 +2981,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -3279,7 +3355,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3347,7 +3422,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true, "license": "MIT" }, "node_modules/lazystream": { @@ -3423,6 +3497,12 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -3431,6 +3511,13 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -3787,7 +3874,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -4456,7 +4542,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -4872,6 +4957,12 @@ "node": ">= 10.0.0" } }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", diff --git a/package.json b/package.json index bd50d6c..ef4716e 100644 --- a/package.json +++ b/package.json @@ -50,5 +50,8 @@ "devDependencies": { "electron": "^34.5.8", "electron-builder": "^25.1.8" + }, + "dependencies": { + "electron-updater": "^6.8.3" } }