From e874b89fe84a0dc4e690712af6da41e76cbeb8ed Mon Sep 17 00:00:00 2001 From: Bryan1029384756 <23323626+Bryan1029384756@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:17:51 -0600 Subject: [PATCH] feat: Bootstrap the Electron application with React, Convex, and LiveKit, including a FriendsView component and a Gitea CI/CD release workflow. --- .claude/settings.local.json | 3 +- .gitea/workflows/release.yml | 87 ++++++++++++++ Frontend/Electron/main.cjs | 33 +++++- Frontend/Electron/package-lock.json | 111 +++++++++++++++++- Frontend/Electron/package.json | 30 ++++- Frontend/Electron/splash.html | 109 +++++++++++++++++ .../Electron/src/components/FriendsView.jsx | 37 ------ Frontend/Electron/updater.cjs | 61 ++++++++++ 8 files changed, 424 insertions(+), 47 deletions(-) create mode 100644 .gitea/workflows/release.yml create mode 100644 Frontend/Electron/splash.html create mode 100644 Frontend/Electron/updater.cjs diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ec7834a..23ea930 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,8 @@ "Bash(dir:*)", "Bash(npx vite build:*)", "Bash(npx tsc:*)", - "Bash(npx -y esbuild:*)" + "Bash(npx -y esbuild:*)", + "WebFetch(domain:gist.github.com)" ] } } diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..bf2446c --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,87 @@ +name: Build and Release + +on: + push: + branches: [main] + +jobs: + build-and-release: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Wine (for Windows cross-compile) + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y wine64 wine32 + + - name: Install dependencies + run: | + npm install + npm run install:frontend + + - name: Read version from package.json + id: version + run: | + VERSION=$(node -p "require('./FrontEnd/Electron/package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Build Electron app + run: | + cd FrontEnd/Electron + npm run build + npx electron-builder --linux --win + env: + GH_TOKEN: ${{ secrets.CI_TOKEN }} + + - name: Delete existing latest release + run: | + RELEASE_ID=$(curl -s -H "Authorization: token $TOKEN" \ + "$GITEA_URL/api/v1/repos/$REPO/releases/tags/latest" | jq -r '.id') + if [ "$RELEASE_ID" != "null" ] && [ -n "$RELEASE_ID" ]; then + curl -X DELETE -H "Authorization: token $TOKEN" \ + "$GITEA_URL/api/v1/repos/$REPO/releases/$RELEASE_ID" + # Also delete the tag so it can be recreated + curl -X DELETE -H "Authorization: token $TOKEN" \ + "$GITEA_URL/api/v1/repos/$REPO/tags/latest" + fi + env: + GITEA_URL: ${{ secrets.CI_URL }} + REPO: ${{ gitea.repository }} + TOKEN: ${{ secrets.CI_TOKEN }} + + - name: Create release and upload artifacts + run: | + VERSION="${{ steps.version.outputs.version }}" + + # Create new release with latest tag + RELEASE_ID=$(curl -s -X POST -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"latest\",\"name\":\"v${VERSION}\",\"body\":\"Auto-release v${VERSION} from CI\"}" \ + "$GITEA_URL/api/v1/repos/$REPO/releases" | jq -r '.id') + + echo "Created release ID: $RELEASE_ID" + + # Upload each artifact + for file in FrontEnd/Electron/dist/latest*.yml \ + FrontEnd/Electron/dist/*.exe \ + FrontEnd/Electron/dist/*.exe.blockmap \ + FrontEnd/Electron/dist/*.AppImage; do + [ -f "$file" ] || continue + FILENAME=$(basename "$file") + echo "Uploading: $FILENAME" + curl -X POST -H "Authorization: token $TOKEN" \ + -F "attachment=@$file" \ + "$GITEA_URL/api/v1/repos/$REPO/releases/$RELEASE_ID/assets?name=$FILENAME" + done + env: + GITEA_URL: ${{ secrets.CI_URL }} + REPO: ${{ gitea.repository }} + TOKEN: ${{ secrets.CI_TOKEN }} diff --git a/Frontend/Electron/main.cjs b/Frontend/Electron/main.cjs index bf9d277..2457374 100644 --- a/Frontend/Electron/main.cjs +++ b/Frontend/Electron/main.cjs @@ -2,6 +2,7 @@ const { app, BrowserWindow, ipcMain, shell } = require('electron'); const path = require('path'); const https = require('https'); const http = require('http'); +const { checkForUpdates } = require('./updater.cjs'); function createWindow() { const win = new BrowserWindow({ @@ -28,8 +29,36 @@ function createWindow() { } } -app.whenReady().then(() => { - createWindow(); +function createSplashWindow() { + const splash = new BrowserWindow({ + width: 300, + height: 350, + frame: false, + resizable: false, + alwaysOnTop: true, + webPreferences: { + nodeIntegration: false, + contextIsolation: true + } + }); + splash.loadFile(path.join(__dirname, 'splash.html')); + return splash; +} + +app.whenReady().then(async () => { + const isDev = !app.isPackaged; + + if (isDev) { + createWindow(); + } else { + const splash = createSplashWindow(); + const noUpdate = await checkForUpdates(splash); + if (noUpdate === false) { + if (!splash.isDestroyed()) splash.close(); + createWindow(); + } + // If update downloaded, quitAndInstall handles restart + } ipcMain.on('window-minimize', () => { const win = BrowserWindow.getFocusedWindow(); diff --git a/Frontend/Electron/package-lock.json b/Frontend/Electron/package-lock.json index cd00ca9..ee8169a 100644 --- a/Frontend/Electron/package-lock.json +++ b/Frontend/Electron/package-lock.json @@ -11,6 +11,8 @@ "@livekit/components-react": "^2.9.17", "@livekit/components-styles": "^1.2.0", "convex": "^1.31.2", + "electron-log": "^5.4.3", + "electron-updater": "^6.7.3", "livekit-client": "^2.16.1", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -2772,7 +2774,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": { @@ -4726,6 +4727,15 @@ "node": ">= 10.0.0" } }, + "node_modules/electron-log": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz", + "integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/electron-publish": { "version": "25.1.7", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz", @@ -4787,6 +4797,82 @@ "dev": true, "license": "ISC" }, + "node_modules/electron-updater": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.7.3.tgz", + "integrity": "sha512-EgkT8Z9noqXKbwc3u5FkJA+r48jwZ5DTUiOkJMOTEEH//n5Am6wfQGz7nvSFEA2oIAMv9jRzn5JKTyWeSKOPgg==", + "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/electron/node_modules/@types/node": { "version": "20.19.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", @@ -5740,7 +5826,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": { @@ -6394,7 +6479,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" @@ -6482,7 +6566,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": { @@ -6628,6 +6711,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", @@ -6636,6 +6725,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", @@ -9026,7 +9122,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/scheduler": { @@ -9550,6 +9645,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/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/Frontend/Electron/package.json b/Frontend/Electron/package.json index 53403e3..915a8db 100644 --- a/Frontend/Electron/package.json +++ b/Frontend/Electron/package.json @@ -1,7 +1,7 @@ { "name": "discord", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "main": "main.cjs", "homepage": "./", @@ -14,26 +14,52 @@ "electron:build": "vite build && electron-builder" }, "build": { + "appId": "com.yourorg.discord-clone", + "productName": "Discord Clone", "files": [ "dist-react/**/*", "main.cjs", "preload.cjs", + "updater.cjs", + "splash.html", "package.json" ], "directories": { "output": "dist" + }, + "publish": [ + { + "provider": "generic", + "url": "https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest" + } + ], + "win": { + "target": ["nsis"] + }, + "mac": { + "target": ["dmg", "zip"], + "category": "public.app-category.social-networking" + }, + "linux": { + "target": ["AppImage"] + }, + "nsis": { + "oneClick": true, + "perMachine": false } }, "dependencies": { "@livekit/components-react": "^2.9.17", "@livekit/components-styles": "^1.2.0", + "convex": "^1.31.2", + "electron-log": "^5.4.3", + "electron-updater": "^6.7.3", "livekit-client": "^2.16.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-markdown": "^10.1.0", "react-router-dom": "^7.11.0", "react-syntax-highlighter": "^16.1.0", - "convex": "^1.31.2", "remark-gfm": "^4.0.1" }, "devDependencies": { diff --git a/Frontend/Electron/splash.html b/Frontend/Electron/splash.html new file mode 100644 index 0000000..8bccf83 --- /dev/null +++ b/Frontend/Electron/splash.html @@ -0,0 +1,109 @@ + + + + + + Discord Clone + + + + +
Discord Clone
+
Starting up...
+
+
+
+ + + + diff --git a/Frontend/Electron/src/components/FriendsView.jsx b/Frontend/Electron/src/components/FriendsView.jsx index 286a212..55c3c60 100644 --- a/Frontend/Electron/src/components/FriendsView.jsx +++ b/Frontend/Electron/src/components/FriendsView.jsx @@ -74,46 +74,9 @@ const FriendsView = ({ onOpenDM }) => { {tab} ))} -
setActiveTab('Add Friend')} - className="friends-tab" - style={{ - cursor: 'pointer', - color: activeTab === 'Add Friend' ? '#fff' : '#3ba55c', - backgroundColor: activeTab === 'Add Friend' ? '#3ba55c' : 'transparent', - padding: '2px 8px', - borderRadius: '4px', - fontWeight: 500, - fontSize: '14px' - }} - > - Add Friend -
- {activeTab === 'Add Friend' && ( -
- setAddFriendSearch(e.target.value)} - style={{ - width: '100%', - backgroundColor: 'var(--bg-tertiary)', - border: '1px solid var(--border-subtle)', - borderRadius: '8px', - color: 'var(--text-normal)', - padding: '10px 14px', - fontSize: '14px', - outline: 'none', - boxSizing: 'border-box' - }} - /> -
- )} - {/* List Header */}
{ + function sendToSplash(js) { + if (splashWindow && !splashWindow.isDestroyed()) { + splashWindow.webContents.executeJavaScript(js).catch(() => {}); + } + } + + autoUpdater.on('checking-for-update', () => { + sendToSplash('setStatus("Checking for updates...")'); + }); + + autoUpdater.on('update-available', () => { + sendToSplash('setStatus("Downloading update...")'); + autoUpdater.downloadUpdate(); + }); + + autoUpdater.on('download-progress', (progress) => { + const percent = Math.round(progress.percent); + sendToSplash(`setProgress(${percent})`); + sendToSplash(`setStatus("Downloading update... ${percent}%")`); + }); + + autoUpdater.on('update-downloaded', () => { + sendToSplash('setStatus("Installing update...")'); + sendToSplash('setProgress(100)'); + setTimeout(() => { + autoUpdater.quitAndInstall(); + }, 1500); + }); + + autoUpdater.on('update-not-available', () => { + sendToSplash('setStatus("Up to date!")'); + sendToSplash('hideProgress()'); + setTimeout(() => resolve(false), 1000); + }); + + autoUpdater.on('error', (err) => { + log.error('Auto-updater error:', err); + sendToSplash('setStatus("Update check failed")'); + sendToSplash('hideProgress()'); + setTimeout(() => resolve(false), 2000); + }); + + autoUpdater.checkForUpdates().catch((err) => { + log.error('checkForUpdates failed:', err); + sendToSplash('setStatus("Update check failed")'); + sendToSplash('hideProgress()'); + setTimeout(() => resolve(false), 2000); + }); + }); +} + +module.exports = { checkForUpdates };