/** * Web/Capacitor platform implementation. * Uses Web Crypto API, localStorage, and Page Visibility API. */ import crypto from './crypto.js'; import session from './session.js'; import settings from './settings.js'; import idle from './idle.js'; import searchStorage from './searchStorage.js'; import SearchDatabase from '@discord-clone/shared/src/utils/SearchDatabase'; const searchDB = new SearchDatabase(searchStorage, crypto); const webPlatform = { crypto, session, settings, idle, links: { openExternal(url) { window.open(url, '_blank', 'noopener,noreferrer'); }, async fetchMetadata(url) { // On web, metadata fetching would hit CORS. Use a Convex action or proxy instead. // Return null to gracefully skip link previews that require server-side fetching. return null; }, }, screenCapture: { async getScreenSources() { // Web uses getDisplayMedia directly (no source picker like Electron). // Return empty array; the web UI should call navigator.mediaDevices.getDisplayMedia() directly. return []; }, }, windowControls: null, updates: null, voiceService: null, systemBars: null, searchDB, features: { hasWindowControls: false, hasScreenCapture: true, hasNativeUpdates: false, hasSearch: true, hasVoiceService: false, hasSystemBars: false, }, }; // Detect Android/Capacitor and enable native APK updates if (window.Capacitor?.isNativePlatform?.()) { const YAML_URL = 'https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest/latest-android.yml'; const AppUpdater = window.Capacitor.Plugins.AppUpdater; let apkUrl; webPlatform.updates = { async checkUpdate() { try { const response = await fetch(YAML_URL); if (!response.ok) { console.log('[UpdateCheck] Fetch failed:', response.status); return { updateAvailable: false }; } const yaml = await response.text(); const versionMatch = yaml.match(/^version:\s*(.+)$/m); if (!versionMatch) { console.log('[UpdateCheck] No version found in YAML'); return { updateAvailable: false }; } const latestVersion = versionMatch[1].trim(); const pathMatch = yaml.match(/^path:\s*(.+)$/m); const apkFilename = pathMatch ? pathMatch[1].trim() : `DiscordClone-v${latestVersion}.apk`; apkUrl = `https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest/${apkFilename}`; const { version: currentVersion } = await AppUpdater.getVersion(); console.log('[UpdateCheck] Latest:', latestVersion, '| Current:', currentVersion); const latest = latestVersion.split('.').map(Number); const current = currentVersion.split('.').map(Number); let updateAvailable = false; let updateType = 'patch'; for (let i = 0; i < Math.max(latest.length, current.length); i++) { const l = latest[i] || 0; const c = current[i] || 0; if (l > c) { updateAvailable = true; updateType = i === 0 ? 'major' : i === 1 ? 'minor' : 'patch'; break; } if (l < c) break; } console.log('[UpdateCheck] Result:', updateAvailable ? updateType + ' update available' : 'up to date'); return { updateAvailable, updateType, latestVersion, currentVersion, apkUrl }; } catch (e) { console.log('[UpdateCheck] Error:', e.message); return { updateAvailable: false }; } }, async installUpdate() { await AppUpdater.downloadAndInstall({ url: apkUrl }); }, onDownloadProgress(callback) { AppUpdater.addListener('downloadProgress', callback); }, }; webPlatform.features.hasNativeUpdates = true; webPlatform.features.hasScreenCapture = false; // Native voice foreground service const VoiceService = window.Capacitor.Plugins.VoiceService; if (VoiceService) { webPlatform.voiceService = { async startService({ channelName, isMuted, isDeafened }) { await VoiceService.startService({ channelName, isMuted, isDeafened }); }, async stopService() { await VoiceService.stopService(); }, async updateNotification(opts) { await VoiceService.updateNotification(opts); }, addNotificationActionListener(callback) { return VoiceService.addListener('voiceNotificationAction', callback); }, }; webPlatform.features.hasVoiceService = true; } // Native system bar coloring const SystemBars = window.Capacitor.Plugins.SystemBars; if (SystemBars) { webPlatform.systemBars = { setColors: (opts) => SystemBars.setColors(opts) }; webPlatform.features.hasSystemBars = true; } } export default webPlatform;