From 8f27000fcf50dcac2ffb6e2096a251ee2299ef5e Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Mon, 9 Feb 2026 23:55:05 +0100 Subject: [PATCH] add RPC to neutralino --- .github/workflows/desktop-build.yml | 10 +- .gitignore | 2 + .../js.neutralino.discordrpc/bridge.ps1 | 91 ++++ extensions/js.neutralino.discordrpc/bridge.py | 142 +++++ js/api.js | 5 - js/app.js | 33 +- js/db.js | 2 - js/discord-rpc.js | 87 ++++ js/player.js | 13 + neutralino.config.json | 20 +- package-lock.json | 489 ++---------------- package.json | 3 +- vite.config.js | 4 +- 13 files changed, 437 insertions(+), 464 deletions(-) create mode 100644 extensions/js.neutralino.discordrpc/bridge.ps1 create mode 100644 extensions/js.neutralino.discordrpc/bridge.py create mode 100644 js/discord-rpc.js diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 2ce5b66..603115b 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -41,21 +41,23 @@ jobs: uses: actions/setup-node@v4 with: node-version: '20' + cache: 'npm' - - name: Install Neutralino CLI - run: npm install -g @neutralinojs/neu + - name: Install dependencies + run: npm install - name: Download Neutralino binaries - run: neu update + run: npx neu update - name: Build application - run: neu build + run: npm run build - name: Prepare Release run: | mkdir release cp dist/Monochrome/resources.neu release/ cp neutralino.config.json release/ + cp -r extensions release/ cp dist/Monochrome/${{ matrix.binary_source }} release/${{ matrix.binary_dest }} shell: bash diff --git a/.gitignore b/.gitignore index 5a88de8..5282d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ bin/ *.log .storage/ auth_storage/ + +www diff --git a/extensions/js.neutralino.discordrpc/bridge.ps1 b/extensions/js.neutralino.discordrpc/bridge.ps1 new file mode 100644 index 0000000..73b074e --- /dev/null +++ b/extensions/js.neutralino.discordrpc/bridge.ps1 @@ -0,0 +1,91 @@ +# bridge.ps1 - JSON Depth Fix +# $Log = Join-Path $PSScriptRoot "bridge_final.log" +function Log($m) { } + +Log "--- START (DEPTH FIX) ---" + +# 1. PID +$p = Get-Process Monochrome -ErrorAction SilentlyContinue | Select-Object -First 1 +if (-not $p) { $p = Get-Process neutralino-win_x64 -ErrorAction SilentlyContinue | Select-Object -First 1 } +$pid_to_send = if ($p) { $p.Id } else { [System.Diagnostics.Process]::GetCurrentProcess().Id } + +# 2. Discord Connection +function Get-Pipe { + for ($i = 0; $i -le 9; $i++) { + try { + $pn = "discord-ipc-$i" + $p = New-Object System.IO.Pipes.NamedPipeClientStream(".", $pn, [System.IO.Pipes.PipeDirection]::InOut) + $p.Connect(100) + return $p + } catch { } + } + return $null +} +$pipe = Get-Pipe; if (-not $pipe) { Log "Discord Fail"; exit } + +function Send-Packet($op, $json) { + if ($op -eq 1) { Log "Sending Activity: $json" } + $j = [System.Text.Encoding]::UTF8.GetBytes($json) + [byte[]]$pkt = [BitConverter]::GetBytes([int]$op) + [BitConverter]::GetBytes([int]$j.Length) + $j + $pipe.Write($pkt, 0, $pkt.Length); $pipe.Flush() +} + +# 3. Handshake +Send-Packet 0 (@{ v = 1; client_id = "1462186088184549661" } | ConvertTo-Json -Compress) +$h = New-Object byte[] 8; if ($pipe.Read($h, 0, 8) -eq 8) { + $l = [BitConverter]::ToInt32($h, 4); $b = New-Object byte[] $l; $pipe.Read($b, 0, $l) | Out-Null + Log "Handshake OK" +} + +function Set-Activity($d, $s, $img) { + $activity = @{ + details = [string]$d + state = [string]$s + type = 2 + assets = @{ + large_image = if ($img -and $img.StartsWith("http")) { [string]$img } else { "monochrome" } + large_text = "Monochrome" + } + } + + # CRITICAL: -Depth 10 ensures 'assets' is not stringified as a class name + $payload = @{ + cmd = "SET_ACTIVITY" + args = @{ pid = [int]$pid_to_send; activity = $activity } + nonce = [Guid]::NewGuid().ToString() + } | ConvertTo-Json -Compress -Depth 10 + + Send-Packet 1 $payload +} + +Start-Sleep -Seconds 1 +Set-Activity "Idling" "Monochrome" $null + +# 4. Config & WS +$line = [Console]::In.ReadLine() +if (-not $line) { exit } +$config = $line | ConvertFrom-Json + +$ws = New-Object System.Net.WebSockets.ClientWebSocket +try { + $uri = [Uri]"ws://127.0.0.1:$($config.nlPort)?extensionId=$($config.nlExtensionId)&connectToken=$($config.nlConnectToken)" + $ws.ConnectAsync($uri, [System.Threading.CancellationToken]::None).Wait() + Log "WS Connected" +} catch { exit } + +# 5. Loop +$buf = New-Object byte[] 65536 +while ($ws.State -eq "Open") { + $task = $ws.ReceiveAsync((New-Object ArraySegment[byte] -ArgumentList @(,$buf)), [System.Threading.CancellationToken]::None) + while (-not $task.Wait(1000)) { if (-not (Get-Process -Id $pid_to_send -ErrorAction SilentlyContinue)) { exit } } + if ($task.Result.Count -gt 0) { + try { + $raw = [System.Text.Encoding]::UTF8.GetString($buf, 0, $task.Result.Count) + $msg = $raw | ConvertFrom-Json + if ($msg.event -eq "discord:update") { + Set-Activity $msg.data.details $msg.data.state $msg.data.largeImageKey + } + elseif ($msg.event -eq "discord:clear") { Set-Activity "Idling" "Monochrome" $null } + } catch {} + } +} diff --git a/extensions/js.neutralino.discordrpc/bridge.py b/extensions/js.neutralino.discordrpc/bridge.py new file mode 100644 index 0000000..9faed82 --- /dev/null +++ b/extensions/js.neutralino.discordrpc/bridge.py @@ -0,0 +1,142 @@ +# bridge.py - Production Discord RPC Bridge (Linux/macOS) +import sys, json, socket, struct, os, uuid, base64, time + +CLIENT_ID = "1462186088184549661" +LAST_STATUS = "" + +def get_discord_path(): + for i in range(10): + path = os.path.join(os.environ.get('XDG_RUNTIME_DIR', '/tmp'), f'discord-ipc-{i}') + if os.path.exists(path): return path + return None + +def send_packet(s, op, data): + payload = json.dumps(data).encode('utf-8') + header = struct.pack('H", ws.recv(2))[0] + elif length == 127: length = struct.unpack(">Q", ws.recv(8))[0] + + data = b"" + while len(data) < length: + data += ws.recv(length - len(data)) + + msg = json.loads(data.decode('utf-8')) + if msg['event'] == 'discord:update': + d = msg['data'] + set_activity(ds, ppid, d.get('details'), d.get('state'), d.get('largeImageKey')) + elif msg['event'] == 'discord:clear': + set_activity(ds, ppid, "Idling", "Monochrome") + elif msg['event'] == 'windowClose': + break + except socket.timeout: continue + except: continue + + # Cleanup + try: + send_packet(ds, 1, { + "cmd": "SET_ACTIVITY", + "args": {"pid": ppid, "activity": None}, + "nonce": str(uuid.uuid4()) + }) + time.sleep(0.1) + ds.close() + except: pass + +if __name__ == "__main__": + main() diff --git a/js/api.js b/js/api.js index 3ad0e65..17dcae7 100644 --- a/js/api.js +++ b/js/api.js @@ -868,7 +868,6 @@ export class LosslessAPI { const seenTrackIds = new Set(tracks.map((t) => t.id)); const artistsToProcess = artists.slice(0, Math.min(5, artists.length)); - console.log(`Processing ${artistsToProcess.length} artists for recommendations`); const artistPromises = artistsToProcess.map(async (artist) => { try { @@ -876,8 +875,6 @@ export class LosslessAPI { const artistData = await this.getArtist(artist.id, { lightweight: true }); if (artistData && artistData.tracks && artistData.tracks.length > 0) { const newTracks = artistData.tracks.filter((track) => !seenTrackIds.has(track.id)).slice(0, 4); - - console.log(`Found ${newTracks.length} new tracks from ${artist.name}`); return newTracks; } else { console.warn(`No tracks found for artist ${artist.name}`); @@ -897,8 +894,6 @@ export class LosslessAPI { } }); - console.log(`Total recommended tracks found: ${recommendedTracks.length}`); - const shuffled = recommendedTracks.sort(() => 0.5 - Math.random()); return shuffled.slice(0, limit); } diff --git a/js/app.js b/js/app.js index e3d4d0b..54f62da 100644 --- a/js/app.js +++ b/js/app.js @@ -13,8 +13,15 @@ import { sidePanelManager } from './side-panel.js'; import { db } from './db.js'; import { syncManager } from './accounts/pocketbase.js'; import { registerSW } from 'virtual:pwa-register'; +import { initializeDiscordRPC } from './discord-rpc.js'; +import * as Neutralino from '@neutralinojs/lib'; import './smooth-scrolling.js'; +// Assign Neutralino to window for global access +if (typeof window !== 'undefined') { + window.Neutralino = Neutralino; +} + // Lazy-loaded modules let settingsModule = null; let downloadsModule = null; @@ -380,18 +387,20 @@ document.addEventListener('DOMContentLoaded', async () => { // Initialize desktop environment (Neutralino) if (window.Neutralino) { - await (async () => { - console.log('Initializing Neutralino desktop environment (Lite Mode)...'); - try { - await Neutralino.init(); - Neutralino.events.on('windowClose', () => { - Neutralino.app.exit(); - }); - console.log('Desktop environment initialized'); - } catch (error) { - console.error('Failed to initialize desktop environment:', error); - } - })(); + console.log('Initializing Neutralino desktop environment (Lite Mode)...'); + try { + Neutralino.init(); + + // Register events immediately + Neutralino.events.on('windowClose', () => { + Neutralino.app.exit(); + }); + + // Start RPC immediately after init + initializeDiscordRPC(player); + } catch (error) { + console.error('Failed to initialize desktop environment:', error); + } } const castBtn = document.getElementById('cast-btn'); diff --git a/js/db.js b/js/db.js index 79466d7..3cda9f3 100644 --- a/js/db.js +++ b/js/db.js @@ -384,8 +384,6 @@ export class MusicDatabase { }); } - console.log(`${storeName}: Adding item with ID ${item.id || item.uuid || item.timestamp}`); - // Critical: Ensure key exists for IndexedDB store.put() const keyPath = store.keyPath; if (keyPath && !item[keyPath]) { diff --git a/js/discord-rpc.js b/js/discord-rpc.js new file mode 100644 index 0000000..0df86fc --- /dev/null +++ b/js/discord-rpc.js @@ -0,0 +1,87 @@ +import { getTrackTitle, getTrackArtists } from './utils.js'; + +export function initializeDiscordRPC(player) { + console.log('[DiscordRPC] Initializing...'); + + const EXTENSION_ID = 'js.neutralino.discordrpc'; + + function sendUpdate(track, isPaused = false) { + if (!track) return; + + let coverUrl = 'monochrome'; + if (track.album?.cover) { + const coverId = track.album.cover.replace(/-/g, '/'); + coverUrl = `https://resources.tidal.com/images/${coverId}/320x320.jpg`; + } + + const data = { + details: getTrackTitle(track), + state: getTrackArtists(track), + largeImageKey: coverUrl, + largeImageText: track.album?.title || 'Monochrome', + smallImageKey: isPaused ? 'pause' : 'play', + smallImageText: isPaused ? 'Paused' : 'Playing', + instance: false, + }; + + if (!isPaused && track.duration) { + const now = Date.now(); + const elapsed = player.audio.currentTime * 1000; + data.startTimestamp = Math.floor((now - elapsed) / 1000); + } + + console.log('[DiscordRPC] Dispatching to', EXTENSION_ID, data); + Neutralino.events.broadcast('discord:update', data).catch(e => console.error('Broadcast failed', e)); + Neutralino.extensions.dispatch(EXTENSION_ID, 'discord:update', data).catch(e => console.error('Dispatch failed', e)); + } + + // Heartbeat & Debug Ping + setInterval(() => { + if (player.currentTrack) { + sendUpdate(player.currentTrack, player.audio.paused); + } else { + const idlingData = { + details: 'Idling', + state: 'Monochrome', + largeImageKey: 'monochrome', + largeImageText: 'Monochrome', + smallImageKey: 'pause', + smallImageText: 'Paused' + }; + Neutralino.events.broadcast('discord:update', idlingData).catch(() => { }); + Neutralino.extensions.dispatch(EXTENSION_ID, 'discord:update', idlingData).catch(() => { }); + } + }, 5000); + + function sendClear() { + Neutralino.events.broadcast('discord:clear', {}).catch(() => { }); + } + + player.audio.addEventListener('play', () => { + sendUpdate(player.currentTrack); + }); + + player.audio.addEventListener('pause', () => { + sendUpdate(player.currentTrack, true); + }); + + player.audio.addEventListener('loadedmetadata', () => { + if (!player.audio.paused) { + sendUpdate(player.currentTrack); + } + }); + + // Send initial status + if (player.currentTrack) { + sendUpdate(player.currentTrack, player.audio.paused); + } else { + Neutralino.events.broadcast('discord:update', { + details: 'Idling', + state: 'Monochrome', + largeImageKey: 'monochrome', + largeImageText: 'Monochrome', + smallImageKey: 'pause', + smallImageText: 'Paused' + }).catch(() => { }); + } +} diff --git a/js/player.js b/js/player.js index 6e46246..643a805 100644 --- a/js/player.js +++ b/js/player.js @@ -402,6 +402,7 @@ export class Player { this.updatePlayingTrackIndicator(); this.updateMediaSession(track); this.updateMediaSessionPlaybackState(); + this.updateNativeWindow(track); try { let streamUrl; @@ -1038,4 +1039,16 @@ export class Player { updateBtn(timerBtn); updateBtn(timerBtnDesktop); } + + async updateNativeWindow(track) { + if (!window.Neutralino) return; + + const trackTitle = getTrackTitle(track); + const artist = getTrackArtists(track); + try { + await Neutralino.window.setTitle(`${trackTitle} • ${artist}`); + } catch (e) { + console.error('Failed to set window title:', e); + } + } } diff --git a/neutralino.config.json b/neutralino.config.json index 06a4e3e..f9960eb 100644 --- a/neutralino.config.json +++ b/neutralino.config.json @@ -6,10 +6,11 @@ "description": "Lossless music streaming", "version": "1.0.0", "defaultMode": "window", - "documentRoot": "public/", + "documentRoot": "www/", "url": "https://monochrome.tf", - "enableServer": false, + "enableServer": true, "enableNativeAPI": true, + "enableExtensions": true, "tokenSecurity": "one-time", "modes": { "window": { @@ -28,14 +29,25 @@ "exitProcessOnClose": true } }, + "port": 5050, "cli": { "binaryName": "Monochrome", - "resourcesPath": "public/", + "resourcesPath": "www/", "binaryVersion": "6.5.0", "clientVersion": "6.5.0" }, + "extensions": [ + { + "id": "js.neutralino.discordrpc", + "commandLinux": "python3 \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.py\"", + "commandMac": "python3 \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.py\"", + "commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\"" + } + ], "nativeAllowList": [ "app.exit", - "window.*" + "window.*", + "extensions.*", + "events.*" ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 189bc6b..088afe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ }, "devDependencies": { "@neutralinojs/neu": "^11.7.0", - "concurrently": "^9.2.1", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "globals": "^17.0.0", @@ -29,8 +28,8 @@ "stylelint-config-standard": "^39.0.1", "stylelint-config-standard-scss": "^16.0.0", "vite": "^7.3.0", - "vite-plugin-pwa": "^1.2.0", - "wait-on": "^9.0.3" + "vite-plugin-neutralino": "^1.0.3", + "vite-plugin-pwa": "^1.2.0" } }, "node_modules/@apideck/better-ajv-errors": { @@ -2488,60 +2487,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@hapi/address": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", - "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@hapi/formula": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", - "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/hoek": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", - "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/pinpoint": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", - "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/tlds": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.4.tgz", - "integrity": "sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@hapi/topo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", - "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^11.0.2" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -3200,13 +3145,6 @@ "win32" ] }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -3530,13 +3468,6 @@ "node": ">= 0.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -3563,18 +3494,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", @@ -3758,6 +3677,20 @@ "dev": true, "license": "MIT" }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/butterchurn": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/butterchurn/-/butterchurn-2.6.7.tgz", @@ -3933,100 +3866,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/codem-isoboxer": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.10.tgz", @@ -4060,19 +3899,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -4097,47 +3923,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concurrently": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "4.1.2", - "rxjs": "7.8.2", - "shell-quote": "1.8.3", - "supports-color": "8.1.1", - "tree-kill": "1.2.2", - "yargs": "17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -4567,6 +4352,20 @@ "integrity": "sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q==", "license": "MIT" }, + "node_modules/edit-json-file": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/edit-json-file/-/edit-json-file-1.8.1.tgz", + "integrity": "sha512-x8L381+GwqxQejPipwrUZIyAg5gDQ9tLVwiETOspgXiaQztLsrOm7luBW5+Pe31aNezuzDY79YyzF+7viCRPXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-value": "^1.0.12", + "iterate-object": "^1.3.4", + "r-json": "^1.2.10", + "set-value": "^4.1.0", + "w-json": "^1.3.10" + } + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -5394,23 +5193,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -5510,16 +5292,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -7072,29 +6844,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", @@ -7111,16 +6860,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -7281,6 +7020,16 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7721,13 +7470,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7958,16 +7700,6 @@ "regjsparser": "bin/parser" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8121,16 +7853,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -8336,19 +8058,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -9290,6 +8999,13 @@ "node": ">=0.6.x" } }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true, + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -9675,6 +9391,13 @@ } } }, + "node_modules/vite-plugin-neutralino": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vite-plugin-neutralino/-/vite-plugin-neutralino-1.0.3.tgz", + "integrity": "sha512-E/PSTCp7m7efk7fa4eE12WQ+5XGNP72gkhOv61X4i0n+NcnrwKtJgEgeCDGUXeLp4ngTfU/CP+EF55jmZs/0Jw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite-plugin-pwa": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz", @@ -9713,26 +9436,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wait-on": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.3.tgz", - "integrity": "sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^1.13.2", - "joi": "^18.0.1", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "rxjs": "^7.8.2" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/websocket": { "version": "1.0.35", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", @@ -10349,16 +10052,6 @@ "dev": true, "license": "MIT" }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", @@ -10377,80 +10070,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yauzl": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", diff --git a/package.json b/package.json index 6ea90f9..384da2c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "sw.js", "scripts": { "dev": "vite", - "build": "vite build", + "build": "vite build && npx neu build", "preview": "vite preview", "start": "vite preview", "lint:js": "eslint .", @@ -38,6 +38,7 @@ "stylelint-config-standard": "^39.0.1", "stylelint-config-standard-scss": "^16.0.0", "vite": "^7.3.0", + "vite-plugin-neutralino": "^1.0.3", "vite-plugin-pwa": "^1.2.0" }, "overrides": { diff --git a/vite.config.js b/vite.config.js index 53b6e30..e631736 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,14 +1,16 @@ import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; +import neutralino from 'vite-plugin-neutralino'; import authGatePlugin from './vite-plugin-auth-gate.js'; export default defineConfig({ base: './', build: { - outDir: 'dist', + outDir: 'www', emptyOutDir: true, }, plugins: [ + neutralino(), authGatePlugin(), VitePWA({ registerType: 'prompt',