12 Commits
v1.1.0 ... main

Author SHA1 Message Date
oxmc
1bf59bcbde Merge pull request #15 from oxmc/dependabot/npm_and_yarn/axios-1.8.2
Bump axios from 1.7.8 to 1.8.2
2025-03-25 03:35:45 -04:00
dependabot[bot]
47d549b991 Bump axios from 1.7.8 to 1.8.2
Bumps [axios](https://github.com/axios/axios) from 1.7.8 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.8...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 07:34:47 +00:00
oxmc
c7515830d6 Merge pull request #14 from ploszukiwacz/main
rpm building support for the gh action
2025-03-25 03:33:24 -04:00
oxmc
d4a08a9ed1 remove xhook and make winreg optional dep 2025-03-25 03:32:18 -04:00
deepsource-io[bot]
8b8fe086ee ci: add .deepsource.toml 2025-02-22 19:41:51 +00:00
oxmc
25bf2cbd21 update readme (forgot) 2025-02-13 07:16:30 -08:00
ploszukiwacz
fbb22e766c workflow 2025-02-13 16:08:15 +01:00
IGoByARealyLongNickName
595bde4e8a Merge branch 'oxmc:main' into main 2025-02-13 16:03:15 +01:00
oxmc
b32fcbd8f1 Fix auto update (win tested) and add stylus 2025-02-13 06:34:03 -08:00
oxmc
ab6fe350d3 Change linux ia32 build to armv7l 2024-12-28 12:19:56 -08:00
PlOszukiwacz
ce9830bfbe rpm bulding 2024-12-28 13:16:16 +01:00
oxmc
d9c4a4a428 Improve app and add more install options (and fix old .dmg files) 2024-12-28 01:08:07 -08:00
35 changed files with 3001 additions and 709 deletions

7
.deepsource.toml Normal file
View File

@@ -0,0 +1,7 @@
version = 1
[[analyzers]]
name = "javascript"
[analyzers.meta]
environment = ["nodejs"]

View File

@@ -1,65 +1,64 @@
name: Bug name: Bug
description: Make sure you complete the template. Otherwise, it will be closed without further explanation! description: Make sure you complete the template. Otherwise, it will be closed without further explanation!
title: "[v<replace_this_with_your_bsky-desktop_version>] Replace this with your title" title: "[v<replace_this_with_your_bsky-desktop_version>] Replace this with your title"
labels: bug labels: [bug]
body: body:
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Is there an existing issue for this? 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.**_ 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: options:
- label: I have searched the existing issues - label: I have searched the existing issues
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Device information label: Device information
description: description: "_Please provide the following information:_"
value: | value: |
- OS: - OS:
- Hardware Specs: - Hardware Specs:
- Etc: - Etc:
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Describe the issue label: Describe the issue
description: _Please attach videos or screenshots if possible_ description: _Please attach videos or screenshots if possible_
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Steps to reproduce label: Steps to reproduce
description: _Please attach videos or screenshots if possible_ description: _Please attach videos or screenshots if possible_
value: | value: |
1. 1.
2. 2.
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: logs id: logs
attributes: attributes:
label: Crash log label: Crash log
description: _If the app crashes, **please provide the crash log**. description: _If the app crashes, **please provide the crash log**.
render: shell render: shell
- type: dropdown - type: dropdown
attributes: attributes:
label: Are you using the latest version of bsky-destop? If not, why? 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._ 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 multiple: false
options: options:
- ✅ Yes, I'm using the latest version of bsky-desktop - ✅ Yes, I'm using the latest version of bsky-desktop
- ❌ No, I'll explain with additional information below - ❌ No, I'll explain with additional information below
validations: validations:
required: true required: true
- type: textarea
attributes:
label: Additional information
validations:
required: false
- type: textarea
attributes:
label: Additional information
validations:
required: false

View File

@@ -31,6 +31,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Build (armv7l)
run: npm run build -- --arch armv7l
- name: Build (x64) - name: Build (x64)
run: npm run build -- --arch x64 run: npm run build -- --arch x64
@@ -49,6 +52,9 @@ jobs:
- name: Generate checksum - name: Generate checksum
run: | run: |
sha256sum dist/*.AppImage > dist/sha256sum.txt sha256sum dist/*.AppImage > dist/sha256sum.txt
sha256sum dist/*.deb >> dist/sha256sum.txt
sha256sum dist/*.zip >> dist/sha256sum.txt
sha256sum dist/*.rpm >> dist/sha256sum.txt
- name: Upload Linux Artifacts - name: Upload Linux Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -57,6 +63,9 @@ jobs:
name: linux-artifacts name: linux-artifacts
path: | path: |
dist/*.AppImage dist/*.AppImage
dist/*.deb
dist/*.zip
dist/*.rpm
dist/latest*.yml dist/latest*.yml
dist/sha256sum.txt dist/sha256sum.txt
@@ -64,7 +73,6 @@ jobs:
name: Build bsky-desktop (Windows) name: Build bsky-desktop (Windows)
runs-on: windows-latest runs-on: windows-latest
env: env:
ext: "exe"
GITHUB_TOKEN: ${{ secrets.GHT }} GITHUB_TOKEN: ${{ secrets.GHT }}
steps: steps:
@@ -78,16 +86,22 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Build (ia32)
run: npm run build -- --arch ia32
- name: Build (x64) - name: Build (x64)
run: npm run build -- --arch x64 run: npm run build -- --arch x64
- name: Build (arm64) - name: Build (arm64)
run: npm run build -- --arch arm64 run: npm run build -- --arch arm64
- name: Generate checksum - name: Generate checksum
run: | run: |
sha256sum dist/*.exe > dist/sha256sum.txt sha256sum dist/*.exe > dist/sha256sum.txt
sha256sum dist/*.msi >> dist/sha256sum.txt
sha256sum dist/*.appx >> dist/sha256sum.txt
sha256sum dist/*.zip >> dist/sha256sum.txt
- name: Upload Windows Artifacts - name: Upload Windows Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -96,6 +110,9 @@ jobs:
name: windows-artifacts name: windows-artifacts
path: | path: |
dist/*.exe dist/*.exe
dist/*.msi
dist/*.appx
dist/*.zip
dist/latest*.yml dist/latest*.yml
dist/sha256sum.txt dist/sha256sum.txt
@@ -103,7 +120,6 @@ jobs:
name: Build bsky-desktop (macOS) name: Build bsky-desktop (macOS)
runs-on: macos-latest runs-on: macos-latest
env: env:
ext: "dmg"
GITHUB_TOKEN: ${{ secrets.GHT }} GITHUB_TOKEN: ${{ secrets.GHT }}
steps: steps:
@@ -127,6 +143,8 @@ jobs:
- name: Generate checksum - name: Generate checksum
run: | run: |
shasum -a 256 dist/*.dmg > dist/sha256sum.txt shasum -a 256 dist/*.dmg > dist/sha256sum.txt
shasum -a 256 dist/*.pkg >> dist/sha256sum.txt
shasum -a 256 dist/*.zip >> dist/sha256sum.txt
- name: Upload macOS Artifacts - name: Upload macOS Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -135,6 +153,8 @@ jobs:
name: macos-artifacts name: macos-artifacts
path: | path: |
dist/*.dmg dist/*.dmg
dist/*.pkg
dist/*.zip
dist/latest*.yml dist/latest*.yml
dist/sha256sum.txt dist/sha256sum.txt
@@ -189,8 +209,16 @@ jobs:
generate_release_notes: true generate_release_notes: true
files: | files: |
dist/linux/*.AppImage dist/linux/*.AppImage
dist/linux/*.deb
dist/linux/*.zip
dist/linux/*.rpm
dist/windows/*.exe dist/windows/*.exe
dist/windows/*.msi
dist/windows/*.appx
dist/windows/*.zip
dist/macos/*.dmg dist/macos/*.dmg
dist/macos/*.pkg
dist/macos/*.zip
sha256sums.txt sha256sums.txt
aur: aur:

View File

@@ -3,7 +3,7 @@
Bsky Desktop is an Electron-based application for Bsky that allows users to manage their accounts and feeds directly from the app, rather than through the web interface. Bsky Desktop is an Electron-based application for Bsky that allows users to manage their accounts and feeds directly from the app, rather than through the web interface.
### Features: ### Features:
- Support for user styles (work in progress; currently only LESS preprocessor is supported) - Support for user styles (work in progress; currently only LESS and Stylus preprocessor is supported)
- Compatibility with both Manifest V2 and V3 Chrome extensions, though only a limited set of Chrome extension APIs are supported. For more information, visit: [Electron Extensions API Documentation](https://www.electronjs.org/docs/latest/api/extensions#supported-extensions-apis) - Compatibility with both Manifest V2 and V3 Chrome extensions, though only a limited set of Chrome extension APIs are supported. For more information, visit: [Electron Extensions API Documentation](https://www.electronjs.org/docs/latest/api/extensions#supported-extensions-apis)
### Working on: ### Working on:
@@ -14,6 +14,20 @@ Bsky Desktop is an Electron-based application for Bsky that allows users to mana
[![Packaging status](https://repology.org/badge/vertical-allrepos/bskydesktop.svg?columns=4&exclude_unsupported=1)](https://repology.org/project/bskydesktop/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/bskydesktop.svg?columns=4&exclude_unsupported=1)](https://repology.org/project/bskydesktop/versions)
#### Windows install options:
- Zip (x64, arm64, ia32)
- Setup (exe, msi, appx) (x64, arm64, ia32)
#### Mac install options:
- Zip (x64, arm64)
- Dmg (x64, arm64)
- Pkg (x64, arm64)
#### Linux install options:
- Zip (x64, arm64, ia32)
- AppImage (x64, arm64, ia32)
- Deb (x64, arm64, ia32)
### Build Instructions for Bsky Desktop ### Build Instructions for Bsky Desktop
To build and run Bsky Desktop locally, follow these steps: To build and run Bsky Desktop locally, follow these steps:

View File

@@ -1,116 +0,0 @@
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs/promises');
const process = require('process');
const packageJson = require('./package.json');
// Electron builder:
const electronBuilderPath = path.join('node_modules', 'electron-builder', 'cli.js');
// Parse command-line arguments:
const supportedPlatforms = ['win', 'mac', 'linux', 'mwl'];
const supportedArchitectures = ['x64', 'armv7l', 'arm64', 'ia32', 'universal'];
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') || null;
let platform, arch, build_args;
//console.log(supportedPlatforms, supportedArchitectures, cplatform, carch);
// Platform Name:
if (cplatform == null) {
switch (process.platform) {
case "win32":
platform = "win";
break;
case "darwin":
platform = "mac";
break;
case "linux":
platform = "linux";
break;
default:
platform = "mwl"; // Build for all
break;
}
} else {
if (!supportedPlatforms.includes(cplatform)) {
console.error(`Invalid platform specified. Supported platforms: ${supportedPlatforms.join(', ')}`);
process.exit(1);
}
platform = cplatform;
}
// Platform Arch:
if (carch == null) {
arch = process.arch === "arm" ? "armv7l" : process.arch;
} else {
arch = carch;
if (!supportedArchitectures.includes(arch)) {
console.error(`Invalid arch specified. Supported architectures: ${supportedArchitectures.join(', ')}`);
process.exit(1);
}
}
// Generate artifact name:
const artifactname = `${packageJson.build.productName}-${packageJson.version}-${platform}-${arch}`;
(async () => {
try {
// Additional build arguments: (started wiht --eb-nmehere)
const additionalArgs = args.filter((arg, index) => arg.startsWith('--eb-') && index % 2 !== 0);
// CLI Args:
build_args = [
electronBuilderPath,
`--${platform}`,
`--${arch}`,
`-c.artifactName="${artifactname}.\${ext}"`,
];
// If additional args are present:
if (additionalArgs.length > 0) {
build_args.push(...additionalArgs);
}
// If pack is true:
if (pack) {
build_args.push(`--dir`);
}
// Make CLI:
const cli = `node ${build_args.join(' ')}`;
console.info(`CLI: ${cli}`);
console.info(`Building ${artifactname}`);
const process = spawn(cli, { shell: true });
// Log stdout as it comes
process.stdout.on('data', (data) => {
console.log(data.toString().trim());
});
// Log stderr as it comes
process.stderr.on('data', (data) => {
console.error(data.toString().trim());
});
// Handle process exit
process.on('close', (code) => {
if (code === 0) {
console.info(`Build completed successfully!`);
} else {
console.error(`Process exited with code ${code}`);
}
});
process.on('error', (error) => {
console.error(`Failed to start process: ${error.message}`);
});
} catch (error) {
console.error('An error occurred:', error);
process.exit(1);
}
})();

129
build/build-app.js Normal file
View File

@@ -0,0 +1,129 @@
const path = require('path');
const process = require('process');
const builder = require('electron-builder');
const { Platform, Arch } = builder;
// Parse command-line arguments
const supportedPlatforms = ['win', 'mac', 'linux'];
const supportedArchitectures = ['x64', 'armv7l', 'arm64', 'ia32', 'universal'];
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;
if (!cplatform) {
platform = process.platform === 'win32' ? 'win' :
process.platform === 'darwin' ? 'mac' :
process.platform === 'linux' ? 'linux' : null;
} else if (!supportedPlatforms.includes(cplatform)) {
console.error(`Invalid platform specified. Supported platforms: ${supportedPlatforms.join(', ')}`);
process.exit(1);
} else {
platform = cplatform;
}
// Determine architecture
let arch = carch || (process.arch === 'arm' ? 'armv7l' : process.arch);
if (!supportedArchitectures.includes(arch)) {
console.error(`Invalid architecture specified. Supported architectures: ${supportedArchitectures.join(', ')}`);
process.exit(1);
}
// Map platform and architecture to electron-builder enums
const platformEnum = {
win: Platform.WINDOWS,
mac: Platform.MAC,
linux: Platform.LINUX
}[platform];
const archEnum = {
x64: Arch.x64,
armv7l: Arch.armv7l,
arm64: Arch.arm64,
ia32: Arch.ia32,
universal: Arch.universal
}[arch];
// Additional build arguments: (starting with --eb-)
const buildArgs = args.filter((arg) => arg.startsWith('--eb-'));
// If additional args are present, add them to the buildArgs array
if (buildArgs.length > 0) {
console.log('Additional build arguments:', buildArgs);
}
// If pack is true, add --dir to buildArgs
if (pack) {
buildArgs.push('--dir');
}
// 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 = 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
const packageJson = require('../package.json');
const artifactName = `${buildConfig.productName}-${packageJson.version}-${platform}-${arch}.\${ext}`;
// Electron Builder configuration
/**
* @type {import('electron-builder').Configuration}
*/
const options = {
artifactName,
nsis: {
deleteAppDataOnUninstall: true,
oneClick: true,
perMachine: false
},
/*dmg: {
contents: [
{ type: 'file', x: 255, y: 85 },
{ type: 'link', path: '/Applications', x: 253, y: 325 }
]
},*/
...buildConfig
};
// Build process
builder.build({
targets: platformEnum.createTarget(null, archEnum),
config: options
}).then(result => {
console.log("Build completed:", JSON.stringify(result, null, 2));
}).catch(error => {
console.error("Build failed:", error);
});

56
build/build-config.json Normal file
View File

@@ -0,0 +1,56 @@
{
"appId": "com.oxmc.bskyDesktop",
"productName": "bskyDesktop",
"asarUnpack": [
"./node_modules/node-notifier/**/*"
],
"removePackageScripts": true,
"mac": {
"target": [
"dmg",
"pkg",
"zip"
],
"icon": "src/ui/images/mac.icns",
"category": "Network"
},
"pkg": {
"installLocation": "/Applications",
"allowAnywhere": true,
"allowCurrentUserHome": true,
"allowRootDirectory": true,
"isVersionChecked": true,
"isRelocatable": false,
"overwriteAction": "upgrade"
},
"linux": {
"target": [
"appimage",
"deb",
"rpm",
"zip"
],
"icon": "src/ui/images/icons",
"category": "Network"
},
"win": {
"target": [
"nsis",
"appx",
"msi",
"zip"
],
"icon": "src/ui/images/logo.ico"
},
"appx": {
"identityName": "BskyDesktop"
},
"protocols": [
{
"name": "bsky",
"schemes": [
"bsky"
]
}
]
}

329
package-lock.json generated
View File

@@ -1,31 +1,40 @@
{ {
"name": "bsky-desktop", "name": "bsky-desktop",
"version": "1.1.0", "version": "1.1.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bsky-desktop", "name": "bsky-desktop",
"version": "1.1.0", "version": "1.1.3",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@electron/asar": "^3.2.17", "@electron/asar": "^3.2.17",
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.2",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"axios": "^1.6.8", "axios": "^1.8.2",
"electron-window-state": "^5.0.3",
"less": "^4.2.1", "less": "^4.2.1",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"node-notifier": "^10.0.0", "node-notifier": "^10.0.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"stylus": "^0.64.0",
"usercss-meta": "^0.12.0", "usercss-meta": "^0.12.0",
"v8-compile-cache": "^2.3.0" "v8-compile-cache": "^2.3.0"
}, },
"devDependencies": { "devDependencies": {
"electron": "^29.4.6", "electron": "^29.4.6",
"electron-builder": "^24.13.3" "electron-builder": "^24.13.3"
},
"optionalDependencies": {
"winreg": "^1.2.5"
} }
}, },
"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": { "node_modules/@develar/schema-utils": {
"version": "2.6.5", "version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@@ -254,7 +263,6 @@
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"string-width": "^5.1.2", "string-width": "^5.1.2",
@@ -272,7 +280,6 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -285,7 +292,6 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -298,14 +304,12 @@
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@isaacs/cliui/node_modules/string-width": { "node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"eastasianwidth": "^0.2.0", "eastasianwidth": "^0.2.0",
@@ -323,7 +327,6 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
@@ -339,7 +342,6 @@
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^6.1.0", "ansi-styles": "^6.1.0",
@@ -425,7 +427,6 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"engines": { "engines": {
@@ -637,7 +638,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -647,7 +647,6 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
@@ -879,9 +878,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.8", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
@@ -1191,7 +1190,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
@@ -1204,7 +1202,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": { "node_modules/combined-stream": {
@@ -1389,7 +1386,6 @@
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
@@ -1588,7 +1584,6 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ejs": { "node_modules/ejs": {
@@ -1682,22 +1677,10 @@
"mime": "^2.5.2" "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": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
@@ -1903,7 +1886,6 @@
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.0", "cross-spawn": "^7.0.0",
@@ -1920,7 +1902,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -2377,7 +2358,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -2428,7 +2408,6 @@
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
@@ -2779,13 +2758,13 @@
}, },
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.6", "version": "1.2.6",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/minipass": { "node_modules/minipass": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -2818,16 +2797,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/mkdirp": {
"version": "0.5.6",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"license": "MIT" "license": "MIT"
@@ -2920,7 +2889,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/parse-node-version": { "node_modules/parse-node-version": {
@@ -2943,7 +2911,6 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -2953,7 +2920,6 @@
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
@@ -2970,7 +2936,6 @@
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/pend": { "node_modules/pend": {
@@ -3057,10 +3022,11 @@
} }
}, },
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.1.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@@ -3232,7 +3198,6 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/semver": { "node_modules/semver": {
@@ -3273,7 +3238,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
@@ -3286,7 +3250,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -3449,7 +3412,6 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@@ -3465,7 +3427,6 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@@ -3480,7 +3441,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
@@ -3494,7 +3454,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
@@ -3503,6 +3462,90 @@
"node": ">=8" "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": { "node_modules/sumchecker": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
@@ -3739,6 +3782,13 @@
"node": ">= 8" "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",
"optional": true
},
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -3762,7 +3812,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
@@ -3883,6 +3932,11 @@
} }
}, },
"dependencies": { "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": { "@develar/schema-utils": {
"version": "2.6.5", "version": "2.6.5",
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@@ -4046,7 +4100,6 @@
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"requires": { "requires": {
"string-width": "^5.1.2", "string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0", "string-width-cjs": "npm:string-width@^4.2.0",
@@ -4059,26 +4112,22 @@
"ansi-regex": { "ansi-regex": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="
"dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
"dev": true
}, },
"emoji-regex": { "emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
"dev": true
}, },
"string-width": { "string-width": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"requires": { "requires": {
"eastasianwidth": "^0.2.0", "eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2", "emoji-regex": "^9.2.2",
@@ -4089,7 +4138,6 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
} }
@@ -4098,7 +4146,6 @@
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"requires": { "requires": {
"ansi-styles": "^6.1.0", "ansi-styles": "^6.1.0",
"string-width": "^5.0.1", "string-width": "^5.0.1",
@@ -4156,7 +4203,6 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true "optional": true
}, },
"@sindresorhus/is": { "@sindresorhus/is": {
@@ -4317,14 +4363,12 @@
"ansi-regex": { "ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
"dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": { "requires": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
@@ -4508,9 +4552,9 @@
"dev": true "dev": true
}, },
"axios": { "axios": {
"version": "1.7.8", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"requires": { "requires": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@@ -4718,7 +4762,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": { "requires": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
} }
@@ -4726,8 +4769,7 @@
"color-name": { "color-name": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"dev": true
}, },
"combined-stream": { "combined-stream": {
"version": "1.0.8", "version": "1.0.8",
@@ -4860,7 +4902,6 @@
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"requires": { "requires": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@@ -4988,8 +5029,7 @@
"eastasianwidth": { "eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"dev": true
}, },
"ejs": { "ejs": {
"version": "3.1.10", "version": "3.1.10",
@@ -5057,18 +5097,10 @@
"mime": "^2.5.2" "mime": "^2.5.2"
} }
}, },
"electron-window-state": {
"version": "5.0.3",
"requires": {
"jsonfile": "^4.0.0",
"mkdirp": "^0.5.1"
}
},
"emoji-regex": { "emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"dev": true
}, },
"end-of-stream": { "end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
@@ -5210,7 +5242,6 @@
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dev": true,
"requires": { "requires": {
"cross-spawn": "^7.0.0", "cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1" "signal-exit": "^4.0.1"
@@ -5219,8 +5250,7 @@
"signal-exit": { "signal-exit": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="
"dev": true
} }
} }
}, },
@@ -5515,8 +5545,7 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
"dev": true
}, },
"is-what": { "is-what": {
"version": "3.14.1", "version": "3.14.1",
@@ -5549,7 +5578,6 @@
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"requires": { "requires": {
"@isaacs/cliui": "^8.0.2", "@isaacs/cliui": "^8.0.2",
"@pkgjs/parseargs": "^0.11.0" "@pkgjs/parseargs": "^0.11.0"
@@ -5805,13 +5833,13 @@
} }
}, },
"minimist": { "minimist": {
"version": "1.2.6" "version": "1.2.6",
"dev": true
}, },
"minipass": { "minipass": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="
"dev": true
}, },
"minizlib": { "minizlib": {
"version": "2.1.2", "version": "2.1.2",
@@ -5834,12 +5862,6 @@
} }
} }
}, },
"mkdirp": {
"version": "0.5.6",
"requires": {
"minimist": "^1.2.6"
}
},
"ms": { "ms": {
"version": "2.1.2" "version": "2.1.2"
}, },
@@ -5901,8 +5923,7 @@
"package-json-from-dist": { "package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
"dev": true
}, },
"parse-node-version": { "parse-node-version": {
"version": "1.0.1", "version": "1.0.1",
@@ -5915,14 +5936,12 @@
"path-key": { "path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
"dev": true
}, },
"path-scurry": { "path-scurry": {
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"requires": { "requires": {
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@@ -5931,8 +5950,7 @@
"lru-cache": { "lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
"dev": true
} }
} }
}, },
@@ -5999,9 +6017,9 @@
} }
}, },
"punycode": { "punycode": {
"version": "2.1.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true "dev": true
}, },
"quick-lru": { "quick-lru": {
@@ -6130,8 +6148,7 @@
"sax": { "sax": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
"devOptional": true
}, },
"semver": { "semver": {
"version": "7.6.3", "version": "7.6.3",
@@ -6157,7 +6174,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": { "requires": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
} }
@@ -6165,8 +6181,7 @@
"shebang-regex": { "shebang-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
"dev": true
}, },
"shellwords": { "shellwords": {
"version": "0.1.1" "version": "0.1.1"
@@ -6277,7 +6292,6 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@@ -6288,7 +6302,6 @@
"version": "npm:string-width@4.2.3", "version": "npm:string-width@4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@@ -6299,7 +6312,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
} }
@@ -6308,11 +6320,63 @@
"version": "npm:strip-ansi@6.0.1", "version": "npm:strip-ansi@6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^5.0.1" "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": { "sumchecker": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
@@ -6479,6 +6543,12 @@
"isexe": "^2.0.0" "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==",
"optional": true
},
"wrap-ansi": { "wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -6494,7 +6564,6 @@
"version": "npm:wrap-ansi@7.0.0", "version": "npm:wrap-ansi@7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": { "requires": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
"string-width": "^4.1.0", "string-width": "^4.1.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "bsky-desktop", "name": "bsky-desktop",
"version": "1.1.0", "version": "1.1.3",
"description": "A desktop app of bsky.app", "description": "A desktop app of bsky.app",
"main": "src/app/main.js", "main": "src/app/main.js",
"scripts": { "scripts": {
@@ -8,89 +8,31 @@
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "electron-builder",
"rebuild": "electron-rebuild", "rebuild": "electron-rebuild",
"build": "node ./build-app.js" "build": "node ./build/build-app.js"
}, },
"author": { "author": {
"name": "oxmc", "name": "oxmc",
"email": "oxmc7769.mail@gmail.com" "email": "oxmc7769.mail@gmail.com"
}, },
"contributors": [
{
"name": "oxmc",
"email": "contact@oxmc.is-a.dev"
},
{
"name": "PlOszukiwaczDEV",
"email": "ploszukiwacz1@duck.com"
},
{
"name": "GizzyUwU",
"email": "me@gizzy.pro"
}
],
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"devDependencies": { "devDependencies": {
"electron": "^29.4.6", "electron": "^29.4.6",
"electron-builder": "^24.13.3" "electron-builder": "^24.13.3"
}, },
"optionalDependencies": {
"winreg": "^1.2.5"
},
"dependencies": { "dependencies": {
"@electron/asar": "^3.2.17", "@electron/asar": "^3.2.17",
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.2",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"axios": "^1.6.8", "axios": "^1.8.2",
"electron-window-state": "^5.0.3",
"less": "^4.2.1", "less": "^4.2.1",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"node-notifier": "^10.0.0", "node-notifier": "^10.0.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"stylus": "^0.64.0",
"usercss-meta": "^0.12.0", "usercss-meta": "^0.12.0",
"v8-compile-cache": "^2.3.0" "v8-compile-cache": "^2.3.0"
},
"build": {
"appId": "com.oxmc.bskyDesktop",
"productName": "bskyDesktop",
"asarUnpack": [
"./node_modules/node-notifier/**/*"
],
"artifactName": "bsky-desktop.${ext}",
"mac": {
"target": [
"dmg",
"pkg"
],
"icon": "build/icons/mac-icon.icns",
"category": "Network"
},
"pkg": {
"scripts": "build/mac-pkg/scripts",
"installLocation": "/Applications",
"allowAnywhere": true,
"allowCurrentUserHome": true,
"allowRootDirectory": true,
"isVersionChecked": true,
"isRelocatable": false,
"overwriteAction": "upgrade"
},
"linux": {
"target": [
"appimage"
],
"icon": "src/ui/images/icons",
"category": "Network"
},
"win": {
"target": [
"nsis"
],
"icon": "src/ui/images/logo.ico"
},
"protocols": [
{
"name": "bsky",
"schemes": [
"bsky"
]
}
]
} }
} }

17
src/app/contributors.json Normal file
View File

@@ -0,0 +1,17 @@
[
{
"name": "oxmc",
"email": "contact@oxmc.is-a.dev",
"url": "https://oxmc.is-a.dev"
},
{
"name": "PlOszukiwaczDEV",
"email": "ploszukiwacz1@duck.com",
"url": "https://ploszukiwacz.pl"
},
{
"name": "GizzyUwU",
"email": "me@gizzy.pro",
"url": "https://gizzy.pro"
}
]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 devsnek
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.

View File

@@ -0,0 +1,660 @@
'use strict';
const EventEmitter = require('events');
const { setTimeout, clearTimeout } = require('timers');
const fetch = require('node-fetch');
const transports = require('./transports');
const { RPCCommands, RPCEvents, RelationshipTypes } = require('./constants');
const { pid: getPid, uuid } = require('./util');
function subKey(event, args) {
return `${event}${JSON.stringify(args)}`;
}
/**
* @typedef {RPCClientOptions}
* @extends {ClientOptions}
* @prop {string} transport RPC transport. one of `ipc` or `websocket`
*/
/**
* The main hub for interacting with Discord RPC
* @extends {BaseClient}
*/
class RPCClient extends EventEmitter {
/**
* @param {RPCClientOptions} [options] Options for the client.
* You must provide a transport
*/
constructor(options = {}) {
super();
this.options = options;
this.accessToken = null;
this.clientId = null;
/**
* Application used in this client
* @type {?ClientApplication}
*/
this.application = null;
/**
* User used in this application
* @type {?User}
*/
this.user = null;
const Transport = transports[options.transport];
if (!Transport) {
throw new TypeError('RPC_INVALID_TRANSPORT', options.transport);
}
this.fetch = (method, path, { data, query } = {}) =>
fetch(`${this.fetch.endpoint}${path}${query ? new URLSearchParams(query) : ''}`, {
method,
body: data,
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
}).then(async (r) => {
const body = await r.json();
if (!r.ok) {
const e = new Error(r.status);
e.body = body;
throw e;
}
return body;
});
this.fetch.endpoint = 'https://discord.com/api';
/**
* Raw transport userd
* @type {RPCTransport}
* @private
*/
this.transport = new Transport(this);
this.transport.on('message', this._onRpcMessage.bind(this));
/**
* Map of nonces being expected from the transport
* @type {Map}
* @private
*/
this._expecting = new Map();
this._connectPromise = undefined;
}
/**
* Search and connect to RPC
*/
connect(clientId) {
if (this._connectPromise) {
return this._connectPromise;
}
this._connectPromise = new Promise((resolve, reject) => {
this.clientId = clientId;
const timeout = setTimeout(() => reject(new Error('RPC_CONNECTION_TIMEOUT')), 10e3);
timeout.unref();
this.once('connected', () => {
clearTimeout(timeout);
resolve(this);
});
this.transport.once('close', () => {
this._expecting.forEach((e) => {
e.reject(new Error('connection closed'));
});
this.emit('disconnected');
reject(new Error('connection closed'));
});
this.transport.connect().catch(reject);
});
return this._connectPromise;
}
/**
* @typedef {RPCLoginOptions}
* @param {string} clientId Client ID
* @param {string} [clientSecret] Client secret
* @param {string} [accessToken] Access token
* @param {string} [rpcToken] RPC token
* @param {string} [tokenEndpoint] Token endpoint
* @param {string[]} [scopes] Scopes to authorize with
*/
/**
* Performs authentication flow. Automatically calls Client#connect if needed.
* @param {RPCLoginOptions} options Options for authentication.
* At least one property must be provided to perform login.
* @example client.login({ clientId: '1234567', clientSecret: 'abcdef123' });
* @returns {Promise<RPCClient>}
*/
async login(options = {}) {
let { clientId, accessToken } = options;
await this.connect(clientId);
if (!options.scopes) {
this.emit('ready');
return this;
}
if (!accessToken) {
accessToken = await this.authorize(options);
}
return this.authenticate(accessToken);
}
/**
* Request
* @param {string} cmd Command
* @param {Object} [args={}] Arguments
* @param {string} [evt] Event
* @returns {Promise}
* @private
*/
request(cmd, args, evt) {
return new Promise((resolve, reject) => {
const nonce = uuid();
this.transport.send({ cmd, args, evt, nonce });
this._expecting.set(nonce, { resolve, reject });
});
}
/**
* Message handler
* @param {Object} message message
* @private
*/
_onRpcMessage(message) {
if (message.cmd === RPCCommands.DISPATCH && message.evt === RPCEvents.READY) {
if (message.data.user) {
this.user = message.data.user;
}
this.emit('connected');
} else if (this._expecting.has(message.nonce)) {
const { resolve, reject } = this._expecting.get(message.nonce);
if (message.evt === 'ERROR') {
const e = new Error(message.data.message);
e.code = message.data.code;
e.data = message.data;
reject(e);
} else {
resolve(message.data);
}
this._expecting.delete(message.nonce);
} else {
this.emit(message.evt, message.data);
}
}
/**
* Authorize
* @param {Object} options options
* @returns {Promise}
* @private
*/
async authorize({ scopes, clientSecret, rpcToken, redirectUri, prompt } = {}) {
if (clientSecret && rpcToken === true) {
const body = await this.fetch('POST', '/oauth2/token/rpc', {
data: new URLSearchParams({
client_id: this.clientId,
client_secret: clientSecret,
}),
});
rpcToken = body.rpc_token;
}
const { code } = await this.request('AUTHORIZE', {
scopes,
client_id: this.clientId,
prompt,
rpc_token: rpcToken,
});
const response = await this.fetch('POST', '/oauth2/token', {
data: new URLSearchParams({
client_id: this.clientId,
client_secret: clientSecret,
code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
}),
});
return response.access_token;
}
/**
* Authenticate
* @param {string} accessToken access token
* @returns {Promise}
* @private
*/
authenticate(accessToken) {
return this.request('AUTHENTICATE', { access_token: accessToken })
.then(({ application, user }) => {
this.accessToken = accessToken;
this.application = application;
this.user = user;
this.emit('ready');
return this;
});
}
/**
* Fetch a guild
* @param {Snowflake} id Guild ID
* @param {number} [timeout] Timeout request
* @returns {Promise<Guild>}
*/
getGuild(id, timeout) {
return this.request(RPCCommands.GET_GUILD, { guild_id: id, timeout });
}
/**
* Fetch all guilds
* @param {number} [timeout] Timeout request
* @returns {Promise<Collection<Snowflake, Guild>>}
*/
getGuilds(timeout) {
return this.request(RPCCommands.GET_GUILDS, { timeout });
}
/**
* Get a channel
* @param {Snowflake} id Channel ID
* @param {number} [timeout] Timeout request
* @returns {Promise<Channel>}
*/
getChannel(id, timeout) {
return this.request(RPCCommands.GET_CHANNEL, { channel_id: id, timeout });
}
/**
* Get all channels
* @param {Snowflake} [id] Guild ID
* @param {number} [timeout] Timeout request
* @returns {Promise<Collection<Snowflake, Channel>>}
*/
async getChannels(id, timeout) {
const { channels } = await this.request(RPCCommands.GET_CHANNELS, {
timeout,
guild_id: id,
});
return channels;
}
/**
* @typedef {CertifiedDevice}
* @prop {string} type One of `AUDIO_INPUT`, `AUDIO_OUTPUT`, `VIDEO_INPUT`
* @prop {string} uuid This device's Windows UUID
* @prop {object} vendor Vendor information
* @prop {string} vendor.name Vendor's name
* @prop {string} vendor.url Vendor's url
* @prop {object} model Model information
* @prop {string} model.name Model's name
* @prop {string} model.url Model's url
* @prop {string[]} related Array of related product's Windows UUIDs
* @prop {boolean} echoCancellation If the device has echo cancellation
* @prop {boolean} noiseSuppression If the device has noise suppression
* @prop {boolean} automaticGainControl If the device has automatic gain control
* @prop {boolean} hardwareMute If the device has a hardware mute
*/
/**
* Tell discord which devices are certified
* @param {CertifiedDevice[]} devices Certified devices to send to discord
* @returns {Promise}
*/
setCertifiedDevices(devices) {
return this.request(RPCCommands.SET_CERTIFIED_DEVICES, {
devices: devices.map((d) => ({
type: d.type,
id: d.uuid,
vendor: d.vendor,
model: d.model,
related: d.related,
echo_cancellation: d.echoCancellation,
noise_suppression: d.noiseSuppression,
automatic_gain_control: d.automaticGainControl,
hardware_mute: d.hardwareMute,
})),
});
}
/**
* @typedef {UserVoiceSettings}
* @prop {Snowflake} id ID of the user these settings apply to
* @prop {?Object} [pan] Pan settings, an object with `left` and `right` set between
* 0.0 and 1.0, inclusive
* @prop {?number} [volume=100] The volume
* @prop {bool} [mute] If the user is muted
*/
/**
* Set the voice settings for a user, by id
* @param {Snowflake} id ID of the user to set
* @param {UserVoiceSettings} settings Settings
* @returns {Promise}
*/
setUserVoiceSettings(id, settings) {
return this.request(RPCCommands.SET_USER_VOICE_SETTINGS, {
user_id: id,
pan: settings.pan,
mute: settings.mute,
volume: settings.volume,
});
}
/**
* Move the user to a voice channel
* @param {Snowflake} id ID of the voice channel
* @param {Object} [options] Options
* @param {number} [options.timeout] Timeout for the command
* @param {boolean} [options.force] Force this move. This should only be done if you
* have explicit permission from the user.
* @returns {Promise}
*/
selectVoiceChannel(id, { timeout, force = false } = {}) {
return this.request(RPCCommands.SELECT_VOICE_CHANNEL, { channel_id: id, timeout, force });
}
/**
* Move the user to a text channel
* @param {Snowflake} id ID of the voice channel
* @param {Object} [options] Options
* @param {number} [options.timeout] Timeout for the command
* have explicit permission from the user.
* @returns {Promise}
*/
selectTextChannel(id, { timeout } = {}) {
return this.request(RPCCommands.SELECT_TEXT_CHANNEL, { channel_id: id, timeout });
}
/**
* Get current voice settings
* @returns {Promise}
*/
getVoiceSettings() {
return this.request(RPCCommands.GET_VOICE_SETTINGS)
.then((s) => ({
automaticGainControl: s.automatic_gain_control,
echoCancellation: s.echo_cancellation,
noiseSuppression: s.noise_suppression,
qos: s.qos,
silenceWarning: s.silence_warning,
deaf: s.deaf,
mute: s.mute,
input: {
availableDevices: s.input.available_devices,
device: s.input.device_id,
volume: s.input.volume,
},
output: {
availableDevices: s.output.available_devices,
device: s.output.device_id,
volume: s.output.volume,
},
mode: {
type: s.mode.type,
autoThreshold: s.mode.auto_threshold,
threshold: s.mode.threshold,
shortcut: s.mode.shortcut,
delay: s.mode.delay,
},
}));
}
/**
* Set current voice settings, overriding the current settings until this session disconnects.
* This also locks the settings for any other rpc sessions which may be connected.
* @param {Object} args Settings
* @returns {Promise}
*/
setVoiceSettings(args) {
return this.request(RPCCommands.SET_VOICE_SETTINGS, {
automatic_gain_control: args.automaticGainControl,
echo_cancellation: args.echoCancellation,
noise_suppression: args.noiseSuppression,
qos: args.qos,
silence_warning: args.silenceWarning,
deaf: args.deaf,
mute: args.mute,
input: args.input ? {
device_id: args.input.device,
volume: args.input.volume,
} : undefined,
output: args.output ? {
device_id: args.output.device,
volume: args.output.volume,
} : undefined,
mode: args.mode ? {
type: args.mode.type,
auto_threshold: args.mode.autoThreshold,
threshold: args.mode.threshold,
shortcut: args.mode.shortcut,
delay: args.mode.delay,
} : undefined,
});
}
/**
* Capture a shortcut using the client
* The callback takes (key, stop) where `stop` is a function that will stop capturing.
* This `stop` function must be called before disconnecting or else the user will have
* to restart their client.
* @param {Function} callback Callback handling keys
* @returns {Promise<Function>}
*/
captureShortcut(callback) {
const subid = subKey(RPCEvents.CAPTURE_SHORTCUT_CHANGE);
const stop = () => {
this._subscriptions.delete(subid);
return this.request(RPCCommands.CAPTURE_SHORTCUT, { action: 'STOP' });
};
this._subscriptions.set(subid, ({ shortcut }) => {
callback(shortcut, stop);
});
return this.request(RPCCommands.CAPTURE_SHORTCUT, { action: 'START' })
.then(() => stop);
}
/**
* Sets the presence for the logged in user.
* @param {object} args The rich presence to pass.
* @param {number} [pid] The application's process ID. Defaults to the executing process' PID.
* @returns {Promise}
*/
setActivity(args = {}, pid = getPid()) {
let timestamps;
let assets;
let party;
let secrets;
if (args.startTimestamp || args.endTimestamp) {
timestamps = {
start: args.startTimestamp,
end: args.endTimestamp,
};
if (timestamps.start instanceof Date) {
timestamps.start = Math.round(timestamps.start.getTime());
}
if (timestamps.end instanceof Date) {
timestamps.end = Math.round(timestamps.end.getTime());
}
if (timestamps.start > 2147483647000) {
throw new RangeError('timestamps.start must fit into a unix timestamp');
}
if (timestamps.end > 2147483647000) {
throw new RangeError('timestamps.end must fit into a unix timestamp');
}
}
if (
args.largeImageKey || args.largeImageText
|| args.smallImageKey || args.smallImageText
) {
assets = {
large_image: args.largeImageKey,
large_text: args.largeImageText,
small_image: args.smallImageKey,
small_text: args.smallImageText,
};
}
if (args.partySize || args.partyId || args.partyMax) {
party = { id: args.partyId };
if (args.partySize || args.partyMax) {
party.size = [args.partySize, args.partyMax];
}
}
if (args.matchSecret || args.joinSecret || args.spectateSecret) {
secrets = {
match: args.matchSecret,
join: args.joinSecret,
spectate: args.spectateSecret,
};
}
return this.request(RPCCommands.SET_ACTIVITY, {
pid,
activity: {
state: args.state,
details: args.details,
timestamps,
assets,
party,
secrets,
buttons: args.buttons,
instance: !!args.instance,
},
});
}
/**
* Clears the currently set presence, if any. This will hide the "Playing X" message
* displayed below the user's name.
* @param {number} [pid] The application's process ID. Defaults to the executing process' PID.
* @returns {Promise}
*/
clearActivity(pid = getPid()) {
return this.request(RPCCommands.SET_ACTIVITY, {
pid,
});
}
/**
* Invite a user to join the game the RPC user is currently playing
* @param {User} user The user to invite
* @returns {Promise}
*/
sendJoinInvite(user) {
return this.request(RPCCommands.SEND_ACTIVITY_JOIN_INVITE, {
user_id: user.id || user,
});
}
/**
* Request to join the game the user is playing
* @param {User} user The user whose game you want to request to join
* @returns {Promise}
*/
sendJoinRequest(user) {
return this.request(RPCCommands.SEND_ACTIVITY_JOIN_REQUEST, {
user_id: user.id || user,
});
}
/**
* Reject a join request from a user
* @param {User} user The user whose request you wish to reject
* @returns {Promise}
*/
closeJoinRequest(user) {
return this.request(RPCCommands.CLOSE_ACTIVITY_JOIN_REQUEST, {
user_id: user.id || user,
});
}
createLobby(type, capacity, metadata) {
return this.request(RPCCommands.CREATE_LOBBY, {
type,
capacity,
metadata,
});
}
updateLobby(lobby, { type, owner, capacity, metadata } = {}) {
return this.request(RPCCommands.UPDATE_LOBBY, {
id: lobby.id || lobby,
type,
owner_id: (owner && owner.id) || owner,
capacity,
metadata,
});
}
deleteLobby(lobby) {
return this.request(RPCCommands.DELETE_LOBBY, {
id: lobby.id || lobby,
});
}
connectToLobby(id, secret) {
return this.request(RPCCommands.CONNECT_TO_LOBBY, {
id,
secret,
});
}
sendToLobby(lobby, data) {
return this.request(RPCCommands.SEND_TO_LOBBY, {
id: lobby.id || lobby,
data,
});
}
disconnectFromLobby(lobby) {
return this.request(RPCCommands.DISCONNECT_FROM_LOBBY, {
id: lobby.id || lobby,
});
}
updateLobbyMember(lobby, user, metadata) {
return this.request(RPCCommands.UPDATE_LOBBY_MEMBER, {
lobby_id: lobby.id || lobby,
user_id: user.id || user,
metadata,
});
}
getRelationships() {
const types = Object.keys(RelationshipTypes);
return this.request(RPCCommands.GET_RELATIONSHIPS)
.then((o) => o.relationships.map((r) => ({
...r,
type: types[r.type],
})));
}
/**
* Subscribe to an event
* @param {string} event Name of event e.g. `MESSAGE_CREATE`
* @param {Object} [args] Args for event e.g. `{ channel_id: '1234' }`
* @returns {Promise<Object>}
*/
async subscribe(event, args) {
await this.request(RPCCommands.SUBSCRIBE, args, event);
return {
unsubscribe: () => this.request(RPCCommands.UNSUBSCRIBE, args, event),
};
}
/**
* Destroy the client
*/
async destroy() {
await this.transport.close();
}
}
module.exports = RPCClient;

View File

@@ -0,0 +1,178 @@
'use strict';
function keyMirror(arr) {
const tmp = {};
for (const value of arr) {
tmp[value] = value;
}
return tmp;
}
exports.browser = typeof window !== 'undefined';
exports.RPCCommands = keyMirror([
'DISPATCH',
'AUTHORIZE',
'AUTHENTICATE',
'GET_GUILD',
'GET_GUILDS',
'GET_CHANNEL',
'GET_CHANNELS',
'CREATE_CHANNEL_INVITE',
'GET_RELATIONSHIPS',
'GET_USER',
'SUBSCRIBE',
'UNSUBSCRIBE',
'SET_USER_VOICE_SETTINGS',
'SET_USER_VOICE_SETTINGS_2',
'SELECT_VOICE_CHANNEL',
'GET_SELECTED_VOICE_CHANNEL',
'SELECT_TEXT_CHANNEL',
'GET_VOICE_SETTINGS',
'SET_VOICE_SETTINGS_2',
'SET_VOICE_SETTINGS',
'CAPTURE_SHORTCUT',
'SET_ACTIVITY',
'SEND_ACTIVITY_JOIN_INVITE',
'CLOSE_ACTIVITY_JOIN_REQUEST',
'ACTIVITY_INVITE_USER',
'ACCEPT_ACTIVITY_INVITE',
'INVITE_BROWSER',
'DEEP_LINK',
'CONNECTIONS_CALLBACK',
'BRAINTREE_POPUP_BRIDGE_CALLBACK',
'GIFT_CODE_BROWSER',
'GUILD_TEMPLATE_BROWSER',
'OVERLAY',
'BROWSER_HANDOFF',
'SET_CERTIFIED_DEVICES',
'GET_IMAGE',
'CREATE_LOBBY',
'UPDATE_LOBBY',
'DELETE_LOBBY',
'UPDATE_LOBBY_MEMBER',
'CONNECT_TO_LOBBY',
'DISCONNECT_FROM_LOBBY',
'SEND_TO_LOBBY',
'SEARCH_LOBBIES',
'CONNECT_TO_LOBBY_VOICE',
'DISCONNECT_FROM_LOBBY_VOICE',
'SET_OVERLAY_LOCKED',
'OPEN_OVERLAY_ACTIVITY_INVITE',
'OPEN_OVERLAY_GUILD_INVITE',
'OPEN_OVERLAY_VOICE_SETTINGS',
'VALIDATE_APPLICATION',
'GET_ENTITLEMENT_TICKET',
'GET_APPLICATION_TICKET',
'START_PURCHASE',
'GET_SKUS',
'GET_ENTITLEMENTS',
'GET_NETWORKING_CONFIG',
'NETWORKING_SYSTEM_METRICS',
'NETWORKING_PEER_METRICS',
'NETWORKING_CREATE_TOKEN',
'SET_USER_ACHIEVEMENT',
'GET_USER_ACHIEVEMENTS',
]);
exports.RPCEvents = keyMirror([
'CURRENT_USER_UPDATE',
'GUILD_STATUS',
'GUILD_CREATE',
'CHANNEL_CREATE',
'RELATIONSHIP_UPDATE',
'VOICE_CHANNEL_SELECT',
'VOICE_STATE_CREATE',
'VOICE_STATE_DELETE',
'VOICE_STATE_UPDATE',
'VOICE_SETTINGS_UPDATE',
'VOICE_SETTINGS_UPDATE_2',
'VOICE_CONNECTION_STATUS',
'SPEAKING_START',
'SPEAKING_STOP',
'GAME_JOIN',
'GAME_SPECTATE',
'ACTIVITY_JOIN',
'ACTIVITY_JOIN_REQUEST',
'ACTIVITY_SPECTATE',
'ACTIVITY_INVITE',
'NOTIFICATION_CREATE',
'MESSAGE_CREATE',
'MESSAGE_UPDATE',
'MESSAGE_DELETE',
'LOBBY_DELETE',
'LOBBY_UPDATE',
'LOBBY_MEMBER_CONNECT',
'LOBBY_MEMBER_DISCONNECT',
'LOBBY_MEMBER_UPDATE',
'LOBBY_MESSAGE',
'CAPTURE_SHORTCUT_CHANGE',
'OVERLAY',
'OVERLAY_UPDATE',
'ENTITLEMENT_CREATE',
'ENTITLEMENT_DELETE',
'USER_ACHIEVEMENT_UPDATE',
'READY',
'ERROR',
]);
exports.RPCErrors = {
CAPTURE_SHORTCUT_ALREADY_LISTENING: 5004,
GET_GUILD_TIMED_OUT: 5002,
INVALID_ACTIVITY_JOIN_REQUEST: 4012,
INVALID_ACTIVITY_SECRET: 5005,
INVALID_CHANNEL: 4005,
INVALID_CLIENTID: 4007,
INVALID_COMMAND: 4002,
INVALID_ENTITLEMENT: 4015,
INVALID_EVENT: 4004,
INVALID_GIFT_CODE: 4016,
INVALID_GUILD: 4003,
INVALID_INVITE: 4011,
INVALID_LOBBY: 4013,
INVALID_LOBBY_SECRET: 4014,
INVALID_ORIGIN: 4008,
INVALID_PAYLOAD: 4000,
INVALID_PERMISSIONS: 4006,
INVALID_TOKEN: 4009,
INVALID_USER: 4010,
LOBBY_FULL: 5007,
NO_ELIGIBLE_ACTIVITY: 5006,
OAUTH2_ERROR: 5000,
PURCHASE_CANCELED: 5008,
PURCHASE_ERROR: 5009,
RATE_LIMITED: 5011,
SELECT_CHANNEL_TIMED_OUT: 5001,
SELECT_VOICE_FORCE_REQUIRED: 5003,
SERVICE_UNAVAILABLE: 1001,
TRANSACTION_ABORTED: 1002,
UNAUTHORIZED_FOR_ACHIEVEMENT: 5010,
UNKNOWN_ERROR: 1000,
};
exports.RPCCloseCodes = {
CLOSE_NORMAL: 1000,
CLOSE_UNSUPPORTED: 1003,
CLOSE_ABNORMAL: 1006,
INVALID_CLIENTID: 4000,
INVALID_ORIGIN: 4001,
RATELIMITED: 4002,
TOKEN_REVOKED: 4003,
INVALID_VERSION: 4004,
INVALID_ENCODING: 4005,
};
exports.LobbyTypes = {
PRIVATE: 1,
PUBLIC: 2,
};
exports.RelationshipTypes = {
NONE: 0,
FRIEND: 1,
BLOCKED: 2,
PENDING_INCOMING: 3,
PENDING_OUTGOING: 4,
IMPLICIT: 5,
};

View File

@@ -0,0 +1,10 @@
'use strict';
const util = require('./util');
module.exports = {
Client: require('./client'),
register(id) {
return util.register(`discord-${id}`);
},
};

View File

@@ -0,0 +1,6 @@
'use strict';
module.exports = {
ipc: require('./ipc'),
websocket: require('./websocket'),
};

View File

@@ -0,0 +1,173 @@
'use strict';
const net = require('net');
const EventEmitter = require('events');
const fetch = require('node-fetch');
const { uuid } = require('../util');
const OPCodes = {
HANDSHAKE: 0,
FRAME: 1,
CLOSE: 2,
PING: 3,
PONG: 4,
};
function getIPCPath(id) {
if (process.platform === 'win32') {
return `\\\\?\\pipe\\discord-ipc-${id}`;
}
const { env: { XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP } } = process;
const prefix = XDG_RUNTIME_DIR || TMPDIR || TMP || TEMP || '/tmp';
return `${prefix.replace(/\/$/, '')}/discord-ipc-${id}`;
}
function getIPC(id = 0) {
return new Promise((resolve, reject) => {
const path = getIPCPath(id);
const onerror = () => {
if (id < 10) {
resolve(getIPC(id + 1));
} else {
reject(new Error('Could not connect'));
}
};
const sock = net.createConnection(path, () => {
sock.removeListener('error', onerror);
resolve(sock);
});
sock.once('error', onerror);
});
}
async function findEndpoint(tries = 0) {
if (tries > 30) {
throw new Error('Could not find endpoint');
}
const endpoint = `http://127.0.0.1:${6463 + (tries % 10)}`;
try {
const r = await fetch(endpoint);
if (r.status === 404) {
return endpoint;
}
return findEndpoint(tries + 1);
} catch (e) {
return findEndpoint(tries + 1);
}
}
function encode(op, data) {
data = JSON.stringify(data);
const len = Buffer.byteLength(data);
const packet = Buffer.alloc(8 + len);
packet.writeInt32LE(op, 0);
packet.writeInt32LE(len, 4);
packet.write(data, 8, len);
return packet;
}
const working = {
full: '',
op: undefined,
};
function decode(socket, callback) {
const packet = socket.read();
if (!packet) {
return;
}
let { op } = working;
let raw;
if (working.full === '') {
op = working.op = packet.readInt32LE(0);
const len = packet.readInt32LE(4);
raw = packet.slice(8, len + 8);
} else {
raw = packet.toString();
}
try {
const data = JSON.parse(working.full + raw);
callback({ op, data }); // eslint-disable-line callback-return
working.full = '';
working.op = undefined;
} catch (err) {
working.full += raw;
}
decode(socket, callback);
}
class IPCTransport extends EventEmitter {
constructor(client) {
super();
this.client = client;
this.socket = null;
}
async connect() {
const socket = this.socket = await getIPC();
socket.on('close', this.onClose.bind(this));
socket.on('error', this.onClose.bind(this));
this.emit('open');
socket.write(encode(OPCodes.HANDSHAKE, {
v: 1,
client_id: this.client.clientId,
}));
socket.pause();
socket.on('readable', () => {
decode(socket, ({ op, data }) => {
switch (op) {
case OPCodes.PING:
this.send(data, OPCodes.PONG);
break;
case OPCodes.FRAME:
if (!data) {
return;
}
if (data.cmd === 'AUTHORIZE' && data.evt !== 'ERROR') {
findEndpoint()
.then((endpoint) => {
this.client.request.endpoint = endpoint;
})
.catch((e) => {
this.client.emit('error', e);
});
}
this.emit('message', data);
break;
case OPCodes.CLOSE:
this.emit('close', data);
break;
default:
break;
}
});
});
}
onClose(e) {
this.emit('close', e);
}
send(data, op = OPCodes.FRAME) {
this.socket.write(encode(op, data));
}
async close() {
return new Promise((r) => {
this.once('close', r);
this.send({}, OPCodes.CLOSE);
this.socket.end();
});
}
ping() {
this.send(uuid(), OPCodes.PING);
}
}
module.exports = IPCTransport;
module.exports.encode = encode;
module.exports.decode = decode;

View File

@@ -0,0 +1,77 @@
'use strict';
const EventEmitter = require('events');
const { browser } = require('../constants');
// eslint-disable-next-line
const WebSocket = browser ? window.WebSocket : require('ws');
const pack = (d) => JSON.stringify(d);
const unpack = (s) => JSON.parse(s);
class WebSocketTransport extends EventEmitter {
constructor(client) {
super();
this.client = client;
this.ws = null;
this.tries = 0;
}
async connect() {
const port = 6463 + (this.tries % 10);
this.tries += 1;
this.ws = new WebSocket(
`ws://127.0.0.1:${port}/?v=1&client_id=${this.client.clientId}`,
browser ? undefined : { origin: this.client.options.origin },
);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onclose = this.onClose.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
}
onOpen() {
this.emit('open');
}
onClose(event) {
if (!event.wasClean) {
return;
}
this.emit('close', event);
}
onError(event) {
try {
this.ws.close();
} catch {} // eslint-disable-line no-empty
if (this.tries > 20) {
this.emit('error', event.error);
} else {
setTimeout(() => {
this.connect();
}, 250);
}
}
onMessage(event) {
this.emit('message', unpack(event.data));
}
send(data) {
this.ws.send(pack(data));
}
ping() {} // eslint-disable-line no-empty-function
close() {
return new Promise((r) => {
this.once('close', r);
this.ws.close();
});
}
}
module.exports = WebSocketTransport;

View File

@@ -0,0 +1,50 @@
'use strict';
let register;
try {
const { app } = require('electron');
register = app.setAsDefaultProtocolClient.bind(app);
} catch (err) {
try {
register = require('register-scheme');
} catch (e) {} // eslint-disable-line no-empty
}
if (typeof register !== 'function') {
register = () => false;
}
function pid() {
if (typeof process !== 'undefined') {
return process.pid;
}
return null;
}
const uuid4122 = () => {
let uuid = '';
for (let i = 0; i < 32; i += 1) {
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
let n;
if (i === 12) {
n = 4;
} else {
const random = Math.random() * 16 | 0;
if (i === 16) {
n = (random & 3) | 0;
} else {
n = random;
}
}
uuid += n.toString(16);
}
return uuid;
};
module.exports = {
pid,
register,
uuid: uuid4122,
};

View File

@@ -1,21 +1,25 @@
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session } = require("electron"); const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session, dialog } = require("electron");
const electronremote = require("@electron/remote/main"); const electronremote = require("@electron/remote/main");
//const asar = require('@electron/asar'); //const asar = require('@electron/asar');
const windowStateKeeper = require("electron-window-state"); const windowStateKeeper = require("./window-state/index");
const { setupTitlebar, attachTitlebarToWindow } = require("./titlebar/main"); const { setupTitlebar, attachTitlebarToWindow } = require("./titlebar/main");
const openAboutWindow = require("./about-window/src/index").default; const openAboutWindow = require("./about-window/src/index").default;
const badge = require('./badge'); const badge = require('./badge');
const nodeNotifier = require('node-notifier');
const contextMenu = require('./context-menu'); const contextMenu = require('./context-menu');
const autoUpdater = require('./utils/auto-update'); const autoUpdater = require('./utils/auto-update');
const loadCRX = require('./utils/loadCRX'); const loadCRX = require('./utils/loadCRX');
const userStyles = require('./utils/userStyles'); const userStyles = require('./utils/userStyles');
const log4js = require("log4js"); const log4js = require("log4js");
//const axios = require("axios");
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
const os = require("os"); const os = require("os");
require('v8-compile-cache'); require('v8-compile-cache');
// Load package.json and contributors.json
const packageJson = require(path.join(__dirname, '..', '..', 'package.json')); const packageJson = require(path.join(__dirname, '..', '..', 'package.json'));
const contributors = require(path.join(__dirname, 'contributors.json'));
// isUpdaing: // isUpdaing:
global.isUpdating = false; global.isUpdating = false;
@@ -36,9 +40,10 @@ global.paths = {
home: os.homedir(), home: os.homedir(),
temp: path.join(os.tmpdir(), global.appInfo.name), temp: path.join(os.tmpdir(), global.appInfo.name),
}; };
global.paths.updateDir = path.join(global.paths.data, 'update'); global.paths.user = path.join(global.paths.data, 'user');
global.paths.extensions = path.join(global.paths.data, 'extensions'); global.paths.updateDir = path.join(global.paths.user, 'update');
global.paths.userstyles = path.join(global.paths.data, 'userstyles'); global.paths.extensions = path.join(global.paths.user, 'extensions');
global.paths.userstyles = path.join(global.paths.user, 'userstyles');
// URLs: // URLs:
global.urls = { global.urls = {
@@ -53,6 +58,36 @@ global.settings.account = `${global.settings.general}/account`;
global.settings.appearance = `${global.settings.general}/appearance`; global.settings.appearance = `${global.settings.general}/appearance`;
global.settings.privacy = `${global.settings.general}/privacy-and-security`; 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: // Badge options:
const badgeOptions = { const badgeOptions = {
fontColor: '#FFFFFF', // The font color fontColor: '#FFFFFF', // The font color
@@ -102,7 +137,7 @@ if (fs.existsSync(logFile) && !fs.existsSync(path.join(global.paths.data, 'lockf
fs.renameSync(logFile, path.join(global.paths.data, `${logFileName}.${mtime.toISOString().split('T')[0]}.log`)); fs.renameSync(logFile, path.join(global.paths.data, `${logFileName}.${mtime.toISOString().split('T')[0]}.log`));
}; };
}; };
logger.log("Starting Bsky Desktop"); logger.log(`Starting Bsky Desktop v${packageJson.version} on ${os.platform()} ${os.arch()}`);
// Create data directory if it does not exist: // Create data directory if it does not exist:
if (!fs.existsSync(global.paths.data)) { if (!fs.existsSync(global.paths.data)) {
@@ -116,10 +151,10 @@ if (!fs.existsSync(global.paths.temp)) {
fs.mkdirSync(global.paths.temp, { recursive: true }); fs.mkdirSync(global.paths.temp, { recursive: true });
}; };
// Create update directory if it does not exist: // Create user directory if it does not exist:
if (!fs.existsSync(global.paths.updateDir)) { if (!fs.existsSync(global.paths.data, 'user')) {
logger.info("Creating Update Directory"); logger.info("Creating User Directory");
fs.mkdirSync(global.paths.updateDir, { recursive: true }); fs.mkdirSync(global.paths.user, { recursive: true });
}; };
// Create extensions directory if it does not exist: // Create extensions directory if it does not exist:
@@ -134,6 +169,12 @@ if (!fs.existsSync(global.paths.userstyles)) {
fs.mkdirSync(global.paths.userstyles, { recursive: true }); 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? // improve performance on linux?
if (process.platform !== "win32" && process.platform !== "darwin") { if (process.platform !== "win32" && process.platform !== "darwin") {
logger.log("Disabling Hardware Acceleration and Transparent Visuals"); logger.log("Disabling Hardware Acceleration and Transparent Visuals");
@@ -148,16 +189,16 @@ setupTitlebar();
// Disable reload and F5 if not in dev mode: // Disable reload and F5 if not in dev mode:
if (process.env.NODE_ENV !== 'development') { if (process.env.NODE_ENV !== 'development') {
app.on('browser-window-focus', function () { app.on('browser-window-focus', function () {
globalShortcut.register("CommandOrControl+R", () => { /*globalShortcut.register("CommandOrControl+R", () => {
//console.log("CommandOrControl+R is pressed: Shortcut Disabled"); //console.log("CommandOrControl+R is pressed: Shortcut Disabled");
}); });
globalShortcut.register("F5", () => { globalShortcut.register("F5", () => {
//console.log("F5 is pressed: Shortcut Disabled"); //console.log("F5 is pressed: Shortcut Disabled");
}); });*/
}); });
app.on('browser-window-blur', function () { app.on('browser-window-blur', function () {
globalShortcut.unregister('CommandOrControl+R'); /*globalShortcut.unregister('CommandOrControl+R');
globalShortcut.unregister('F5'); globalShortcut.unregister('F5');*/
}); });
}; };
@@ -220,14 +261,14 @@ function createWindow() {
PageView.setBounds({ PageView.setBounds({
x: 0, x: 0,
y: 30, y: 30,
width: mainWindow.getBounds().width, width: mainWindow.isMaximized() ? mainWindow.getBounds().width - 16 : mainWindow.getBounds().width,
height: mainWindow.getBounds().height - 30, height: mainWindow.getBounds().height - 30,
}); });
mainWindow.on("resize", () => { mainWindow.on("resize", () => {
PageView.setBounds({ PageView.setBounds({
x: 0, x: 0,
y: 30, y: 30,
width: mainWindow.getBounds().width, width: mainWindow.isMaximized() ? mainWindow.getBounds().width - 16 : mainWindow.getBounds().width,
height: mainWindow.getBounds().height - 30, height: mainWindow.getBounds().height - 30,
}); });
}); });
@@ -302,7 +343,7 @@ function showAboutWindow() {
//open_devtools: process.env.NODE_ENV !== 'production', //open_devtools: process.env.NODE_ENV !== 'production',
use_version_info: [ use_version_info: [
['Application Version', `${global.appInfo.version}`], ['Application Version', `${global.appInfo.version}`],
['Contributors', packageJson.contributors.map((contributor) => contributor.name).join(', ')], ['Contributors', contributors.map((contributor) => contributor.name).join(', ')],
], ],
license: `MIT, GPL-2.0, GPL-3.0, ${global.appInfo.license}`, license: `MIT, GPL-2.0, GPL-3.0, ${global.appInfo.license}`,
}); });
@@ -310,7 +351,7 @@ function showAboutWindow() {
function createTray() { function createTray() {
logger.log("Creating Tray"); logger.log("Creating Tray");
const tray = new Tray(path.join(global.paths.app, 'ui', 'images', 'logo.png')); const tray = new Tray(path.join(global.paths.app, 'ui', 'images', 'icons', '32x32.png'));
tray.setToolTip('Bsky Desktop'); tray.setToolTip('Bsky Desktop');
tray.setContextMenu(Menu.buildFromTemplate([ tray.setContextMenu(Menu.buildFromTemplate([
{ label: global.appInfo.name, enabled: false }, { label: global.appInfo.name, enabled: false },
@@ -497,81 +538,198 @@ app.whenReady().then(() => {
// Set session: // Set session:
global.session = ses; global.session = ses;
// Initialize the updater:
logger.log("Initializing Updater");
autoUpdater();
// Handle ipc for render: // Handle ipc for render:
ipcMain.on('close-app', (event, arg) => { ipcMain.on('close-app', (event, arg) => {
app.quit(); app.quit();
}); });
ipcMain.on('app:restart', (event, arg) => {
app.relaunch();
app.quit();
});
// Create windows and tray:
createWindow(); createWindow();
createTray(); createTray();
// Load extensions (.crx files): // Wait for splash screen to load before checking for updates, loading extensions and userstyles
logger.log("Checking for extensions"); global.splash.webContents.on('did-finish-load', async () => {
const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx')); // Check for internet connection:
if (extensions.length > 0) { logger.log("Checking for internet connection");
logger.log(`Unpacking ${extensions.length} extensions and loading them`); require('dns').lookup('google.com', err => {
extensions.forEach((extension) => { if (err) {
loadCRX(path.join(global.paths.extensions, extension)); logger.log('No internet connection, showing not connected message');
}); global.PageView.webContents.loadFile(path.join(global.paths.app, 'ui', 'offline.html'));
} else { return;
// Check for unpacked extensions: }
const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory()); 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 // Check if the directory contains a manifest.json file
unpackedExtensions.forEach((extension) => { unpackedExtensions.forEach((extension) => {
const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json'); const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json');
if (fs.existsSync(manifestPath)) { if (fs.existsSync(manifestPath)) {
logger.log(`Loading unpacked extension: ${extension}`); logger.log(`Loading unpacked extension: ${extension}`);
session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => { global.splash.webContents.send('ui:progtext', { title: `Loading unpacked extension: ${extension}` });
logger.log(`Extension loaded with ID: ${id}`); session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => {
}).catch((error) => { logger.log(`Extension loaded with ID: ${id}`);
logger.error(`Failed to load extension: ${error}`); }).catch((error) => {
}); logger.error(`Failed to load extension: ${error}`);
} else { });
logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`); } else {
}; logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`);
}); };
}; });
};
// Load userstyles // Load userstyles
logger.log("Checking for userstyles"); logger.log("Checking for userstyles");
const userStylesDir = path.join(global.paths.userstyles); global.splash.webContents.send('ui:progtext', { title: 'Checking for userstyles' });
if (fs.existsSync(userStylesDir)) { const userStylesDir = path.join(global.paths.userstyles);
const files = fs.readdirSync(userStylesDir); if (fs.existsSync(userStylesDir)) {
if (files.length > 0) { const files = fs.readdirSync(userStylesDir).filter((file) => file.endsWith('.css'));
const userStylePromises = files.map(async file => { if (files.length > 0) {
const cssFile = path.join(userStylesDir, file); logger.log(`Loading ${files.length} userstyles`);
// Parse the CSS file const userStylePromises = files.map(async file => {
try { const cssFile = path.join(userStylesDir, file);
const cssContent = fs.readFileSync(cssFile, 'utf-8'); // Parse the CSS file
const result = await userStyles.parseCSS(cssContent); try {
const cssContent = fs.readFileSync(cssFile, 'utf-8');
const result = await userStyles.parseCSS(cssContent);
logger.info(`Loaded userstyle: ${result.metadata.name}`); logger.info(`Loading userstyle: ${result.metadata.name}`);
// Compile the userstyle // Compile the userstyle
const compiled = await userStyles.compileStyle(result.css, result.metadata); const compiled = await userStyles.compileStyle(result.css, result.metadata);
// Check if the site 'bsky.app' is defined // Check if the site 'bsky.app' is defined
if (compiled.sites && compiled.sites['bsky.app']) { if (compiled.sites?.['bsky.app']) {
// Apply the userstyle to the PageView // Apply the userstyle to the PageView
await PageView.webContents.insertCSS(compiled.sites['bsky.app']); await PageView.webContents.insertCSS(compiled.sites['bsky.app']);
logger.info(`Applied userstyle: ${result.metadata.name}`); logger.info(`Applied userstyle: ${result.metadata.name}`);
} else { global.splash.webContents.send('ui:progtext', { title: `Applied userstyle: ${result.metadata.name}` });
logger.warn(`Userstyle ${result.metadata.name} does not target 'bsky.app'`); } 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 { } else {
logger.log("Failed to get singleInstanceLock, Quitting"); logger.log("Failed to get singleInstanceLock, Quitting");
app.quit(); app.quit();

View File

@@ -90,7 +90,7 @@ class Updater extends EventEmitter {
}; };
this.options = Object.assign({}, def, options); this.options = Object.assign({}, def, options);
this.options.cacheDirectory = path.resolve(this.options.tmpdir, this.options.name); 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); fs.existsSync(this.options.cacheDirectory) || fs.mkdirSync(this.options.cacheDirectory);
} }
@@ -207,7 +207,7 @@ class Updater extends EventEmitter {
quitAndInstall(timeout) { quitAndInstall(timeout) {
setTimeout(() => { setTimeout(() => {
app.relaunch({ args: process.argv.slice(1) + ['--relaunch'] }); app.relaunch({ args: process.argv.slice(1) + ['--relaunch', '--update-installed'] });
app.exit(0); app.exit(0);
}, timeout || 100); }, timeout || 100);
} }

View File

@@ -1,9 +1,11 @@
const path = require('path');
const os = require('os');
const fs = require('fs');
const childProcess = require('child_process'); const childProcess = require('child_process');
const { app } = require('electron'); const { app } = require('electron');
const log4js = require('log4js'); 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 // import the asarUpdater module
const asarUpdater = require('./asarUpdater'); const asarUpdater = require('./asarUpdater');
@@ -14,9 +16,73 @@ const SystemInfo = require('./sysInfo');
// Get the current system platform // Get the current system platform
const sys = new SystemInfo(); const sys = new SystemInfo();
// Detect the app install type
const installType = require('./installType');
// Setup the logger // Setup the logger
const logger = log4js.getLogger("bskydesktop"); 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() { function asarUpdate() {
asarUpdater.init(); asarUpdater.init();
@@ -62,7 +128,7 @@ function asarUpdate() {
// Set the feed URL (only works in packaged app): // Set the feed URL (only works in packaged app):
if (app.isPackaged) { if (app.isPackaged) {
logger.log("Setting Feed URL for app.asar"); 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: //Check for updates:
@@ -70,88 +136,379 @@ function asarUpdate() {
if (app.isPackaged) { if (app.isPackaged) {
const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours
setInterval(() => { setInterval(() => {
//asarUpdater.checkForUpdates(); asarUpdater.checkForUpdates();
}, UPDATE_CHECK); }, UPDATE_CHECK);
//asarUpdater.checkForUpdates(); asarUpdater.checkForUpdates();
} else { } else {
logger.warn("Not checking for updates as app is not packaged"); logger.warn("Not checking for updates as app is not packaged");
}; };
} }
function checkForUpdates() { // Check for updates
// Current system information async function checkForUpdates() {
logger.log('Current system information:', sys.platform, sys.getVersion()); 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 // Check if the current system is Windows
if (sys.isWin()) { if (sys.isWin()) {
// Check if the system is before Windows 10 // Check if the system is before Windows 10
if (sys.earlierThan('10.0.0')) { if (sys.earlierThan('10.0.0')) {
// Windows 10 and above are supported, but windows 7 and 8 are not supported // 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...'); logger.error('Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...');
} else { resolve({ err: 'old-os', msg: 'Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...' });
// 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';
}
} else { } else {
// macOS 10 is mostly x64, but some versions are ARM64 // Check for updates, and if there are updates, download and install them
macArch = 'x64'; 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 // Check if the current system is macOS
if (sys.isLinux()) { if (sys.isMac()) {
// Check for updates, and if there are updates, download and install them (no system version check) // Check the current version of macOS, and whether we can use the pkg installer
// Linux versions use AppImage, so we instead need to check for a new asar file so that the app can if (sys.laterThan('10.0.0')) {
// load the new asar instead of the packaged one (or even just delete the old appimage and download a new one) // Check for updates, and if there are updates, download and install them
logger.log('Checking for updates (linux)...'); logger.log('Checking for updates (mac)...');
asarUpdate();
} // 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; // 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
};

View File

@@ -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
};

View File

@@ -61,6 +61,16 @@ class SystemInfo {
return !this.laterThan(compareVersion); 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]) // Private: Parse version strings (e.g., "10.0.19045" -> [10, 0, 19045])
_parseVersion(version) { _parseVersion(version) {
return version.split('.').map((num) => parseInt(num, 10) || 0); return version.split('.').map((num) => parseInt(num, 10) || 0);

View File

@@ -1,5 +1,6 @@
const usercssMeta = require('usercss-meta'); const usercssMeta = require('usercss-meta');
const less = require('less'); const less = require('less');
const stylus = require('stylus');
/** /**
* Extracts all global variable and mixin definitions from CSS. * Extracts all global variable and mixin definitions from CSS.
@@ -72,8 +73,8 @@ function parseDomainRule(css, startPos) {
if (!match) return null; if (!match) return null;
const domains = match[0] const domains = match[0]
.match(/'[^']+'/g) // Extract all single-quoted domain values .match(/['"][^'"]+['"]/g) // Extract all single- or double-quoted domain values
.map(domain => domain.replace(/'/g, '').trim()); .map(domain => domain.replace(/['"]/g, '').trim()); // Remove quotes and trim whitespace
return { return {
domains, domains,
@@ -90,17 +91,26 @@ function parseMozRules(css) {
const rules = {}; const rules = {};
let currentPos = 0; let currentPos = 0;
// Helper to extract global definitions (CSS outside @-moz-document)
const globalCode = extractGlobalDefinitions(css); const globalCode = extractGlobalDefinitions(css);
while (true) { while (currentPos < css.length) {
const domainRule = parseDomainRule(css, currentPos); // Match @-moz-document syntax and extract domains
if (!domainRule) break; 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; if (!bracedContent) break;
// Add the CSS to all matched domains // Parse the domains and add the CSS to each
for (const domain of domainRule.domains) { 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}`; rules[domain] = `${globalCode}\n${bracedContent.content}`;
} }
@@ -155,10 +165,17 @@ async function compileStyle(code, metadata, userVars = {}) {
switch (metadata?.preprocessor?.toLowerCase()) { switch (metadata?.preprocessor?.toLowerCase()) {
case 'less': case 'less':
compiledCode = await compileLess(fullCode); compiledCode = await compileLess(fullCode, vars);
break; 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: default:
compiledCode = code; // Return unmodified for unknown preprocessors compiledCode = code; // Return unmodified for plain CSS/unknown preprocessor
} }
// Parse domain rules // Parse domain rules
@@ -167,10 +184,15 @@ async function compileStyle(code, metadata, userVars = {}) {
// Compile each domain's CSS if needed // Compile each domain's CSS if needed
const compiledRules = {}; const compiledRules = {};
for (const [domain, css] of Object.entries(domainRules)) { for (const [domain, css] of Object.entries(domainRules)) {
if (metadata.preprocessor === 'less') { switch (metadata.preprocessor?.toLowerCase()) {
compiledRules[domain] = await compileLess(css, vars); case 'less':
} else { compiledRules[domain] = await compileLess(css, vars);
compiledRules[domain] = css; 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 { return {
compiledCss: combinedCss, compiledCss: combinedCss,
sites: compiledRules // Map of domains to their CSS sites: compiledRules, // Map of domains to their CSS
}; };
} catch (error) { } catch (error) {
console.error('Style compilation error:', error); //console.error('Style compilation error:', error);
throw error; return {
error
};
} }
} }
/** /**
* Compiles LESS code to CSS. * Compiles LESS code to CSS.
* @param {string} code - The LESS code. * @param {string} code - The LESS code.
* @param {object} vars - User-defined variables.
* @returns {Promise<string>} The compiled CSS. * @returns {Promise<string>} The compiled CSS.
*/ */
async function compileLess(code) { async function compileLess(code, vars = {}) {
const { css } = await less.render(code, { return new Promise((resolve, reject) => {
math: 'parens-division', less.render(code, {
javascriptEnabled: true, math: 'parens-division',
compress: false 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<string>} 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 = { module.exports = {

View File

@@ -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
};
};

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Jakub Szwacz
Copyright (c) Marcel Wiehle <marcel@wiehle.me> (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.

105
src/ui/css/offline.css Normal file
View File

@@ -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);
}

View File

@@ -6,6 +6,7 @@
-webkit-user-drag: none; -webkit-user-drag: none;
/* Disable selection */ /* Disable selection */
-webkit-user-select: none; -webkit-user-select: none;
user-select: none;
} }
html, html,

View File

@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;display:block;" width="150px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;display:block;" width="150px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="30" stroke="#2B87D3" stroke-width="10" fill="none"></circle> <circle cx="50" cy="50" r="30" stroke="#2B87D3" stroke-width="10" fill="none"></circle>
<circle cx="50" cy="50" r="30" stroke="#0C396A" stroke-width="8" stroke-linecap="round" fill="none"> <circle cx="50" cy="50" r="30" stroke="#0C396A" stroke-width="8" stroke-linecap="round" fill="none">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform> <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1"></animate> <animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1"></animate>
</circle> </circle>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 812 B

BIN
src/ui/images/mac.icns Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
<svg class="spinner" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="60" height="60" style="box-sizing: border-box; padding: 3px; overflow: visible; color: #0071c2;">
<circle cx="60" cy="60" r="50" fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" style="transform-origin: center;">
<animateTransform attributeName="transform" type="rotate" values="-90;810" keyTimes="0;1" dur="2s" repeatCount="indefinite"></animateTransform>
<animate attributeName="stroke-dashoffset" values="0%;0%;-157.080%" calcMode="spline" keySplines="0.61, 1, 0.88, 1; 0.12, 0, 0.39, 0" keyTimes="0;0.5;1" dur="2s" repeatCount="indefinite"></animate>
<animate attributeName="stroke-dasharray" values="0% 314.159%;157.080% 157.080%;0% 314.159%" calcMode="spline" keySplines="0.61, 1, 0.88, 1; 0.12, 0, 0.39, 0" keyTimes="0;0.5;1" dur="2s" repeatCount="indefinite"></animate>
</circle>
</svg>

After

Width:  |  Height:  |  Size: 920 B

31
src/ui/offline.html Normal file
View File

@@ -0,0 +1,31 @@
<html>
<head>
<title>No internet connection</title>
<link rel="stylesheet" href="./css/offline.css">
<link rel="stylesheet" href="./css/fa/6.7.1/css/all.min.css">
</head>
<body>
<div class="container">
<div class="divround" style="width: 510px;">
<h1>No internet connection</h1>
<p>We couldn't detect an internet connection. Please check your network and try again.</p>
<button class="btn" id="refresh-app"><i class="fa fa-refresh"></i> Reload</button>
</div>
</div>
<script>
// When the reload button is clicked, send a message to the main process to reload the app
document.getElementById('refresh-app').addEventListener('click', () => {
// Check for internet connection before reloading
if (navigator.onLine) {
window.ipc.send('app:restart');
return;
} else {
alert('Please check your network and try again.');
};
});
</script>
</body>
</html>

View File

@@ -1,33 +1,37 @@
const { contextBridge, ipcRenderer } = require('electron'); 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", { contextBridge.exposeInMainWorld("ipc", {
send: (channel, data) => { send: (channel, data) => {
ipcRenderer.send(channel, data); if (allowedChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
}, },
on: (channel, callback) => { on: (channel, callback) => {
ipcRenderer.on(channel, (event, ...args) => callback(...args)); if (allowedChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => callback(...args));
}
}, },
}); });
contextBridge.exposeInMainWorld("badge", { // Load a script asynchronously
update: (badgeNumber) => { function loadScriptAsync(src) {
ipcRenderer.send('ui:badgeCount', badgeNumber); return new Promise((resolve, reject) => {
} const script = document.createElement("script");
}); script.src = src;
script.onload = () => {
// Helper to dynamically load a script and append it to the DOM console.log(`Loaded script: ${src}`);
function loadScript(src, callback) { resolve();
const script = document.createElement('script'); };
script.src = src; script.onerror = (error) => {
script.onload = () => { console.error(`Failed to load script: ${src}`, error);
console.log(`Loaded script: ${src}`); reject(error);
if (callback) callback(); };
}; document.body.appendChild(script);
script.onerror = (error) => { });
console.error(`Failed to load script: ${src}`, error);
};
document.body.appendChild(script);
} }
// Inject CSS into the DOM // Inject CSS into the DOM
@@ -39,22 +43,19 @@ function injectCSS(href) {
console.log(`Injected CSS: ${href}`); console.log(`Injected CSS: ${href}`);
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener("DOMContentLoaded", async () => {
// Inject CSS injectCSS("ui:///lib/izitoast.min.css");
injectCSS('ui:///lib/izitoast.min.css'); injectCSS("ui:///rend/extra-themes.css");
injectCSS('ui:///rend/extra-themes.css'); injectCSS("ui:///css/fa/6.7.1/css/all.min.css");
injectCSS('ui:///css/fa/6.7.1/css/all.min.css');
// Load jQuery first try {
loadScript('ui:///lib/jquery-3.3.1.min.js', () => { await loadScriptAsync("ui:///lib/jquery-3.3.1.min.js");
// Load confetti and iziToast after jQuery (ui libs) await loadScriptAsync("ui:///lib/confetti-1.9.3-browser.min.js");
loadScript('ui:///lib/confetti-1.9.3-browser.min.js'); await loadScriptAsync("ui:///lib/izitoast.min.js");
loadScript('ui:///lib/izitoast.min.js', () => { await loadScriptAsync("ui:///rend/register-handles.js");
// Load app specific scripts await loadScriptAsync("ui:///rend/bsky-ext.js");
loadScript('ui:///rend/register-handles.js'); await loadScriptAsync("ui:///rend/specialAnimations.js");
// Load custom implementations } catch (error) {
loadScript('ui:///rend/bsky-ext.js'); console.error("Failed to load one or more scripts.", error);
loadScript('ui:///rend/specialAnimations.js'); }
}); });
});
});

File diff suppressed because one or more lines are too long

View File

@@ -36,6 +36,8 @@ document.addEventListener('DOMContentLoaded', () => {
ipcRenderer.on('ui:progtext', (event, other) => { ipcRenderer.on('ui:progtext', (event, other) => {
console.log('Updating progress text:', other); console.log('Updating progress text:', other);
infotext.innerText = other.title; infotext.innerText = other.title;
progtext.innerText = other.subtitle; if (other.subtitle) {
progtext.innerText = other.subtitle;
};
}); });
}); });