diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 84f27d1..dd10bd7 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -1,65 +1,64 @@ name: Bug description: Make sure you complete the template. Otherwise, it will be closed without further explanation! title: "[v] Replace this with your title" -labels: bug +labels: [bug] body: -- type: checkboxes - attributes: - label: Is there an existing issue for this? - description: _Please check the [**issues**](https://github.com/oxmc/bsky-desktop/issues) page to see if someone has already reported the bug. **I DIDN\'T MAKE THIS CHECKBOX FOR COSMETIC.**_ - options: - - label: I have searched the existing issues - required: true - -- type: textarea - attributes: - label: Device information - description: - value: | + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: _Please check the [**issues**](https://github.com/oxmc/bsky-desktop/issues) page to see if someone has already reported the bug. **I DIDN\'T MAKE THIS CHECKBOX FOR COSMETIC.**_ + options: + - label: I have searched the existing issues + required: true + + - type: textarea + attributes: + label: Device information + description: "_Please provide the following information:_" + value: | - OS: - Hardware Specs: - Etc: - validations: - required: true + validations: + required: true -- type: textarea - attributes: - label: Describe the issue - description: _Please attach videos or screenshots if possible_ - validations: - required: true + - type: textarea + attributes: + label: Describe the issue + description: _Please attach videos or screenshots if possible_ + validations: + required: true -- type: textarea - attributes: - label: Steps to reproduce - description: _Please attach videos or screenshots if possible_ - value: | + - type: textarea + attributes: + label: Steps to reproduce + description: _Please attach videos or screenshots if possible_ + value: | 1. - 2. - validations: - required: true + 2. + validations: + required: true -- type: textarea - id: logs - attributes: - label: Crash log - description: _If the app crashes, **please provide the crash log**. - render: shell + - type: textarea + id: logs + attributes: + label: Crash log + description: _If the app crashes, **please provide the crash log**. + render: shell -- type: dropdown - attributes: - label: Are you using the latest version of bsky-destop? If not, why? - description: _Developers spent loads of time and effort to fix bugs & make improvements with every release. You might want to try and update to the [latest version](https://github.com/oxmc/bsky-desktop/releases) before reporting an issue._ - multiple: false - options: - - ✅ Yes, I'm using the latest version of bsky-desktop - - ❌ No, I'll explain with additional information below - validations: - required: true - -- type: textarea - attributes: - label: Additional information - validations: - required: false + - type: dropdown + attributes: + label: Are you using the latest version of bsky-destop? If not, why? + description: _Developers spent loads of time and effort to fix bugs & make improvements with every release. You might want to try and update to the [latest version](https://github.com/oxmc/bsky-desktop/releases) before reporting an issue._ + multiple: false + options: + - ✅ Yes, I'm using the latest version of bsky-desktop + - ❌ No, I'll explain with additional information below + validations: + required: true + - type: textarea + attributes: + label: Additional information + validations: + required: false diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 7c9637c..f06fec3 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -31,8 +31,8 @@ jobs: - name: Install dependencies run: npm install - - name: Build (ia32) - run: npm run build -- --arch ia32 + - name: Build (armv7l) + run: npm run build -- --arch armv7l - name: Build (x64) run: npm run build -- --arch x64 diff --git a/build/build-app.js b/build/build-app.js index 35f2521..b24d43b 100644 --- a/build/build-app.js +++ b/build/build-app.js @@ -10,6 +10,7 @@ const args = process.argv.slice(2); const carch = args.includes('--arch') ? args[args.indexOf('--arch') + 1] : null; const cplatform = args.includes('--platform') ? args[args.indexOf('--platform') + 1] : null; const pack = args.includes('--pack'); // Keep track of --pack as a boolean flag +const nobc = args.includes('--no-bc'); // Keep track of --nobc as a boolean flag // Determine platform let platform; @@ -59,14 +60,38 @@ if (pack) { buildArgs.push('--dir'); } -// Read build-config.json +// Default build configuration (used when --no-bc is passed) [Default values are for bskyDesktop] +const defaultBuildConfig = { + "appId": "com.oxmc.bskyDesktop", + "productName": "bskyDesktop", + "asarUnpack": [ + "./node_modules/node-notifier/**/*" + ], + "win": { + "icon": "src/ui/images/logo.ico", + }, + "mac": { + "icon": "src/ui/images/mac.icns", + }, + "linux": { + "icon": "src/ui/images/icons", + }, +}; + +// Read build-config.json if it exists and --no-bc is not present const buildConfigPath = path.join(__dirname, 'build-config.json'); -let buildConfig; -try { - buildConfig = require(buildConfigPath); -} catch (err) { - console.error(`Failed to read build-config.json: ${err.message}`); - process.exit(1); +let buildConfig = defaultBuildConfig; +if (!nobc) { + if (!require('fs').existsSync(buildConfigPath)) { + console.error('build-config.json not found'); + process.exit(1); + } + try { + buildConfig = require(buildConfigPath); + } catch (err) { + console.error(`Failed to read build-config.json: ${err.message}`); + process.exit(1); + } } // Generate artifact name @@ -84,12 +109,12 @@ const options = { oneClick: true, perMachine: false }, - dmg: { + /*dmg: { contents: [ { type: 'file', x: 255, y: 85 }, { type: 'link', path: '/Applications', x: 253, y: 325 } ] - }, + },*/ ...buildConfig }; diff --git a/package-lock.json b/package-lock.json index e7352bd..ce972a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,19 +13,26 @@ "@electron/remote": "^2.1.2", "adm-zip": "^0.5.12", "axios": "^1.6.8", - "electron-window-state": "^5.0.3", "less": "^4.2.1", "log4js": "^6.9.1", "node-notifier": "^10.0.0", "semver": "^7.6.3", + "stylus": "^0.64.0", "usercss-meta": "^0.12.0", - "v8-compile-cache": "^2.3.0" + "v8-compile-cache": "^2.3.0", + "winreg": "^1.2.5" }, "devDependencies": { "electron": "^29.4.6", "electron-builder": "^24.13.3" } }, + "node_modules/@adobe/css-tools": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", + "license": "MIT" + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -254,7 +261,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -272,7 +278,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -285,7 +290,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -298,14 +302,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -323,7 +325,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -339,7 +340,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -425,7 +425,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -637,7 +636,6 @@ "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" @@ -647,7 +645,6 @@ "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" @@ -1191,7 +1188,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1204,7 +1200,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1389,7 +1384,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1588,7 +1582,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/ejs": { @@ -1682,22 +1675,10 @@ "mime": "^2.5.2" } }, - "node_modules/electron-window-state": { - "version": "5.0.3", - "license": "MIT", - "dependencies": { - "jsonfile": "^4.0.0", - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "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/end-of-stream": { @@ -1903,7 +1884,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -1920,7 +1900,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -2377,7 +2356,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2428,7 +2406,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -2779,13 +2756,13 @@ }, "node_modules/minimist": { "version": "1.2.6", + "dev": true, "license": "MIT" }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -2818,16 +2795,6 @@ "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.2", "license": "MIT" @@ -2920,7 +2887,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parse-node-version": { @@ -2943,7 +2909,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2953,7 +2918,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -2970,7 +2934,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/pend": { @@ -3057,10 +3020,11 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3232,7 +3196,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "devOptional": true, "license": "ISC" }, "node_modules/semver": { @@ -3273,7 +3236,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -3286,7 +3248,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3449,7 +3410,6 @@ "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", @@ -3465,7 +3425,6 @@ "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", @@ -3480,7 +3439,6 @@ "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" @@ -3494,7 +3452,6 @@ "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" @@ -3503,6 +3460,90 @@ "node": ">=8" } }, + "node_modules/stylus": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.64.0.tgz", + "integrity": "sha512-ZIdT8eUv8tegmqy1tTIdJv9We2DumkNZFdCF5mz/Kpq3OcTaxSuCAYZge6HKK2CmNC02G1eJig2RV7XTw5hQrA==", + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "~4.3.3", + "debug": "^4.3.2", + "glob": "^10.4.5", + "sax": "~1.4.1", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/stylus/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/stylus/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylus/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylus/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/stylus/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -3739,6 +3780,12 @@ "node": ">= 8" } }, + "node_modules/winreg": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.5.tgz", + "integrity": "sha512-uf7tHf+tw0B1y+x+mKTLHkykBgK2KMs3g+KlzmyMbLvICSHQyB/xOFjTT8qZ3oeTFyU7Bbj4FzXitGG6jvKhYw==", + "license": "BSD-2-Clause" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3762,7 +3809,6 @@ "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", @@ -3883,6 +3929,11 @@ } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==" + }, "@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -4046,7 +4097,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "requires": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -4059,26 +4109,22 @@ "ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==" }, "ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "requires": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -4089,7 +4135,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -4098,7 +4143,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "requires": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -4156,7 +4200,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true }, "@sindresorhus/is": { @@ -4317,14 +4360,12 @@ "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 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "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, "requires": { "color-convert": "^2.0.1" } @@ -4718,7 +4759,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -4726,8 +4766,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "combined-stream": { "version": "1.0.8", @@ -4860,7 +4899,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4988,8 +5026,7 @@ "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "ejs": { "version": "3.1.10", @@ -5057,18 +5094,10 @@ "mime": "^2.5.2" } }, - "electron-window-state": { - "version": "5.0.3", - "requires": { - "jsonfile": "^4.0.0", - "mkdirp": "^0.5.1" - } - }, "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 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "end-of-stream": { "version": "1.4.4", @@ -5210,7 +5239,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -5219,8 +5247,7 @@ "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" } } }, @@ -5515,8 +5542,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-what": { "version": "3.14.1", @@ -5549,7 +5575,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", "@pkgjs/parseargs": "^0.11.0" @@ -5805,13 +5830,13 @@ } }, "minimist": { - "version": "1.2.6" + "version": "1.2.6", + "dev": true }, "minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" }, "minizlib": { "version": "2.1.2", @@ -5834,12 +5859,6 @@ } } }, - "mkdirp": { - "version": "0.5.6", - "requires": { - "minimist": "^1.2.6" - } - }, "ms": { "version": "2.1.2" }, @@ -5901,8 +5920,7 @@ "package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, "parse-node-version": { "version": "1.0.1", @@ -5915,14 +5933,12 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "requires": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -5931,8 +5947,7 @@ "lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" } } }, @@ -5999,9 +6014,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "quick-lru": { @@ -6130,8 +6145,7 @@ "sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "devOptional": true + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "semver": { "version": "7.6.3", @@ -6157,7 +6171,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -6165,8 +6178,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shellwords": { "version": "0.1.1" @@ -6277,7 +6289,6 @@ "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, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6288,7 +6299,6 @@ "version": "npm:string-width@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, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6299,7 +6309,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -6308,11 +6317,63 @@ "version": "npm:strip-ansi@6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, + "stylus": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.64.0.tgz", + "integrity": "sha512-ZIdT8eUv8tegmqy1tTIdJv9We2DumkNZFdCF5mz/Kpq3OcTaxSuCAYZge6HKK2CmNC02G1eJig2RV7XTw5hQrA==", + "requires": { + "@adobe/css-tools": "~4.3.3", + "debug": "^4.3.2", + "glob": "^10.4.5", + "sax": "~1.4.1", + "source-map": "^0.7.3" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + } + } + }, "sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -6479,6 +6540,11 @@ "isexe": "^2.0.0" } }, + "winreg": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.5.tgz", + "integrity": "sha512-uf7tHf+tw0B1y+x+mKTLHkykBgK2KMs3g+KlzmyMbLvICSHQyB/xOFjTT8qZ3oeTFyU7Bbj4FzXitGG6jvKhYw==" + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6494,7 +6560,6 @@ "version": "npm:wrap-ansi@7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", diff --git a/package.json b/package.json index b49ed20..051b2c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky-desktop", - "version": "1.1.1", + "version": "1.1.3", "description": "A desktop app of bsky.app", "main": "src/app/main.js", "scripts": { @@ -24,12 +24,13 @@ "@electron/remote": "^2.1.2", "adm-zip": "^0.5.12", "axios": "^1.6.8", - "electron-window-state": "^5.0.3", "less": "^4.2.1", "log4js": "^6.9.1", "node-notifier": "^10.0.0", "semver": "^7.6.3", + "stylus": "^0.64.0", "usercss-meta": "^0.12.0", - "v8-compile-cache": "^2.3.0" + "v8-compile-cache": "^2.3.0", + "winreg": "^1.2.5" } } diff --git a/src/app/main.js b/src/app/main.js index e7af383..08cda88 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -1,30 +1,26 @@ const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session, dialog } = require("electron"); const electronremote = require("@electron/remote/main"); //const asar = require('@electron/asar'); -const windowStateKeeper = require("electron-window-state"); +const windowStateKeeper = require("./window-state/index"); const { setupTitlebar, attachTitlebarToWindow } = require("./titlebar/main"); const openAboutWindow = require("./about-window/src/index").default; const badge = require('./badge'); +const nodeNotifier = require('node-notifier'); const contextMenu = require('./context-menu'); const autoUpdater = require('./utils/auto-update'); const loadCRX = require('./utils/loadCRX'); const userStyles = require('./utils/userStyles'); const log4js = require("log4js"); +//const axios = require("axios"); const path = require("path"); const fs = require("fs"); const os = require("os"); require('v8-compile-cache'); +// Load package.json and contributors.json const packageJson = require(path.join(__dirname, '..', '..', 'package.json')); const contributors = require(path.join(__dirname, 'contributors.json')); -// Check if app is ran from the installer dmg (macOS) -if (process.platform === 'darwin' && app.isPackaged && app.isInApplicationsFolder()) { - // Creeate a dialog to ask the user to move the app to the Applications folder - dialog.showErrorBox('Move to Applications folder', 'Please move the app to the Applications folder to ensure it works correctly.'); - app.quit(); -}; - // isUpdaing: global.isUpdating = false; @@ -44,9 +40,10 @@ global.paths = { home: os.homedir(), temp: path.join(os.tmpdir(), global.appInfo.name), }; -global.paths.updateDir = path.join(global.paths.data, 'update'); -global.paths.extensions = path.join(global.paths.data, 'extensions'); -global.paths.userstyles = path.join(global.paths.data, 'userstyles'); +global.paths.user = path.join(global.paths.data, 'user'); +global.paths.updateDir = path.join(global.paths.user, 'update'); +global.paths.extensions = path.join(global.paths.user, 'extensions'); +global.paths.userstyles = path.join(global.paths.user, 'userstyles'); // URLs: global.urls = { @@ -61,6 +58,36 @@ global.settings.account = `${global.settings.general}/account`; global.settings.appearance = `${global.settings.general}/appearance`; global.settings.privacy = `${global.settings.general}/privacy-and-security`; +// Check if app is run from the installer dmg (macOS) +if (process.platform === 'darwin' && app.isPackaged && !app.isInApplicationsFolder()) { + const response = dialog.showMessageBoxSync({ + type: 'question', + buttons: ['Yes', 'No'], + title: 'Move to Applications folder', + message: 'Please move the app to the Applications folder to ensure it works correctly. Would you like to move it now?' + }); + + if (response === 0) { // User clicked 'Yes' + const appPath = app.getPath('exe'); + const applicationsFolder = '/Applications'; + const appName = path.basename(appPath); + const destinationPath = path.join(applicationsFolder, appName); + + // Try to move the app + fs.rename(appPath, destinationPath, (err) => { + if (err) { + dialog.showErrorBox('Move Failed', 'Failed to move the app to the Applications folder.'); + } else { + dialog.showInformationBox({ message: 'The app has been moved to the Applications folder. Please restart it.' }); + app.quit(); + } + }); + } else { + dialog.showErrorBox('Move to Applications folder', 'Please move the app to the Applications folder to ensure it works correctly.'); + app.quit(); + } +} + // Badge options: const badgeOptions = { fontColor: '#FFFFFF', // The font color @@ -124,10 +151,10 @@ if (!fs.existsSync(global.paths.temp)) { fs.mkdirSync(global.paths.temp, { recursive: true }); }; -// Create update directory if it does not exist: -if (!fs.existsSync(global.paths.updateDir)) { - logger.info("Creating Update Directory"); - fs.mkdirSync(global.paths.updateDir, { recursive: true }); +// Create user directory if it does not exist: +if (!fs.existsSync(global.paths.data, 'user')) { + logger.info("Creating User Directory"); + fs.mkdirSync(global.paths.user, { recursive: true }); }; // Create extensions directory if it does not exist: @@ -142,6 +169,12 @@ if (!fs.existsSync(global.paths.userstyles)) { fs.mkdirSync(global.paths.userstyles, { recursive: true }); }; +// Create update directory if it does not exist: +if (!fs.existsSync(global.paths.updateDir)) { + logger.info("Creating Update Directory"); + fs.mkdirSync(global.paths.updateDir, { recursive: true }); +}; + // improve performance on linux? if (process.platform !== "win32" && process.platform !== "darwin") { logger.log("Disabling Hardware Acceleration and Transparent Visuals"); @@ -156,16 +189,16 @@ setupTitlebar(); // Disable reload and F5 if not in dev mode: if (process.env.NODE_ENV !== 'development') { app.on('browser-window-focus', function () { - globalShortcut.register("CommandOrControl+R", () => { + /*globalShortcut.register("CommandOrControl+R", () => { //console.log("CommandOrControl+R is pressed: Shortcut Disabled"); }); globalShortcut.register("F5", () => { //console.log("F5 is pressed: Shortcut Disabled"); - }); + });*/ }); app.on('browser-window-blur', function () { - globalShortcut.unregister('CommandOrControl+R'); - globalShortcut.unregister('F5'); + /*globalShortcut.unregister('CommandOrControl+R'); + globalShortcut.unregister('F5');*/ }); }; @@ -228,14 +261,14 @@ function createWindow() { PageView.setBounds({ x: 0, y: 30, - width: mainWindow.getBounds().width, + width: mainWindow.isMaximized() ? mainWindow.getBounds().width - 16 : mainWindow.getBounds().width, height: mainWindow.getBounds().height - 30, }); mainWindow.on("resize", () => { PageView.setBounds({ x: 0, y: 30, - width: mainWindow.getBounds().width, + width: mainWindow.isMaximized() ? mainWindow.getBounds().width - 16 : mainWindow.getBounds().width, height: mainWindow.getBounds().height - 30, }); }); @@ -505,81 +538,198 @@ app.whenReady().then(() => { // Set session: global.session = ses; - // Initialize the updater: - logger.log("Initializing Updater"); - autoUpdater(); - // Handle ipc for render: ipcMain.on('close-app', (event, arg) => { app.quit(); }); + ipcMain.on('app:restart', (event, arg) => { + app.relaunch(); + app.quit(); + }); + + // Create windows and tray: createWindow(); createTray(); - // Load extensions (.crx files): - logger.log("Checking for extensions"); - const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx')); - if (extensions.length > 0) { - logger.log(`Unpacking ${extensions.length} extensions and loading them`); - extensions.forEach((extension) => { - loadCRX(path.join(global.paths.extensions, extension)); - }); - } else { - // Check for unpacked extensions: - const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory()); + // Wait for splash screen to load before checking for updates, loading extensions and userstyles + global.splash.webContents.on('did-finish-load', async () => { + // Check for internet connection: + logger.log("Checking for internet connection"); + require('dns').lookup('google.com', err => { + if (err) { + logger.log('No internet connection, showing not connected message'); + global.PageView.webContents.loadFile(path.join(global.paths.app, 'ui', 'offline.html')); + return; + } + logger.log('Internet available, checking for updates'); + // Initialize the updater: + logger.log("Initializing Updater"); + global.splash.webContents.send('ui:progtext', { title: 'Checking for updates...', subtitle: 'Awaiting response' }); + autoUpdater.checkForUpdates().then(async (result) => { + //console.log(result); + switch (result.code) { + case 'update-available': + global.isUpdating = true; + logger.log("Update available, downloading"); + global.splash.webContents.send('ui:progtext', { title: 'Update available', subtitle: 'Downloading update' }); + try { + const update = await autoUpdater.downloadUpdate(); + //console.log(update); + if (update.err && update.err.code === 'unpackaged') { + logger.warn("Update failed to download, unpackaged app"); + global.splash.webContents.send('ui:progtext', { title: 'Not downloading update', subtitle: 'Unpackaged app' }); + global.isUpdating = false; + } else { + if (update.err) { + logger.error("Update failed to download"); + console.log(update.err); + global.splash.webContents.send('ui:progtext', { title: 'Failed to download update', subtitle: 'Continuing as normal...' }); + }; + switch (update.code) { + case 'downloaded': + logger.log("Update downloaded, installing"); + global.splash.webContents.send('ui:progtext', { title: 'Update downloaded', subtitle: 'Installing update' }); + try { + const install = await autoUpdater.installUpdate(update.path); + //console.log(install); + if (install.err) { + logger.error("Failed to install update"); + console.log(install.err); + global.splash.webContents.send('ui:progtext', { title: 'Failed to install update', subtitle: 'Continuing as normal...' }); + } else { + if (install.code === 'update-installed') { + logger.log("Update installed, restarting"); + global.splash.webContents.send('ui:progtext', { title: 'Update installed', subtitle: 'Restarting...' }); + app.relaunch(); + app.quit(); + } else { + logger.error("Failed to install update"); + global.splash.webContents.send('ui:progtext', { title: 'Failed to install update', subtitle: 'Continuing as normal...' }); + }; + }; + } catch (error) { + logger.error(`Error installing update:`, error); + global.splash.webContents.send('ui:progtext', { title: 'Error installing update', subtitle: ' ' }); + }; + break; + case 'download-failed': + logger.error("Failed to download update"); + global.splash.webContents.send('ui:progtext', { title: 'Failed to download update', subtitle: 'Continuing as normal...' }); + global.isUpdating = false; + global.mainWindow.show(); + global.splash.destroy(); + break; + default: + logger.error("Unknown update download status"); + global.splash.webContents.send('ui:progtext', { title: 'Unknown update download status', subtitle: ' ' }); + break; + }; + }; + } catch (error) { + logger.error(`Error downloading update:`, error); + global.splash.webContents.send('ui:progtext', { title: 'Error downloading update', subtitle: ' ' }); + }; + break; + case 'no-update': + logger.log("No update available"); + global.splash.webContents.send('ui:progtext', { title: 'Checking for updates...', subtitle: 'Up to date!' }); + break; + case 'check-failed': + logger.warn("Failed to check for updates"); + global.splash.webContents.send('ui:progtext', { title: 'Failed to check for updates', subtitle: ' ' }); + break; + case 'old-os': + logger.warn("Old OS, unable to update"); + global.splash.webContents.send('ui:progtext', { title: 'Update not available', subtitle: 'Old OS' }); + break; + default: + logger.warn("Unknown update status"); + global.splash.webContents.send('ui:progtext', { title: 'Unknown update status', subtitle: ' ' }); + break; + }; + }).catch((error) => { + logger.error(`Error checking for updates: ${error}`); + global.splash.webContents.send('ui:progtext', { title: 'Error checking for updates', subtitle: ' ' }); + }).finally(async () => { + // Load extensions (.crx files): + logger.log("Checking for extensions"); + global.splash.webContents.send('ui:progtext', { title: 'Checking for extensions', subtitle: ' ' }); + const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx')); + if (extensions.length > 0) { + logger.log(`Unpacking ${extensions.length} extensions and loading them`); + global.splash.webContents.send('ui:progtext', { title: `Unpacking ${extensions.length} extensions` }); + extensions.forEach((extension) => { + logger.log(`Loading extension: ${extension}`); + global.splash.webContents.send('ui:progtext', { title: `Loading extension: ${extension}` }); + loadCRX(path.join(global.paths.extensions, extension)); + }); + } else { + // Check for unpacked extensions: + const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory()); - // Check if the directory contains a manifest.json file - unpackedExtensions.forEach((extension) => { - const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json'); - if (fs.existsSync(manifestPath)) { - logger.log(`Loading unpacked extension: ${extension}`); - session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => { - logger.log(`Extension loaded with ID: ${id}`); - }).catch((error) => { - logger.error(`Failed to load extension: ${error}`); - }); - } else { - logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`); - }; - }); - }; + // Check if the directory contains a manifest.json file + unpackedExtensions.forEach((extension) => { + const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json'); + if (fs.existsSync(manifestPath)) { + logger.log(`Loading unpacked extension: ${extension}`); + global.splash.webContents.send('ui:progtext', { title: `Loading unpacked extension: ${extension}` }); + session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => { + logger.log(`Extension loaded with ID: ${id}`); + }).catch((error) => { + logger.error(`Failed to load extension: ${error}`); + }); + } else { + logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`); + }; + }); + }; - // Load userstyles - logger.log("Checking for userstyles"); - const userStylesDir = path.join(global.paths.userstyles); - if (fs.existsSync(userStylesDir)) { - const files = fs.readdirSync(userStylesDir); - if (files.length > 0) { - const userStylePromises = files.map(async file => { - const cssFile = path.join(userStylesDir, file); - // Parse the CSS file - try { - const cssContent = fs.readFileSync(cssFile, 'utf-8'); - const result = await userStyles.parseCSS(cssContent); + // Load userstyles + logger.log("Checking for userstyles"); + global.splash.webContents.send('ui:progtext', { title: 'Checking for userstyles' }); + const userStylesDir = path.join(global.paths.userstyles); + if (fs.existsSync(userStylesDir)) { + const files = fs.readdirSync(userStylesDir).filter((file) => file.endsWith('.css')); + if (files.length > 0) { + logger.log(`Loading ${files.length} userstyles`); + const userStylePromises = files.map(async file => { + const cssFile = path.join(userStylesDir, file); + // Parse the CSS file + try { + const cssContent = fs.readFileSync(cssFile, 'utf-8'); + const result = await userStyles.parseCSS(cssContent); - logger.info(`Loading userstyle: ${result.metadata.name}`); + logger.info(`Loading userstyle: ${result.metadata.name}`); - // Compile the userstyle - const compiled = await userStyles.compileStyle(result.css, result.metadata); + // Compile the userstyle + const compiled = await userStyles.compileStyle(result.css, result.metadata); - // Check if the site 'bsky.app' is defined - if (compiled.sites?.['bsky.app']) { - // Apply the userstyle to the PageView - await PageView.webContents.insertCSS(compiled.sites['bsky.app']); + // Check if the site 'bsky.app' is defined + if (compiled.sites?.['bsky.app']) { + // Apply the userstyle to the PageView + await PageView.webContents.insertCSS(compiled.sites['bsky.app']); - logger.info(`Applied userstyle: ${result.metadata.name}`); - } else { - logger.warn(`Userstyle ${result.metadata.name} does not target 'bsky.app'`); + logger.info(`Applied userstyle: ${result.metadata.name}`); + global.splash.webContents.send('ui:progtext', { title: `Applied userstyle: ${result.metadata.name}` }); + } else { + if (compiled.error) { + logger.warn(`Error loading userstyle: ${compiled.error.message}`); + } else { + logger.warn(`Userstyle ${result.metadata.name} does not target 'bsky.app'`); + } + } + } catch (error) { + logger.error(`Error loading userstyle: ${file}`, error); + } + }); + + await Promise.all(userStylePromises); } - } catch (error) { - logger.error(`Error loading userstyle: ${file}`, error); } }); - - Promise.all(userStylePromises); - } - } + }); + }); } else { logger.log("Failed to get singleInstanceLock, Quitting"); app.quit(); diff --git a/src/app/utils/asarUpdater.js b/src/app/utils/asarUpdater.js index ad9898c..bc6343e 100644 --- a/src/app/utils/asarUpdater.js +++ b/src/app/utils/asarUpdater.js @@ -90,7 +90,7 @@ class Updater extends EventEmitter { }; this.options = Object.assign({}, def, options); this.options.cacheDirectory = path.resolve(this.options.tmpdir, this.options.name); - this.options.headers['user-agent'] = this.options.headers['user-agent'] || 'asar-updater/v0.0.2 (https://github.com/zce/asar-updater)'; + this.options.headers['user-agent'] = this.options.headers['user-agent'] || 'Mozilla/5.0 asar-updater/v0.3.3 (https://github.com/zce/asar-updater)'; fs.existsSync(this.options.cacheDirectory) || fs.mkdirSync(this.options.cacheDirectory); } @@ -207,7 +207,7 @@ class Updater extends EventEmitter { quitAndInstall(timeout) { setTimeout(() => { - app.relaunch({ args: process.argv.slice(1) + ['--relaunch'] }); + app.relaunch({ args: process.argv.slice(1) + ['--relaunch', '--update-installed'] }); app.exit(0); }, timeout || 100); } diff --git a/src/app/utils/auto-update.js b/src/app/utils/auto-update.js index d2aa486..630cb1c 100644 --- a/src/app/utils/auto-update.js +++ b/src/app/utils/auto-update.js @@ -1,9 +1,11 @@ -const path = require('path'); -const os = require('os'); -const fs = require('fs'); const childProcess = require('child_process'); const { app } = require('electron'); const log4js = require('log4js'); +const SemVer = require('semver'); +const axios = require('axios'); +const path = require('path'); +const os = require('os'); +const fs = require('fs'); // import the asarUpdater module const asarUpdater = require('./asarUpdater'); @@ -14,9 +16,73 @@ const SystemInfo = require('./sysInfo'); // Get the current system platform const sys = new SystemInfo(); +// Detect the app install type +const installType = require('./installType'); + // Setup the logger const logger = log4js.getLogger("bskydesktop"); +// System architecture / Platform +const systemArch = sys.isARM64() ? 'arm64' : sys.isX64() ? '64' : sys.isX86() ? '86' : 'unknown'; +const systemPlatform = sys.isWin() ? 'win' : sys.isMac() ? 'mac' : sys.isLinux() ? 'linux' : 'unknown'; + +// Download url: (arch, platform, installer) +const downloadUrl = 'https://cdn.oxmc.me/apps/bsky-desktop/dl'; + +// Installer path (used to save the installer) +var installerPath = ''; +var installerType = ''; + +async function detectInstallerType() { + let installerType = 'unknown'; + + // First check if the app is paclaged + if (!app.isPackaged) { + logger.warn('App is not packaged, cannot detect installer type.'); + return 'unpackaged'; + } + + try { + // Check if system is Windows + if (sys.isWin()) { + //console.log('Checking for Windows installer type...'); + const res = await installType.getUninstallEntries({ DisplayName: 'bskyDesktop' }); + + //console.log(res); + + // Ensure result is valid before checking properties + if (res.length > 0) { + installerType = res[0].WindowsInstaller === 'MSI' ? 'msi' : 'exe'; + } else { + // Default to exe if no installer type is found + installerType = 'exe'; + } + } + + // Check if system is macOS + if (sys.isMac()) { + const res = await installType.checkAppInstallationMethod(); + + switch (res.type) { // Use res.type instead of res directly + case 'MAS': + installerType = 'mas'; + break; + case 'PKG': + installerType = 'pkg'; + break; + case 'ZIP/DMG': + installerType = 'dmg'; + break; + } + } + } catch (err) { + logger.error('Failed to detect installer type:', err); + } + + return installerType; +} + +// Asar updater function asarUpdate() { asarUpdater.init(); @@ -62,7 +128,7 @@ function asarUpdate() { // Set the feed URL (only works in packaged app): if (app.isPackaged) { logger.log("Setting Feed URL for app.asar"); - asarUpdater.setFeedURL(path.join(global.paths.app_root), 'https://cdn.oxmc.me/internal/bsky-desktop/update/core'); + asarUpdater.setFeedURL(path.join(global.paths.app_root), 'https://cdn.oxmc.me/apps/bsky-desktop/update/core'); }; //Check for updates: @@ -70,88 +136,379 @@ function asarUpdate() { if (app.isPackaged) { const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours setInterval(() => { - //asarUpdater.checkForUpdates(); + asarUpdater.checkForUpdates(); }, UPDATE_CHECK); - //asarUpdater.checkForUpdates(); + asarUpdater.checkForUpdates(); } else { logger.warn("Not checking for updates as app is not packaged"); }; } -function checkForUpdates() { - // Current system information - logger.log('Current system information:', sys.platform, sys.getVersion(), sys.arch); +// Check for updates +async function checkForUpdates() { + return new Promise((resolve, reject) => { + // Current system information + logger.log('Current system information:', sys.platform, sys.getVersion(), sys.arch); - // Check if the current system is Windows - if (sys.isWin()) { - // Check if the system is before Windows 10 - if (sys.earlierThan('10.0.0')) { - // Windows 10 and above are supported, but windows 7 and 8 are not supported - logger.error('Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...'); - } else { - // Check for updates, and if there are updates, download and install them - logger.log('Checking for updates (win)...'); - } - } - - // Check if the current system is macOS - if (sys.isMac()) { - let macArch = ''; - // Check the current version of macOS, and whether we can use the pkg installer - if (sys.laterThan('10.0.0')) { - // Check if system is after macOS 10 (11, 12, etc.) - if (sys.laterThan('11.0.0')) { - // macOS 11 and above support ARM64, check if system is ARM64 - logger.log('Checkking system architecture...'); - if (sys.isARM64()) { - // System is ARM64 (mac-arm64) - macArch = 'arm64'; - } else { - // System is x64 (mac-x64) - macArch = 'x64'; - } + // Check if the current system is Windows + if (sys.isWin()) { + // Check if the system is before Windows 10 + if (sys.earlierThan('10.0.0')) { + // Windows 10 and above are supported, but windows 7 and 8 are not supported + logger.error('Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...'); + resolve({ err: 'old-os', msg: 'Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...' }); } else { - // macOS 10 is mostly x64, but some versions are ARM64 - macArch = 'x64'; + // Check for updates, and if there are updates, download and install them + logger.log('Checking for updates (win)...'); + axios.get('https://cdn.oxmc.me/apps/bsky-desktop/update/core').then((res) => { + //console.log(res.data); + const latestVersion = res.data.version; + const currentVersion = app.getVersion(); + if (SemVer.gt(latestVersion, currentVersion)) { + //logger.log('Update available:', latestVersion); + global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update', message: 'An update is available' })); + if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Available', subtitle: 'An update is available! Downloading...' }); + resolve({ code: 'update-available', msg: 'An update is available.', info: { latest: latestVersion, current: currentVersion, response: res.data } }); + } else { + //logger.log('No updates available.'); + resolve({ code: 'no-update', msg: 'No updates available.' }); + } + }).catch((err) => { + logger.error('Failed to check for updates:', err); + resolve({ code: 'check-failed', msg: 'Failed to check for updates.' }); + }); } - logger.log('System architecture:', macArch); - // Check for updates, and if there are updates, download and install them - logger.log('Checking for updates (mac)...'); - - // Run the .pkg installer - const pkgPath = path.join(global.paths.updateDir, 'bsky-desktop.pkg'); - const command = `sudo installer -pkg ${pkgPath} -target /`; - - // Spawn a new shell - /*const shellProcess = spawn('sh', ['-c', command], { - stdio: 'inherit', // Pipe input/output to/from the shell - }); - - shellProcess.on('error', (err) => { - console.error('Failed to spawn shell:', err); - }); - - shellProcess.on('close', (code) => { - if (code === 0) { - console.log('Update installed successfully.'); - } else { - console.error(`Shell process exited with code ${code}.`); - } - });*/ - } else { - // macOS versions before 10 are not supported - logger.error('macOS versions before 10 are not supported, not updating...'); } - } - // Check if the current system is Linux - if (sys.isLinux()) { - // Check for updates, and if there are updates, download and install them (no system version check) - // Linux versions use AppImage, so we instead need to check for a new asar file so that the app can - // load the new asar instead of the packaged one (or even just delete the old appimage and download a new one) - logger.log('Checking for updates (linux)...'); - asarUpdate(); - } + // Check if the current system is macOS + if (sys.isMac()) { + // Check the current version of macOS, and whether we can use the pkg installer + if (sys.laterThan('10.0.0')) { + // Check for updates, and if there are updates, download and install them + logger.log('Checking for updates (mac)...'); + + // Check for updates + axios.get('https://cdn.oxmc.me/apps/bsky-desktop/update/core').then((res) => { + //console.log(res.data); + const latestVersion = res.data.version; + const currentVersion = app.getVersion(); + if (SemVer.gt(latestVersion, currentVersion)) { + //logger.log('Update available:', latestVersion); + global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update', message: 'An update is available' })); + if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Available', subtitle: 'An update is available! Downloading...' }); + resolve({ code: 'update-available', msg: 'An update is available.', info: { latest: latestVersion, current: currentVersion, response: res.data } }); + } else { + //logger.log('No updates available.'); + resolve({ code: 'no-update', msg: 'No updates available.' }); + } + }).catch((err) => { + logger.error('Failed to check for updates:', err); + resolve({ code: 'check-failed', msg: 'Failed to check for updates.' }); + }); + } else { + // macOS versions before 10 are not supported + logger.error('macOS versions before 10 are not supported, not updating...'); + resolve({ err: 'old-os', msg: 'macOS versions before 10 are not supported, not updating...' }); + } + } + + // Check if the current system is Linux + if (sys.isLinux()) { + // Check for updates, and if there are updates, download and install them (no system version check) + // Linux versions use AppImage, so we instead need to check for a new asar file so that the app can + // load the new asar instead of the packaged one (or even just delete the old appimage and download a new one) + logger.log('Checking for updates (linux)...'); + asarUpdate(); + } + }).catch((err) => { + logger.error('Failed to check for updates:', err); + }); } -module.exports = checkForUpdates; \ No newline at end of file +// Download the update installer +async function downloadUpdate() { + return new Promise(async (resolve, reject) => { + // download the update installer + //logger.log('Downloading update...'); + + // Create the update directory if it doesn't exist + if (!fs.existsSync(global.paths.updateDir)) { + fs.mkdirSync(global.paths.updateDir); + } + + // Get the installer name + const installerName = `bsky-desktop-${sys.platform}-${systemArch}.exe`; + installerPath = path.join(global.paths.updateDir, installerName); + + // Detect the installer type + installerType = await detectInstallerType(); + //installerType = 'exe'; // For testing purposes + //logger.info('Installer type:', installerType); + + // if the installer type is unknown, return an error + if (installerType === 'unknown') { + //logger.error('Failed to detect installer type:', installerType); + reject({ code: 'unknown-installer', msg: 'Failed to detect installer type.', err: installerType }); + return; + } + + // If the installer type is unpackaged, return and continue + if (installerType === 'unpackaged') { + //logger.warn('App is not packaged, cannot download update.'); + reject({ code: 'unpackaged', msg: 'App is not packaged, cannot download update.' }); + return; + } + + // download the installer as a stream + const installerStream = fs.createWriteStream(installerPath); + + // download the installer + axios({ + method: 'get', + url: downloadUrl, + responseType: 'stream', + headers: { + "x-platform": systemPlatform, + "x-arch": systemArch, + "x-installMethod": installerType + } + }).then((res) => { + // pipe the installer stream to the installer file + res.data.pipe(installerStream); + + // close the installer stream + installerStream.on('finish', () => { + installerStream.close(); + // resolve the promise + resolve({ code: 'downloaded', msg: 'Update downloaded successfully.', path: installerPath }); + }); + + // handle errors + installerStream.on('error', (err) => { + //logger.error('Failed to download and save update:', err); + reject({ code: 'download-save-failed', msg: 'Failed to download and save update.', err: err }); + }); + + // handle close + installerStream.on('close', () => { + //logger.log('Installer stream closed.'); + }); + }).catch((err) => { + logger.error('Failed to download and save update:', err); + reject({ code: 'download-failed', msg: 'Failed to download update.', err: err }); + }); + }).catch((err) => { + //logger.error('Failed to download update:', err); + return { code: 'download-failed', msg: 'Failed to download update.', err: err }; + }); +} + +async function installUpdate(installer) { + return new Promise((resolve, reject) => { + try { + // Current system information + //logger.log('Current system information:', sys.platform, sys.getVersion(), sys.arch); + + // Check if the current system is Windows + if (sys.isWin()) { + // Check if the system is before Windows 10 + if (sys.earlierThan('10.0.0')) { + // Windows 10 and above are supported, but windows 7 and 8 are not supported + logger.error('Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not installing updating...'); + reject({ err: 'old-os', msg: 'Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not installing update...' }); + } else { + // Install the update + logger.log('Installing update (win)...'); + + // Check if the installer path is valid + installerPath = path.resolve(installer); + if (!fs.existsSync(installerPath)) { + logger.error('Installer path is invalid:', installerPath); + reject({ code: 'invalid-installer', msg: 'Installer path is invalid.', path: installerPath }); + } + + // Create the log file + const logStream = fs.createWriteStream(path.join(global.paths.updateDir, 'install.log'), { flags: 'a' }); + + // Run the installer (msi or exe) + switch (installerType) { + case 'msi': + // Run the msi installer /passive, /quiet, /qn, /norestart + const msiExec = childProcess.spawn('cmd.exe', ['/c', 'msiexec', '/i "${installerPath}"', '/qn', '/norestart', `/log ${path.join(global.paths.updateDir, 'installer-exec.log')}`], { + detached: true, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + msiExec.on('error', (err) => { + //console.error('Failed to start process:', err); + reject({ code: 'process-failed', msg: 'Failed to start installation process.', err: err }); + }); + + msiExec.on('exit', (code, signal) => { + if (code === 0) { + //console.log('Update installed successfully.'); + resolve({ code: 'update-installed', msg: 'Update installed successfully.' }); + } else { + //console.error(`Installer process exited with code: ${code}, signal: ${signal}`); + reject({ code: 'install-failed', msg: 'Installer process failed.', exitCode: code, signal: signal }); + } + }); + + msiExec.stdout.on('data', (data) => { + //console.log(`stdout: ${data}`); + logStream.write(data); + }); + + msiExec.stderr.on('data', (data) => { + //console.error(`stderr: ${data}`); + logStream.write(data); + }); + + app.quit(); + break; + case 'exe': + // Run the exe installer /s + const exeExec = childProcess.spawn('cmd.exe', ['/c', installerPath], { + detached: true, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + exeExec.on('error', (err) => { + //console.error('Failed to start process:', err); + reject({ code: 'process-failed', msg: 'Failed to start installation process.', err: err }); + }); + + exeExec.on('exit', (code, signal) => { + if (code === 0) { + //console.log('Update installed successfully.'); + resolve({ code: 'update-installed', msg: 'Update installed successfully.' }); + } else { + //console.error(`Installer process exited with code: ${code}, signal: ${signal}`); + reject({ code: 'install-failed', msg: 'Installer process failed.', exitCode: code, signal: signal }); + } + }); + + exeExec.stdout.on('data', (data) => { + //console.log(`stdout: ${data}`); + logStream.write(data); + }); + + exeExec.stderr.on('data', (data) => { + //console.error(`stderr: ${data}`); + logStream.write(data); + }); + + app.quit(); + break; + default: + //logger.error('Installer type is invalid:', installerType); + reject({ code: 'invalid-installer-type', msg: 'Installer type is invalid.', type: installerType }); + break; + } + } + } + + // Check if the current system is macOS + if (sys.isMac()) { + // Check the current version of macOS, and whether we can use the pkg installer + if (sys.laterThan('10.0.0')) { + // Install the update + logger.log('Installing update (mac)...'); + + // Check if the installer path is valid + installerPath = path.resolve(installer); + if (!fs.existsSync(installerPath)) { + logger.error('Installer path is invalid:', installerPath); + reject({ code: 'invalid-installer', msg: 'Installer path is invalid.', path: installerPath }); + } + + // Create the log file + const logStream = fs.createWriteStream(path.join(global.paths.updateDir, 'install.log'), { flags: 'a' }); + + // Run the .pkg installer + const command = `sudo installer -pkg ${installerPath} -target /`; + const shellProcess = childProcess.spawn('sh', ['-c', command], { + detached: true, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + shellProcess.on('error', (err) => { + //console.error('Failed to start process:', err); + reject({ code: 'process-failed', msg: 'Failed to start installation process.', err: err }); + }); + + shellProcess.on('exit', (code, signal) => { + if (code === 0) { + //console.log('Update installed successfully.'); + resolve({ code: 'update-installed', msg: 'Update installed successfully.' }); + } else { + //console.error(`Installer process exited with code: ${code}, signal: ${signal}`); + reject({ code: 'install-failed', msg: 'Installer process failed.', exitCode: code, signal: signal }); + } + }); + + shellProcess.stdout.on('data', (data) => { + //console.log(`stdout: ${data}`); + logStream.write(data); + }); + + shellProcess.stderr.on('data', (data) => { + //console.error(`stderr: ${data}`); + logStream.write(data); + }); + } else { + // macOS versions before 10 are not supported + logger.error('macOS versions before 10 are not supported, not installing update...'); + reject({ err: 'old-os', msg: 'macOS versions before 10 are not supported, not installing update...' }); + } + } + + // Check if the current system is Linux (This should be handled via asarUpdater) + if (sys.isLinux()) { + // Check for updates, and if there are updates, download and install them (no system version check) + // Linux versions use AppImage, so we instead need to check for a new asar file so that the app can + // load the new asar instead of the packaged one (or even just delete the old appimage and download a new one) + //logger.log('Checking for updates (linux)...'); + logger.log('This is not implemented yet, please update manually.'); + //asarUpdate(); + } + + } catch (err) { + logger.error('Failed to install update:', err); + reject({ code: 'install-failed', msg: 'Failed to install update.', err: err }); + } + }); +}; + +async function downloadAndInstall() { + return new Promise((resolve, reject) => { + // Download the update installer + downloadUpdate().then((res) => { + if (res.code === 'downloaded') { + // Install the update + installUpdate(res.path).then((res) => { + if (res.code === 'install-failed') { + //logger.error('Failed to install update:', res.err); + reject({ code: 'install-failed', msg: 'Failed to install update.', err: res.err }); + } else { + //logger.log('Update installed successfully.'); + resolve({ code: 'update-installed', msg: 'Update installed successfully.' }); + } + }); + } else { + //logger.error('Failed to download update:', res.err); + reject({ code: 'download-failed', msg: 'Failed to download update.', err: res.err }); + } + }).catch((err) => { + //logger.error('Failed to download and install update:', err); + reject({ code: 'download-install-failed', msg: 'Failed to download and install update.', err: err }); + }); + }); +} + +module.exports = { + checkForUpdates, + downloadUpdate, + installUpdate, + downloadAndInstall +}; \ No newline at end of file diff --git a/src/app/utils/installType.js b/src/app/utils/installType.js new file mode 100644 index 0000000..4cdd436 --- /dev/null +++ b/src/app/utils/installType.js @@ -0,0 +1,121 @@ +const path = require('path'); +const { exec } = require('child_process'); +const os = require('os'); +const fs = require('fs'); + +// Get system information +const SystemInfo = require('./sysInfo'); + +// Get the current system platform +const sys = new SystemInfo(); + +// Only import the registry package if the system is Windows +let Registry; +if (sys.platform === 'win32') { + Registry = require('winreg'); +} + +// Get the uninstall entries from the registry +async function getUninstallEntries(filter = {}) { + if (sys.platform !== 'win32') { + console.error('This function only works on Windows.'); + return []; + } + + const registryPaths = [ + 'HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall', + 'HKLM\\\\Software\\\\Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall' + ]; + + let results = []; + + for (const regPath of registryPaths) { + const regKey = new Registry({ hive: Registry.HKLM, key: regPath }); + + try { + const subkeys = await new Promise((resolve, reject) => { + regKey.keys((err, keys) => { + if (err) reject(err); + else resolve(keys); + }); + }); + + for (const subkey of subkeys) { + const values = await new Promise((resolve, reject) => { + subkey.values((err, items) => { + if (err) reject(err); + else resolve(items); + }); + }); + + let entry = {}; + values.forEach(item => { + if (item.name === 'DisplayName') entry.DisplayName = item.value; + if (item.name === 'Publisher') entry.Publisher = item.value; + if (item.name === 'InstallDate') entry.InstallDate = item.value; + if (item.name === 'DisplayVersion') entry.DisplayVersion = item.value; + if (item.name === 'HelpLink') entry.HelpLink = item.value; + if (item.name === 'UninstallString') entry.UninstallString = item.value; + if (item.name === 'WindowsInstaller') entry.WindowsInstaller = item.value === '1' ? 'MSI' : 'EXE'; + }); + + if (entry.DisplayName && entry.UninstallString) { + // If WindowsInstaller value is missing, check UninstallString + if (!entry.WindowsInstaller) { + entry.WindowsInstaller = entry.UninstallString.includes('msiexec') ? 'MSI' : 'EXE'; + } + + results.push(entry); + } + } + } catch (error) { + console.error(`Error accessing registry path ${regPath}:`, error); + } + } + + results.sort((a, b) => a.DisplayName.localeCompare(b.DisplayName)); + + // Filter results based on the provided filter (e.g., { DisplayName: 'bskyDesktop' }) + if (filter.DisplayName) { + return results.filter(entry => entry.DisplayName?.includes(filter.DisplayName)); + } + + //console.log(results); + return results; +} + +async function checkAppInstallationMethod(appPath, bundleId) { + if (!fs.existsSync(appPath)) { + return { error: "App does not exist at the specified path." }; + } + + try { + // Check for Mac App Store receipt + const { stdout: masOutput } = await execPromise(`mdls -name kMDItemAppStoreReceiptURL "${appPath}"`); + if (!masOutput.includes("(null)")) { + return { type: "MAS" }; + } + + // Check for PKG installation + try { + const { stdout: pkgOutput } = await execPromise(`pkgutil --pkg-info "${bundleId}"`); + return { type: "PKG", stdout: pkgOutput }; + } catch { + // Ignore error; means PKG is not found + } + + // Check if manually installed (ZIP/DMG) + const receiptPath = path.join(appPath, 'Contents', '_MASReceipt', 'receipt'); + if (!fs.existsSync(receiptPath)) { + return { type: "ZIP/DMG" }; + } + + } catch (error) { + return { error: error.message }; + } +} + +module.exports = { + getUninstallEntries, + checkAppInstallationMethod +}; \ No newline at end of file diff --git a/src/app/utils/sysInfo.js b/src/app/utils/sysInfo.js index 66f9bf7..dc7709f 100644 --- a/src/app/utils/sysInfo.js +++ b/src/app/utils/sysInfo.js @@ -61,6 +61,16 @@ class SystemInfo { return !this.laterThan(compareVersion); } + // Get edition of the os + getEdition() { + if (this.isWin()) { + const edition = childProcess.execSync('wmic os get Caption').toString().trim(); + return edition.split('\n')[1].trim(); + } else { + return 'N/A'; + } + } + // Private: Parse version strings (e.g., "10.0.19045" -> [10, 0, 19045]) _parseVersion(version) { return version.split('.').map((num) => parseInt(num, 10) || 0); diff --git a/src/app/utils/userStyles.js b/src/app/utils/userStyles.js index b99ae1e..7b4cf88 100644 --- a/src/app/utils/userStyles.js +++ b/src/app/utils/userStyles.js @@ -1,5 +1,6 @@ const usercssMeta = require('usercss-meta'); const less = require('less'); +const stylus = require('stylus'); /** * Extracts all global variable and mixin definitions from CSS. @@ -72,8 +73,8 @@ function parseDomainRule(css, startPos) { if (!match) return null; const domains = match[0] - .match(/'[^']+'/g) // Extract all single-quoted domain values - .map(domain => domain.replace(/'/g, '').trim()); + .match(/['"][^'"]+['"]/g) // Extract all single- or double-quoted domain values + .map(domain => domain.replace(/['"]/g, '').trim()); // Remove quotes and trim whitespace return { domains, @@ -90,17 +91,26 @@ function parseMozRules(css) { const rules = {}; let currentPos = 0; + // Helper to extract global definitions (CSS outside @-moz-document) const globalCode = extractGlobalDefinitions(css); - while (true) { - const domainRule = parseDomainRule(css, currentPos); - if (!domainRule) break; + while (currentPos < css.length) { + // Match @-moz-document syntax and extract domains + const domainMatch = css.slice(currentPos).match(/@-moz-document\s+(.*?){/s); + if (!domainMatch) break; - const bracedContent = extractBracedContent(css, domainRule.ruleStart); + const domainsStr = domainMatch[1]; + const ruleStart = currentPos + domainMatch.index + domainMatch[0].length - 1; + + // Extract the content inside the braces for this @-moz-document rule + const bracedContent = extractBracedContent(css, ruleStart); if (!bracedContent) break; - // Add the CSS to all matched domains - for (const domain of domainRule.domains) { + // Parse the domains and add the CSS to each + const domainList = domainsStr.match(/domain\("([^"]+)"\)/g) || []; + const domains = domainList.map((d) => d.match(/domain\("([^"]+)"\)/)[1]); + + for (const domain of domains) { rules[domain] = `${globalCode}\n${bracedContent.content}`; } @@ -155,10 +165,17 @@ async function compileStyle(code, metadata, userVars = {}) { switch (metadata?.preprocessor?.toLowerCase()) { case 'less': - compiledCode = await compileLess(fullCode); + compiledCode = await compileLess(fullCode, vars); break; + case 'stylus': + compiledCode = await compileStylus(fullCode, vars); + break; + case 'sass': + throw Error('SASS preprocessor not supported yet. Skipping compilation.'); + case 'scss': + throw Error('SCSS preprocessor not supported yet. Skipping compilation.'); default: - compiledCode = code; // Return unmodified for unknown preprocessors + compiledCode = code; // Return unmodified for plain CSS/unknown preprocessor } // Parse domain rules @@ -167,10 +184,15 @@ async function compileStyle(code, metadata, userVars = {}) { // Compile each domain's CSS if needed const compiledRules = {}; for (const [domain, css] of Object.entries(domainRules)) { - if (metadata.preprocessor === 'less') { - compiledRules[domain] = await compileLess(css, vars); - } else { - compiledRules[domain] = css; + switch (metadata.preprocessor?.toLowerCase()) { + case 'less': + compiledRules[domain] = await compileLess(css, vars); + break; + case 'stylus': + compiledRules[domain] = await compileStylus(css, vars); + break; + default: + compiledRules[domain] = css; } } @@ -181,27 +203,55 @@ async function compileStyle(code, metadata, userVars = {}) { return { compiledCss: combinedCss, - sites: compiledRules // Map of domains to their CSS + sites: compiledRules, // Map of domains to their CSS }; } catch (error) { - console.error('Style compilation error:', error); - throw error; + //console.error('Style compilation error:', error); + return { + error + }; } } /** * Compiles LESS code to CSS. * @param {string} code - The LESS code. + * @param {object} vars - User-defined variables. * @returns {Promise} The compiled CSS. */ -async function compileLess(code) { - const { css } = await less.render(code, { - math: 'parens-division', - javascriptEnabled: true, - compress: false +async function compileLess(code, vars = {}) { + return new Promise((resolve, reject) => { + less.render(code, { + math: 'parens-division', + javascriptEnabled: true, + compress: false, + globalVars: vars + }, (err, output) => { + if (err) return reject(err); + resolve(output.css); + }); + }); +} + +/** + * Compiles Stylus code to CSS. + * @param {string} code - The Stylus code. + * @returns {Promise} The compiled CSS. + */ +async function compileStylus(code, vars = {}) { + console.log(vars, code); + return new Promise((resolve, reject) => { + var stlus = stylus(code); + stlus.set('compress', false); + for (const [key, value] of Object.entries(vars)) { + stlus.define(key, value); + } + stlus.render((err, output) => { + if (err) return reject(err); + resolve(output); + }); }); - return css; } module.exports = { diff --git a/src/app/window-state/index.js b/src/app/window-state/index.js new file mode 100644 index 0000000..cc26da6 --- /dev/null +++ b/src/app/window-state/index.js @@ -0,0 +1,224 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const electron = require('electron'); + +let remote; +try { + remote = require('@electron/remote'); +} catch (err) { + remote = electron.remote; // Fallback for older Electron versions +} + +module.exports = function (options) { + const app = electron.app || (remote ? remote.app : null); + const screen = electron.screen || (remote ? remote.screen : null); + let state; + let winRef; + let stateChangeTimer; + const eventHandlingDelay = 100; + const config = Object.assign({ + file: 'window-state.json', + path: app.getPath('userData'), + maximize: true, + fullScreen: true + }, options); + const fullStoreFileName = path.join(config.path, config.file); + + function isNormal(win) { + return !win.isMaximized() && !win.isMinimized() && !win.isFullScreen(); + } + + function hasBounds() { + return state && + Number.isInteger(state.x) && + Number.isInteger(state.y) && + Number.isInteger(state.width) && state.width > 0 && + Number.isInteger(state.height) && state.height > 0; + } + + function resetStateToDefault() { + const displayBounds = screen.getPrimaryDisplay().bounds; + + // Reset state to default values on the primary display + state = { + width: config.defaultWidth || 800, + height: config.defaultHeight || 600, + x: 0, + y: 0, + displayBounds + }; + } + + function windowWithinBounds(bounds) { + return ( + state.x >= bounds.x && + state.y >= bounds.y && + state.x + state.width <= bounds.x + bounds.width && + state.y + state.height <= bounds.y + bounds.height + ); + } + + function ensureWindowVisibleOnSomeDisplay() { + const visible = screen.getAllDisplays().some(display => { + return windowWithinBounds(display.bounds); + }); + + if (!visible) { + // Window is partially or fully not visible now. + // Reset it to safe defaults. + return resetStateToDefault(); + } + } + + function validateState() { + const isValid = state && (hasBounds() || state.isMaximized || state.isFullScreen); + if (!isValid) { + state = null; + return; + } + + if (hasBounds() && state.displayBounds) { + ensureWindowVisibleOnSomeDisplay(); + } + } + + function updateState(win) { + win = win || winRef; + if (!win || !state) return; + // Don't throw an error when window was closed + try { + const winBounds = win.getBounds(); + if (isNormal(win)) { + state.x = winBounds.x; + state.y = winBounds.y; + state.width = winBounds.width; + state.height = winBounds.height; + } + state.isMaximized = win.isMaximized(); + state.isFullScreen = win.isFullScreen(); + state.displayBounds = screen.getDisplayMatching(winBounds).bounds; + } catch (err) { } + } + + function saveState(win) { + // Update window state only if it was provided + if (win) { + updateState(win); + } + + // Save state + try { + fs.mkdirSync(path.dirname(fullStoreFileName), { recursive: true }); + fs.writeFileSync(fullStoreFileName, JSON.stringify(state)); + } catch (err) { + // Don't care + } + } + + function stateChangeHandler() { + // Handles both 'resize' and 'move' + clearTimeout(stateChangeTimer); + stateChangeTimer = setTimeout(() => saveState(winRef), eventHandlingDelay); + } + + function closeHandler() { + updateState(); + } + + function closedHandler() { + // Unregister listeners and save state + unmanage(); + saveState(); + } + + function manage(win) { + if (config.maximize && state.isMaximized) { + win.maximize(); + } + if (config.fullScreen && state.isFullScreen) { + win.setFullScreen(true); + } + + function resizeBrowserView() { + try { + const view = win.getBrowserView(); + if (view) { + const windowBounds = win.getBounds(); // Full window size + const screenBounds = screen.getPrimaryDisplay().bounds; // Screen size + + // Calculate the usable area by subtracting window chrome and any borders + const width = windowBounds.width; // Width of the full window + const height = windowBounds.height; // Height of the full window + + // Ensure BrowserView fits within the screen size and window size + view.setBounds({ + x: 0, + y: 0, + width: Math.min(width, screenBounds.width), // Prevent overflow horizontally + height: Math.min(height, screenBounds.height) // Prevent overflow vertically + }); + } + } catch (err) { + // Ignore errors if BrowserView is not present + } + } + + function stateChangeHandlerWithBrowserView() { + stateChangeHandler(); // Preserve original behavior + resizeBrowserView(); // Resize BrowserView if applicable + } + + win.on('resize', stateChangeHandler); + win.on('move', stateChangeHandler); + win.on('close', closeHandler); + win.on('closed', closedHandler); + + // Initial BrowserView resize in case window is already open + resizeBrowserView(); + + winRef = win; + } + + function unmanage() { + if (winRef) { + winRef.removeListener('resize', stateChangeHandler); + winRef.removeListener('move', stateChangeHandler); + clearTimeout(stateChangeTimer); + winRef.removeListener('close', closeHandler); + winRef.removeListener('closed', closedHandler); + winRef = null; + } + } + + // Load previous state + try { + state = JSON.parse(fs.readFileSync(fullStoreFileName, 'utf8')); + } catch (err) { + // Don't care + } + + // Check state validity + validateState(); + + // Set state fallback values + state = Object.assign({ + width: config.defaultWidth || 800, + height: config.defaultHeight || 600 + }, state || {}); + + return { + get x() { return state.x; }, + get y() { return state.y; }, + get width() { return state.width; }, + get height() { return state.height; }, + get displayBounds() { return state.displayBounds; }, + get isMaximized() { return state.isMaximized; }, + get isFullScreen() { return state.isFullScreen; }, + saveState, + unmanage, + manage, + resetStateToDefault + }; +}; \ No newline at end of file diff --git a/src/app/window-state/license b/src/app/window-state/license new file mode 100644 index 0000000..8078dfa --- /dev/null +++ b/src/app/window-state/license @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jakub Szwacz +Copyright (c) Marcel Wiehle (http://marcel.wiehle.me) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/ui/css/offline.css b/src/ui/css/offline.css new file mode 100644 index 0000000..be42e48 --- /dev/null +++ b/src/ui/css/offline.css @@ -0,0 +1,105 @@ +html, +body { + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; /* Prevent overflow from the body */ + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + background-color: #082032; + color: white; +} + +.container { + display: flex; + justify-content: center; + align-items: flex-start; /* Align items to the top */ + height: 100vh; /* Full viewport height */ + overflow-y: auto; /* Allow vertical scrolling */ + margin: 0; +} + +.divround { + background-color: #2c394b; + height: auto; /* Let it grow with content */ + width: 90vw; /* or set a max-width if preferred */ + max-width: 510px; /* Fixed width for your content */ + margin: auto; + padding: 2.375vh; /* Converted from 19px to vh */ + border-radius: 0.5em; + box-shadow: 2px 3px 7px 2px rgba(0, 0, 0, 0.02); + text-align: center; + overflow-y: auto; /* Allow scrolling inside the element if needed */ +} + +.circular { + border-radius: 125px; + -webkit-border-radius: 125px; + -moz-border-radius: 125px; + background-size: cover; + display: inline-block; + width: 250px; + height: 250px; +} + +.btn { + background-color: DodgerBlue; + border: none; + color: white; + padding: 10px 20px; + cursor: pointer; + font-size: 20px; + border-radius: 8px; +} + +/* Non-rounded border */ +.btn.nr { + border-radius: 0px; +} + +/* Full width */ +.btn.fw { + width: 100% +} + +/* Darker background on mouse-over */ +.btn:hover { + background-color: RoyalBlue; +} + +/* Success btn */ +.btn.success { + background-color: #a6efb8e6; +} + +/* Danger btn */ +.btn.danger { + background-color: #ffafb4e6; +} + +/* Warning btn */ +.btn.warning { + background-color: #ffefb4e6; +} + +/* Info btn */ +.btn.info { + background-color: #b4e4f9e6; +} + +/* button (input style) */ +.btn.input { + background-color: rgb(82, 124, 165); +} + +/* Darker on mouse-over */ +.btn.input:hover { + background-color: rgb(72, 109, 146); +} + +/* Darker on active */ +.btn.input:active { + background-color: rgb(66, 111, 155); +} \ No newline at end of file diff --git a/src/ui/css/splash.css b/src/ui/css/splash.css index 7c51188..0fed9fa 100644 --- a/src/ui/css/splash.css +++ b/src/ui/css/splash.css @@ -6,6 +6,7 @@ -webkit-user-drag: none; /* Disable selection */ -webkit-user-select: none; + user-select: none; } html, diff --git a/src/ui/offline.html b/src/ui/offline.html new file mode 100644 index 0000000..72de763 --- /dev/null +++ b/src/ui/offline.html @@ -0,0 +1,31 @@ + + + + No internet connection + + + + + +
+
+

No internet connection

+

We couldn't detect an internet connection. Please check your network and try again.

+ +
+
+ + + + \ No newline at end of file diff --git a/src/ui/preload.js b/src/ui/preload.js index ebb5220..b87d70e 100644 --- a/src/ui/preload.js +++ b/src/ui/preload.js @@ -1,33 +1,37 @@ const { contextBridge, ipcRenderer } = require('electron'); -// Expose IPC methods +// Expose protected methods that allow the renderer process to use +const allowedChannels = ["ui:badgeCount", "ui:notif", "ui:settings", "ui:openSettings", "app:restart"]; + +// Expose ipcRenderer to the renderer process contextBridge.exposeInMainWorld("ipc", { send: (channel, data) => { - ipcRenderer.send(channel, data); + if (allowedChannels.includes(channel)) { + ipcRenderer.send(channel, data); + } }, on: (channel, callback) => { - ipcRenderer.on(channel, (event, ...args) => callback(...args)); + if (allowedChannels.includes(channel)) { + ipcRenderer.on(channel, (event, ...args) => callback(...args)); + } }, }); -contextBridge.exposeInMainWorld("badge", { - update: (badgeNumber) => { - ipcRenderer.send('ui:badgeCount', badgeNumber); - } -}); - -// Helper to dynamically load a script and append it to the DOM -function loadScript(src, callback) { - const script = document.createElement('script'); - script.src = src; - script.onload = () => { - console.log(`Loaded script: ${src}`); - if (callback) callback(); - }; - script.onerror = (error) => { - console.error(`Failed to load script: ${src}`, error); - }; - document.body.appendChild(script); +// Load a script asynchronously +function loadScriptAsync(src) { + return new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = src; + script.onload = () => { + console.log(`Loaded script: ${src}`); + resolve(); + }; + script.onerror = (error) => { + console.error(`Failed to load script: ${src}`, error); + reject(error); + }; + document.body.appendChild(script); + }); } // Inject CSS into the DOM @@ -39,22 +43,19 @@ function injectCSS(href) { console.log(`Injected CSS: ${href}`); } -document.addEventListener('DOMContentLoaded', () => { - // Inject CSS - injectCSS('ui:///lib/izitoast.min.css'); - injectCSS('ui:///rend/extra-themes.css'); - injectCSS('ui:///css/fa/6.7.1/css/all.min.css'); +document.addEventListener("DOMContentLoaded", async () => { + injectCSS("ui:///lib/izitoast.min.css"); + injectCSS("ui:///rend/extra-themes.css"); + injectCSS("ui:///css/fa/6.7.1/css/all.min.css"); - // Load jQuery first - loadScript('ui:///lib/jquery-3.3.1.min.js', () => { - // Load confetti and iziToast after jQuery (ui libs) - loadScript('ui:///lib/confetti-1.9.3-browser.min.js'); - loadScript('ui:///lib/izitoast.min.js', () => { - // Load app specific scripts - loadScript('ui:///rend/register-handles.js'); - // Load custom implementations - loadScript('ui:///rend/bsky-ext.js'); - loadScript('ui:///rend/specialAnimations.js'); - }); - }); -}); + try { + await loadScriptAsync("ui:///lib/jquery-3.3.1.min.js"); + await loadScriptAsync("ui:///lib/confetti-1.9.3-browser.min.js"); + await loadScriptAsync("ui:///lib/izitoast.min.js"); + await loadScriptAsync("ui:///rend/register-handles.js"); + await loadScriptAsync("ui:///rend/bsky-ext.js"); + await loadScriptAsync("ui:///rend/specialAnimations.js"); + } catch (error) { + console.error("Failed to load one or more scripts.", error); + } +}); \ No newline at end of file diff --git a/src/ui/rend/register-splash.js b/src/ui/rend/register-splash.js index 5e25fdd..a37948a 100644 --- a/src/ui/rend/register-splash.js +++ b/src/ui/rend/register-splash.js @@ -36,6 +36,8 @@ document.addEventListener('DOMContentLoaded', () => { ipcRenderer.on('ui:progtext', (event, other) => { console.log('Updating progress text:', other); infotext.innerText = other.title; - progtext.innerText = other.subtitle; + if (other.subtitle) { + progtext.innerText = other.subtitle; + }; }); }); \ No newline at end of file