diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..acaecce
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,173 @@
+# This workflow runs on every new tag created, and builds the app for 3 platforms (mac, windows, linux).
+# It also creates a new release on the repo's Release page, and uploads the artifacts to there.
+#
+# Usage:
+# git commit -m "Release v0.9.2"
+# git tag v0.9.2
+# git push --force --tags
+run-name: Build & Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ release:
+ name: ${{ matrix.os == 'macos-latest' && 'Mac' || 'Linux' }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [macos-latest, ubuntu-latest]
+
+ steps:
+ - name: Check out Git repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Get semver string
+ id: semver_parser
+ uses: booxmedialtd/ws-action-parse-semver@v1
+ with:
+ version_extractor_regex: 'v(.*)'
+ input_string: ${{ github.ref_name }}
+
+ - name: Get previous tag
+ run: |
+ PREVTAG=$(git describe --abbrev=0 --tags "${{ github.ref }}^")
+ echo "PREVTAG=$PREVTAG" >> $GITHUB_ENV
+
+ - name: Get version from package.json
+ run: |
+ echo PKGJSONVERSION=$(jq -r '.version' package.json) >> $GITHUB_ENV
+
+ - name: Version check
+ run: |
+ if [ "${{ env.PKGJSONVERSION }}" != "${{ steps.semver_parser.outputs.fullversion }}" ]; then
+ echo "Version mismatch: \"${{ env.PKGJSONVERSION }}\" != \""${{ steps.semver_parser.outputs.fullversion }}"\""
+ echo "You need to update the version number in package.json"
+ exit 1
+ fi
+
+ - name: Generate Changelog Body
+ if: matrix.os == 'macos-latest'
+ run: |
+ echo -e "# Sidenoder \`${{ github.ref_name }}\`" > changelog-body.md
+
+ git log ${{env.PREVTAG}}..HEAD^1 --pretty=format:"%s" | sort -f | while read line; do
+ line="$(tr '[:lower:]' '[:upper:]' <<< ${line:0:1})${line:1}"
+
+ case $line in
+ [Ff]eat*)
+ line=$(echo $line | sed -E "s/^[Ff]eat\(?.*\)?: //")
+ echo "- $line" >> commits-feat;;
+ [Ff]ix*)
+ line=$(echo $line | sed -E "s/^[Ff]ix\(?.*\)?: //")
+ echo "- $line" >> commits-fix;;
+ [Pp]erf*)
+ line=$(echo $line | sed -E "s/^[Pp]erf\(?.*\)?: //")
+ echo "- $line" >> commits-perf;;
+ [Ss]tyle*)
+ line=$(echo $line | sed -E "s/^[Ss]tyle\(?.*\)?: //")
+ echo "- $line" >> commits-style;;
+ [Mm]erge*)
+ # Skip merge commits
+ ;;
+ *)
+ echo "- $line" >> commits-other;;
+ esac
+ done
+
+ if [ -s commits-feat ]; then
+ echo -e "\n## New Features\n\n$(cat commits-feat)" > commits-feat
+ cat commits-feat >> changelog-body.md
+ fi
+
+ if [ -s commits-fix ]; then
+ echo -e "\n## Fixes\n\n$(cat commits-fix)" > commits-fix
+ cat commits-fix >> changelog-body.md
+ fi
+
+ if [ -s commits-perf ]; then
+ echo -e "\n## Performance Improvements\n\n$(cat commits-perf)" > commits-perf
+ cat commits-perf >> changelog-body.md
+ fi
+
+ if [ -s commits-style ]; then
+ echo -e "\n## Style Changes\n\n$(cat commits-style)" > commits-style
+ cat commits-style >> changelog-body.md
+ fi
+
+ if [ -s commits-other ]; then
+ echo -e "\n## Other Changes\n\n$(cat commits-other)" > commits-other
+ cat commits-other >> changelog-body.md
+ fi
+
+ echo -e "\n---\n\n" >> changelog-body.md
+ echo -e "### View the full changelog [here](https://github.com/MikeRatcliffe/sidenoder/compare/${{env.PREVTAG}}...${{ github.ref_name }})." >> changelog-body.md
+
+ - name: Install Node.js, NPM and Yarn
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22.x
+
+ - name: npm install
+ run: npm install
+
+ - name: Build (Mac)
+ if: matrix.os == 'macos-latest'
+ run: |
+ npm run dist-mac
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install Wine (Windows)
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ sudo dpkg --add-architecture i386
+ sudo apt-get update
+ sudo apt-get install wine32 wine64
+
+ - name: Build (Windows & Linux)
+ if: matrix.os == 'ubuntu-latest'
+ run: npm run dist-win-linux
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Push Build To Releases (Mac)
+ if: matrix.os == 'macos-latest'
+ uses: ncipollo/release-action@v1
+ with:
+ tag: v${{ steps.semver_parser.outputs.fullversion }}
+ allowUpdates: true
+ artifactErrorsFailBuild: true
+ bodyFile: changelog-body.md
+ generateReleaseNotes: false
+ makeLatest: false
+ prerelease: ${{ steps.semver_parser.outputs.prerelease != ''}}
+ replacesArtifacts: false
+ artifacts: /tmp/out/sidenoder*.dmg
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Push Builds To Releases (Windows & Linux)
+ if: matrix.os == 'ubuntu-latest'
+ uses: ncipollo/release-action@v1
+ with:
+ tag: v${{ steps.semver_parser.outputs.fullversion }}
+ allowUpdates: true
+ artifactErrorsFailBuild: true
+ generateReleaseNotes: false
+ makeLatest: true
+ omitBody: true
+ prerelease: ${{ steps.semver_parser.outputs.prerelease != ''}}
+ replacesArtifacts: false
+ artifacts: |
+ /tmp/out/sidenoder*.exe
+ /tmp/out/sidenoder*.AppImage
+ /tmp/out/sidenoder*.deb
+ /tmp/out/sidenoder*.rpm
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/main-osx.yml b/.github/workflows/main-osx.yml
deleted file mode 100644
index 027f26e..0000000
--- a/.github/workflows/main-osx.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: CI-OSX
-
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the main branch
-on:
- push:
- branches: [ ]
- pull_request:
- branches: [ ]
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
- runs-on: macos-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
-
- - uses: actions/checkout@v2
- - name: Use Node.js
- uses: actions/setup-node@v3
- with:
- node-version: '21.x'
-
- - name: run makeshit
- run: |
- npm install
- npm run dist-mac
-
- mkdir -p /tmp/builds
-
-
- cd /Users/runner/work/quest-sidenoder/quest-sidenoder/out/make
- mkdir -p /tmp/builds/darwin/dmg/
- mv *.dmg /tmp/builds/darwin/dmg/
- cd /Users/runner/work/quest-sidenoder/quest-sidenoder/out/make/zip/darwin/x64/
- mkdir -p /tmp/builds/darwin/zip/
- sudo mv *.zip /tmp/builds/darwin/zip/
-
-
- - name: Upload darwin Build dmg Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-darwin-x64-dmg
- path: /tmp/builds/darwin/dmg/
- - name: Upload darwin Build zip Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-darwin-x64-zip
- path: /tmp/builds/darwin/zip/
diff --git a/.github/workflows/main-win-other.yml b/.github/workflows/main-win-other.yml
deleted file mode 100644
index a3931d9..0000000
--- a/.github/workflows/main-win-other.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: CI-WINDOWS-OTHER
-
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the main branch
-on:
- push:
- branches: [ ]
- pull_request:
- branches: [ ]
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
- runs-on: ubuntu-20.04
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
- - name: Use Node.js
- uses: actions/setup-node@v3
- with:
- node-version: '21.x'
-
- - name: electron-packager
- run: |
-
- npm install
- sudo dpkg --add-architecture i386
- sudo apt update
- sudo apt-get install wine32
- sudo apt install wine64
- sudo npm install electron-packager -g
- sudo electron-packager ./ sidenoder --platform=win32 --arch=x64 --icon=build/icon.png
-
- sudo cp /home/runner/work/quest-sidenoder/quest-sidenoder/windows-install.bat /home/runner/work/quest-sidenoder/quest-sidenoder/sidenoder-win32-x64/windows-install.bat
- cd /home/runner/work/quest-sidenoder/quest-sidenoder/sidenoder-win32-x64/
- mkdir -p /tmp/builds/win32/x64/
- zip -r /tmp/builds/win32/x64/sidenoder-win32-x64.zip *
-
-
- - name: Upload windows Build Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-win32-x64
- path: /tmp/builds/win32/x64/sidenoder-win32-x64.zip
\ No newline at end of file
diff --git a/.github/workflows/main-win.yml b/.github/workflows/main-win.yml
deleted file mode 100644
index abba994..0000000
--- a/.github/workflows/main-win.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: CI-WINDOWS
-
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the main branch
-on:
- push:
- branches: [ ]
- pull_request:
- branches: [ ]
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
- - name: Use Node.js
- uses: actions/setup-node@v3
- with:
- node-version: '21.x'
-
-
- - name: electron-packager
- run: |
-
- npm install
- sudo apt install wine64 --fix-missing
- sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install wine32
- sudo npm install electron-packager -g
- electron-packager ./ sidenoder --platform=win32 --arch=x64 --icon=build/icon.png
-
- sudo cp /home/runner/work/quest-sidenoder/quest-sidenoder/windows-install.bat /home/runner/work/quest-sidenoder/quest-sidenoder/sidenoder-win32-x64/windows-install.bat
- cd /home/runner/work/quest-sidenoder/quest-sidenoder/sidenoder-win32-x64/
- mkdir -p /tmp/builds/win32/x64/
- zip -r /tmp/builds/win32/x64/sidenoder-win32-x64.zip *
-
-
- - name: Upload windows Build Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-win32-x64
- path: /tmp/builds/win32/x64/sidenoder-win32-x64.zip
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index f1a3286..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,90 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: CI-LINUX
-
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the main branch
-on:
- push:
- branches: [ ]
- pull_request:
- branches: [ ]
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
-
- # Runs a single command using the runners shell
- - uses: actions/checkout@v4
- - name: Use Node.js
- uses: actions/setup-node@v3
- with:
- node-version: '21.x'
- - name: run make
- run: |
-
- npm install
- npm run dist
-
- mkdir -p /tmp/builds
- cd /home/runner/work/quest-sidenoder/quest-sidenoder/out/make/deb/x64/
- mkdir -p /tmp/builds/linux/deb/
- tar -cvf /tmp/builds/linux/deb/sidenoder-linux-x64-deb.tar *.deb
- cd /home/runner/work/quest-sidenoder/quest-sidenoder/out/make/rpm/x64/
- mkdir -p /tmp/builds/linux/rpm/
- tar -cvf /tmp/builds/linux/rpm/sidenoder-linux-x64-rpm.tar *.rpm
- cd /home/runner/work/quest-sidenoder/quest-sidenoder/out/make/zip/linux/x64/
- mkdir -p /tmp/builds/linux/zip/
- sudo mv *.zip /tmp/builds/linux/zip/
-
- cd /home/runner/work/quest-sidenoder/quest-sidenoder
- sudo rm -rf /home/runner/work/quest-sidenoder/quest-sidenoder/out
-
- sudo npm run-script dist
-
- mkdir -p /tmp/builds/AppImage/x64
- mkdir -p /tmp/builds/snap/x64
-
- cd /home/runner/work/quest-sidenoder/quest-sidenoder/dist/
- sudo mv sidenoder*.AppImage /tmp/builds/AppImage/x64/
- sudo mv sidenoder*.snap /tmp/builds/snap/x64/
-
-
-
-
-
-
- - name: Upload linux Build deb Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-linux-x64-deb
- path: /tmp/builds/linux/deb/
- - name: Upload linux Build rpm Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-linux-x64-rpm
- path: /tmp/builds/linux/rpm/
- - name: Upload linux Build zip Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-linux-x64-zip
- path: /tmp/builds/linux/zip/
-
- - name: Upload linux Build appimage Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-linux-x64-AppImage
- path: /tmp/builds/AppImage/x64/*.AppImage
- - name: Upload linux Build snap Artifact
- uses: actions/upload-artifact@v2
- with:
- name: sidenoder-linux-x64-snap
- path: /tmp/builds/snap/x64/*.snap
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..78f4d1d
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1 @@
+npm run commitlint ${1}
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 5e6a7c9..87ea02a 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1 +1 @@
-npm run prettier:check
+npm run pre-commit
diff --git a/.stylelintignore b/.stylelintignore
new file mode 100644
index 0000000..433b482
--- /dev/null
+++ b/.stylelintignore
@@ -0,0 +1 @@
+*.min.css
diff --git a/.stylelintrc.json b/.stylelintrc.json
new file mode 100644
index 0000000..0a08a15
--- /dev/null
+++ b/.stylelintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["stylelint-config-standard"]
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2cc3598
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,145 @@
+# Contributing to Sidenoder
+
+Thank you for your interest in contributing to **Sidenoder**! We appreciate your help in improving the project. Please follow the guidelines below to get started.
+
+## Table of Contents
+
+- [Table of Contents](#table-of-contents)
+- [Getting Started](#getting-started)
+- [Setting Up Your Environment](#setting-up-your-environment)
+- [Running the Project](#running-the-project)
+- [Making Changes](#making-changes)
+ - [Commit Guidelines](#commit-guidelines)
+- [Submitting Your Contribution](#submitting-your-contribution)
+ - [Pull Request Checklist](#pull-request-checklist)
+- [Code Style Guidelines](#code-style-guidelines)
+- [Reporting Issues](#reporting-issues)
+
+## Getting Started
+
+To contribute to Sidenoder, you’ll need to:
+
+1. Fork the repository from [Sidenoder GitHub](https://github.com/VRPirates/sidenoder).
+2. Clone your forked repository to your local machine.
+
+```bash
+git clone https://github.com/YOUR_USERNAME/sidenoder.git
+```
+
+3. Navigate to the project directory:
+
+```bash
+cd sidenoder
+```
+
+## Setting Up Your Environment
+
+Sidenoder is an Electron app, and you will need to have **Node.js** and **npm** installed. You can download and install Node.js from [nodejs.org](https://nodejs.org).
+
+After installing Node.js, run the following command to install the project dependencies:
+
+```bash
+npm install
+```
+
+## Running the Project
+
+Once the dependencies are installed, you can run the project locally in development mode. Use the following command:
+
+```bash
+npm start
+```
+
+This will start the Electron app, and any changes you make will be reflected in real time.
+
+## Making Changes
+
+Before making any changes, ensure that your local repository is up to date with the latest changes from the main repository:
+
+```bash
+git checkout main
+git pull upstream main
+```
+
+Now, create a new branch for your feature or bug fix:
+
+```bash
+git switch -c your-feature-branch-name
+```
+
+Make sure to keep your changes isolated to one specific task or issue.
+
+### Commit Guidelines
+
+Committing your code is simple, please stick to these rules:
+
+- Write clear, concise commit messages.
+- Use one commit per feature or bug fix.
+- Use present tense ("add feature" instead of "added feature").
+
+```bash
+# Commit your code
+# Be sure to follow the format for commits: `type: description`
+# e.g. `fix: correct issue with fetch request`
+git commit -ma "fix: correct issue with fetch request"
+```
+
+The following types are valid:
+
+| **Type** | **Description** |
+| ---------- | ----------------------------------------------------------------------------------------------------------- |
+| `build` | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
+| `chore` | Other changes that don't modify `src` or test files |
+| `ci` | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
+| `docs` | Documentation only changes |
+| `feat` | A new feature |
+| `fix` | A bug fix |
+| `perf` | Performance improvements |
+| `refactor` | A code change that neither fixes a bug nor adds a feature |
+| `revert` | Reverts a previous commit |
+| `style` | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) |
+| `test` | Adding missing tests or correcting existing tests |
+
+## Submitting Your Contribution
+
+Once you're done with your changes:
+
+1. Push your branch to your forked repository:
+
+```bash
+git push origin your-feature-branch-name
+```
+
+2. Go to the original repository at [Sidenoder GitHub](https://github.com/VRPirates/sidenoder).
+3. Click on the `[Pull Request]` button.
+4. Ensure your pull request has a clear description of what you’ve done and reference any relevant issues (if applicable).
+
+### Pull Request Checklist
+
+**NOTE: Always Ensure that the code works by running the app locally.**
+
+After submitting, a project maintainer will review your pull request. You may be asked to make changes, so please respond to feedback promptly.
+
+## Code Style Guidelines
+
+To maintain consistency, ensure that your code follows the project's coding standards. We use the following tools:
+
+- **ESLint**: Helps catch common errors and enforces code consistency.
+- **Prettier**: Formats code.
+- **StyleLint**: Helps catch common CSS errors and enforces code consistency.
+
+These tools will automatically run when you attempt to commit your code and warn you of any issues.
+
+## Reporting Issues
+
+If you encounter any issues or have suggestions for improvements, please open an issue on the [Issues page](https://github.com/VRPirates/sidenoder/issues). Be sure to include:
+
+- A clear and descriptive title.
+- Steps to reproduce the issue, if applicable.
+- Screenshots or logs if relevant.
+
+You can also browse existing issues and contribute by helping to resolve them.
+
+---
+
+Thank you for contributing to Sidenoder! Your support is highly appreciated, and we look forward to seeing your pull requests.
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..06b6ac1
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,65 @@
+/**
+ * This commitlint config file extends the conventional configuration
+ * and adds the following customizations:
+ *
+ * - Ignores commit messages that start with "Merge"
+ * - Allows the following commit types:
+ * - build
+ * - chore
+ * - ci
+ * - docs
+ * - feat
+ * - fix
+ * - perf
+ * - refactor
+ * - revert
+ * - style
+ * - test
+ * - Merge
+ *
+ * Please see the commitlint documentation for more information on how to use
+ * this configuration file:
+ * https://commitlint.js.org/reference/rules.html
+ *
+ */
+const RuleConfigSeverity = {
+ Disabled: 0,
+ Warning: 1,
+ Error: 2,
+};
+
+module.exports = {
+ ignores: [(message) => message.startsWith("Merge")],
+ rules: {
+ "type-enum": [
+ 2,
+ "always",
+ [
+ "build",
+ "chore",
+ "ci",
+ "docs",
+ "feat",
+ "fix",
+ "perf",
+ "refactor",
+ "revert",
+ "style",
+ "test",
+ ],
+ ],
+ "body-case": [RuleConfigSeverity.Disabled, "always"],
+ "body-leading-blank": [RuleConfigSeverity.Warning, "always"],
+ "body-max-line-length": [RuleConfigSeverity.Disabled, "always"],
+ "footer-leading-blank": [RuleConfigSeverity.Warning, "always"],
+ "footer-max-line-length": [RuleConfigSeverity.Disabled, "always"],
+ "header-max-length": [RuleConfigSeverity.Disabled, "always"],
+ "header-trim": [RuleConfigSeverity.Error, "always"],
+ "scope-case": [RuleConfigSeverity.Error, "always", "lower-case"],
+ "subject-case": [RuleConfigSeverity.Disabled, "always"],
+ "subject-empty": [RuleConfigSeverity.Error, "never"],
+ "subject-full-stop": [RuleConfigSeverity.Error, "never", "."],
+ "type-case": [RuleConfigSeverity.Error, "always", "lower-case"],
+ "type-empty": [RuleConfigSeverity.Error, "never"],
+ },
+};
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..50afe90
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,167 @@
+import eslintConfigPrettier from "eslint-config-prettier";
+import globals from "globals";
+import html from "eslint-plugin-html";
+import json from "eslint-plugin-json";
+import pluginJs from "@eslint/js";
+import stylisticJs from "@stylistic/eslint-plugin-js";
+
+export default [
+ pluginJs.configs.recommended,
+ eslintConfigPrettier,
+ {
+ files: ["**/*.js", "**/*.html", "**/*.twig"],
+ languageOptions: {
+ ecmaVersion: "latest",
+ sourceType: "commonjs",
+ globals: {
+ ...globals.browser,
+ ...globals.commonjs,
+ ...globals.node,
+ $: "readonly",
+ $id: "readonly",
+ arch: "readonly",
+ checkMount: "readonly",
+ dialog: "readonly",
+ formatBytes: "readonly",
+ formatEta: "readonly",
+ hash_alg: "readonly",
+ id: "readonly",
+ ipcRenderer: "readonly",
+ loadInclude: "readonly",
+ path: "readonly",
+ platform: "readonly",
+ remote: "readonly",
+ shell: "readonly",
+ sidenoderHome: "readonly",
+ version: "readonly",
+ win: "readonly",
+ },
+ },
+ plugins: {
+ html,
+ "@stylistic/js": stylisticJs,
+ },
+ rules: {
+ "@stylistic/js/brace-style": [
+ "error",
+ "1tbs",
+ { allowSingleLine: false },
+ ],
+ "@stylistic/js/indent": [
+ "error",
+ 2,
+ {
+ ignoredNodes: ["TemplateLiteral"],
+ SwitchCase: 1,
+ },
+ ],
+ "@stylistic/js/no-mixed-spaces-and-tabs": ["error"],
+ "@stylistic/js/semi": ["error", "always"],
+ "constructor-super": ["error"],
+ curly: ["error", "all"],
+ eqeqeq: ["error", "always"],
+ "for-direction": ["error"],
+ "getter-return": ["error"],
+ "no-async-promise-executor": ["error"],
+ "no-case-declarations": ["error"],
+ "no-class-assign": ["error"],
+ "no-compare-neg-zero": ["error"],
+ "no-cond-assign": ["error"],
+ "no-const-assign": ["error"],
+ "no-constant-condition": ["error"],
+ "no-control-regex": ["error"],
+ "no-debugger": ["error"],
+ "no-delete-var": ["error"],
+ "no-dupe-args": ["error"],
+ "no-dupe-class-members": ["error"],
+ "no-dupe-else-if": ["error"],
+ "no-dupe-keys": ["error"],
+ "no-duplicate-case": ["error"],
+ "no-else-return": ["error"],
+ "no-empty": ["error"],
+ "no-empty-character-class": ["error"],
+ "no-empty-pattern": ["error"],
+ "no-ex-assign": ["error"],
+ "no-extra-boolean-cast": ["off"],
+ "no-fallthrough": ["error"],
+ "no-func-assign": ["error"],
+ "no-global-assign": ["error"],
+ "no-import-assign": ["error"],
+ "no-inner-declarations": ["off"],
+ "no-invalid-regexp": ["error"],
+ "no-irregular-whitespace": ["error"],
+ "no-loss-of-precision": ["error"],
+ "no-misleading-character-class": ["error"],
+ "no-nonoctal-decimal-escape": ["error"],
+ "no-obj-calls": ["error"],
+ "no-octal": ["error"],
+ "no-prototype-builtins": ["error"],
+ "no-redeclare": ["error"],
+ "no-regex-spaces": ["error"],
+ "no-self-assign": ["error"],
+ "no-setter-return": ["error"],
+ "no-shadow-restricted-names": ["error"],
+ "no-sparse-arrays": ["error"],
+ "no-this-before-super": ["error"],
+ "no-undef": ["error"],
+ "no-unexpected-multiline": ["error"],
+ "no-unreachable": ["error"],
+ "no-unsafe-finally": ["error"],
+ "no-unsafe-negation": ["error"],
+ "no-unsafe-optional-chaining": ["error"],
+ "no-unused-labels": ["error"],
+ "no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^_",
+ caughtErrors: "all",
+ caughtErrorsIgnorePattern: "^_",
+ },
+ ],
+ "no-use-before-define": [
+ "error",
+ {
+ functions: false,
+ classes: true,
+ variables: true,
+ allowNamedExports: true,
+ },
+ ],
+ "no-useless-backreference": ["error"],
+ "no-useless-catch": ["error"],
+ "no-useless-escape": ["error"],
+ "no-var": ["error"],
+ "no-with": ["error"],
+ "prefer-const": [
+ "error",
+ {
+ destructuring: "all",
+ },
+ ],
+ "prefer-regex-literals": ["error"],
+ "prefer-template": ["error"],
+ "require-yield": ["error"],
+ "use-isnan": ["error"],
+ "valid-typeof": ["error"],
+ },
+ },
+ {
+ files: ["**/*.mjs"],
+ languageOptions: {
+ sourceType: "module",
+ },
+ },
+ {
+ files: ["**/*.json"],
+ plugins: {
+ json,
+ },
+ processor: json.processors[".json"],
+ rules: {
+ ...json.configs.recommended.rules,
+ },
+ },
+ {
+ ignores: ["**/node_modules/**", "**/*.min.js", "**/modernizr.custom.js"],
+ },
+];
diff --git a/main.js b/main.js
index e75412d..c37cc04 100644
--- a/main.js
+++ b/main.js
@@ -1,13 +1,18 @@
+/* eslint
+ no-unused-vars: [
+ "error", {
+ "varsIgnorePattern": "dialog",
+ "argsIgnorePattern": "^_"
+ }
+ ]
+*/
const {
app,
BrowserWindow,
Notification,
- powerSaveBlocker,
ipcMain,
dialog,
} = require("electron");
-// const { BrowserWindow } = require('@electron/remote')
-const fs = require("fs");
const path = require("path");
global.twig = require("electron-twig");
@@ -35,20 +40,18 @@ global.hash_alg = "sha256";
global.locale = "en-US";
global.platform = global.platform.replace("32", "").replace("64", "");
-if (global.platform == "darwin") global.platform = "mac";
+if (global.platform === "darwin") {
+ global.platform = "mac";
+}
app.on("ready", () => {
global.locale = app.getLocale();
});
-eval(fs.readFileSync(path.join(__dirname, "versioncheck.js"), "utf8"));
-
+const checkVersion = require("./versioncheck");
const tools = require("./tools");
-// const id = powerSaveBlocker.start('prevent-display-sleep');
-// console.log(powerSaveBlocker.isStarted(id));
-
-ipcMain.on("get_installed", async (event, arg) => {
+ipcMain.on("get_installed", async (event, _arg) => {
console.log("get_installed received");
const apps = await tools.getInstalledApps();
@@ -58,7 +61,7 @@ ipcMain.on("get_installed", async (event, arg) => {
return;
});
-ipcMain.on("get_installed_with_updates", async (event, arg) => {
+ipcMain.on("get_installed_with_updates", async (event, _arg) => {
console.log("get_installed_with_updates received");
const apps = await tools.getInstalledAppsWithUpdates();
@@ -67,7 +70,7 @@ ipcMain.on("get_installed_with_updates", async (event, arg) => {
event.reply("get_installed_with_updates", { success: true, apps });
});
-ipcMain.on("get_device_info", async (event, arg) => {
+ipcMain.on("get_device_info", async (event, _arg) => {
getDeviceInfo(event);
});
@@ -78,7 +81,7 @@ async function getDeviceInfo(event) {
event.reply("get_device_info", res);
}
-ipcMain.on("connect_wireless", async (event, arg) => {
+ipcMain.on("connect_wireless", async (event, _arg) => {
console.log("connect_wireless received");
if (!global.adbDevice && !global.currentConfiguration.lastIp) {
console.log("Missing device, sending ask_device");
@@ -92,7 +95,7 @@ ipcMain.on("connect_wireless", async (event, arg) => {
return;
});
-ipcMain.on("disconnect_wireless", async (event, arg) => {
+ipcMain.on("disconnect_wireless", async (event, _arg) => {
console.log("disconnect_wireless received");
const res = await tools.disconnectWireless();
event.reply("connect_wireless", { success: !res });
@@ -106,14 +109,14 @@ ipcMain.on("check_deps", async (event, arg) => {
event.reply("check_deps", res);
});
-ipcMain.on("mount", async (event, arg) => {
+ipcMain.on("mount", async (event, _arg) => {
await tools.mount();
setTimeout(() => checkMount(event), 1000);
return;
});
-ipcMain.on("check_mount", async (event, arg) => {
+ipcMain.on("check_mount", async (event, _arg) => {
checkMount(event);
});
@@ -179,6 +182,7 @@ ipcMain.on("reset_cache", async (event, arg) => {
ipcMain.on("get_dir", async (event, arg) => {
console.log("get_dir received", arg);
+
if (typeof arg === "string" && arg.endsWith(".apk")) {
const install = {
path: arg,
@@ -199,11 +203,12 @@ ipcMain.on("get_dir", async (event, arg) => {
const list = await tools.getDir(folder);
- dirList = [];
- incList = [];
- notSupported = [];
- if (!list) incList = [{ name: "ERROR: Browse failed" }];
- else
+ const notSupported = [];
+ const dirList = [];
+ let incList = [];
+ if (!list) {
+ incList = [{ name: "ERROR: Browse failed" }];
+ } else {
for (const item of list) {
if (!item.isFile) {
dirList.push(item);
@@ -217,8 +222,9 @@ ipcMain.on("get_dir", async (event, arg) => {
notSupported.push(item);
}
+ }
- response = {};
+ const response = {};
response.success = true;
response.list = dirList.concat(incList, notSupported);
response.path = folder;
@@ -227,7 +233,7 @@ ipcMain.on("get_dir", async (event, arg) => {
//event.reply('get_dir', response)
});
-ipcMain.on("enable_mtp", async (event, arg) => {
+ipcMain.on("enable_mtp", async (event, _arg) => {
console.log("enable_mtp received");
if (!global.adbDevice) {
console.log("Missing device, sending ask_device");
@@ -245,7 +251,7 @@ ipcMain.on("enable_mtp", async (event, arg) => {
return;
});
-ipcMain.on("scrcpy_start", async (event, arg) => {
+ipcMain.on("scrcpy_start", async (event, _arg) => {
console.log("scrcpy_start received");
if (!global.adbDevice) {
console.log("Missing device, sending ask_device");
@@ -259,7 +265,7 @@ ipcMain.on("scrcpy_start", async (event, arg) => {
return;
});
-ipcMain.on("reboot_device", async (event, arg) => {
+ipcMain.on("reboot_device", async (event, _arg) => {
console.log("reboot_device received");
if (!global.adbDevice) {
console.log("Missing device, sending ask_device");
@@ -277,7 +283,7 @@ ipcMain.on("reboot_device", async (event, arg) => {
return;
});
-ipcMain.on("reboot_recovery", async (event, arg) => {
+ipcMain.on("reboot_recovery", async (event, _arg) => {
console.log("reboot_recovery received");
if (!global.adbDevice) {
console.log("Missing device, sending ask_device");
@@ -295,7 +301,7 @@ ipcMain.on("reboot_recovery", async (event, arg) => {
return;
});
-ipcMain.on("reboot_bootloader", async (event, arg) => {
+ipcMain.on("reboot_bootloader", async (event, _arg) => {
console.log("reboot_bootloader received");
if (!global.adbDevice) {
console.log("Missing device, sending ask_device");
@@ -339,12 +345,12 @@ ipcMain.on("sideload_update", async (event, arg) => {
ipcMain.on("device_tweaks", async (event, arg) => {
console.log("device_tweaks received", arg);
- if (arg.cmd == "get") {
+ if (arg.cmd === "get") {
const res = await tools.deviceTweaksGet(arg);
event.reply("device_tweaks", res);
}
- if (arg.cmd == "set") {
+ if (arg.cmd === "set") {
if (!global.adbDevice) {
console.log("Missing device, sending ask_device");
event.reply("ask_device", "");
@@ -352,7 +358,7 @@ ipcMain.on("device_tweaks", async (event, arg) => {
}
const res = await tools.deviceTweaksSet(arg);
- event.reply("device_tweaks", arg);
+ event.reply("device_tweaks", res);
}
return;
@@ -360,7 +366,7 @@ ipcMain.on("device_tweaks", async (event, arg) => {
ipcMain.on("uninstall", async (event, arg) => {
console.log("uninstall received");
- resp = await tools.uninstall(arg);
+ await tools.uninstall(arg);
event.reply("uninstall", { success: true });
getDeviceInfo(event);
return;
@@ -480,7 +486,7 @@ function createWindow() {
};
win.loadURL(`file://${__dirname}/views/index.twig`);
- if (process.argv[2] == "--dev") {
+ if (process.argv[2] === "--dev") {
win.webContents.openDevTools();
}
@@ -528,16 +534,22 @@ async function startApp() {
createWindow();
app.on("activate", () => {
- if (BrowserWindow.getAllWindows().length != 0) return;
+ if (BrowserWindow.getAllWindows().length !== 0) {
+ return;
+ }
createWindow();
});
- app.on("window-all-closed", (e) => {
- // powerSaveBlocker.stop(id)
+ app.on("window-all-closed", () => {
console.log("quit");
if (global.platform !== "mac") {
app.quit();
}
});
+
+ app.on("will-quit", async () => {
+ console.log("will-quit");
+ await tools.destroy();
+ });
}
startApp();
diff --git a/package.json b/package.json
index d00b347..700230b 100644
--- a/package.json
+++ b/package.json
@@ -6,55 +6,97 @@
"dependencies": {
"@devicefarmer/adbkit": "3.2.6",
"@electron/remote": "2.1.2",
+ "@eslint/js": "^9.10.0",
"@fortawesome/fontawesome-free": "^6.6.0",
+ "@stylistic/eslint-plugin-js": "^2.7.2",
"adbkit-apkreader": "3.2.0",
+ "bootstrap": "^5.3.3",
+ "bootswatch": "^5.3.3",
"command-exists": "1.2.9",
"compare-versions": "6.1.1",
"electron-find": "1.0.7",
"electron-twig": "1.1.1",
+ "eslint": "^9.10.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-html": "^8.1.1",
+ "eslint-plugin-json": "^4.0.1",
"fix-path": "4.0.0",
+ "globals": "^15.9.0",
"jquery": "3.7.1",
"jquery-ui": "^1.14.0",
- "node-fetch": "3.3.2",
"socks-proxy-agent": "8.0.4",
"web-auto-extractor": "1.0.17"
},
"devDependencies": {
+ "@commitlint/cli": "^19.5.0",
+ "@commitlint/config-conventional": "^19.5.0",
"@destination/prettier-plugin-twig": "^1.5.0",
"electron": "32.0.2",
"electron-builder": "25.0.5",
"husky": "^9.1.5",
+ "inquirer": "^9.3.6",
"lint-staged": "^15.2.10",
- "prettier": "^3.3.3"
+ "prettier": "^3.3.3",
+ "stylelint": "^16.9.0",
+ "stylelint-config-standard": "^36.0.1"
},
"scripts": {
+ "commitlint": "commitlint --edit",
"start": "electron ./ --dev",
"pack": "electron-builder --dir",
"dist": "electron-builder -mwl",
"dist-win": "electron-builder -w",
+ "dist-win-linux": "electron-builder -wl",
"dist-mac": "electron-builder -m",
"dist-linux": "electron-builder -l",
+ "eslint:check": "eslint .",
+ "eslint:fix": "eslint . --fix",
+ "pre-commit": "lint-staged --concurrent false",
"prettier:check": "prettier --check \"**/*.{css,js,json,md,twig,yml}\"",
"prettier:format": "prettier --write \"**/*.{css,js,json,md,twig,yml}\"",
"postdist": "npm run pack-win && npm run pack-mac",
"postinstall": "electron-builder install-app-deps",
"pack-win": "cd out/win-unpacked && cp ../windows-install.bat ./ && 7za a sidenoder-$npm_package_version-win.zip ./* && mv sidenoder-$npm_package_version-win.zip ../",
"pack-mac": "cd out/mac && tar -czf sidenoder-$npm_package_version-mac.tar.gz ./* && mv sidenoder-$npm_package_version-mac.tar.gz ../",
- "prepare": "husky"
+ "prepare": "husky",
+ "stylelint:check": "stylelint \"**/*.css\"",
+ "stylelint:fix": "stylelint --fix \"**/*.css\""
},
"lint-staged": {
- "{**/*,*}.{css,js,json,md,twig,yml}": "prettier --write"
+ "**/*.css": [
+ "stylelint --fix",
+ "stylelint"
+ ],
+ "*.{js,html,twig}": [
+ "eslint --fix",
+ "eslint"
+ ],
+ "*.{css,html,js,json,md,twig,yml}": [
+ "prettier --write",
+ "prettier --check"
+ ]
},
"build": {
"appId": "com.sidenoder.app",
"asar": true,
"directories": {
- "output": "out"
+ "output": "/tmp/out"
},
"afterPack": "./removeLocales.js",
+ "publish": [
+ {
+ "provider": "github",
+ "owner": "VRPirates"
+ }
+ ],
"mac": {
"target": [
- "dir"
+ {
+ "target": "dmg",
+ "arch": [
+ "universal"
+ ]
+ }
]
},
"win": {
@@ -80,6 +122,12 @@
"arch": [
"x64"
]
+ },
+ {
+ "target": "rpm",
+ "arch": [
+ "x64"
+ ]
}
],
"category": "Utility"
diff --git a/readme.md b/readme.md
index efd7846..98ad79b 100644
--- a/readme.md
+++ b/readme.md
@@ -1,3 +1,5 @@
+[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
+
**SideNoder** - A **cross platform sideloader** for Quest(1&2&3) standalone vr headset.
# Quest 3 compatibility fix added in v 0.8.0
@@ -55,12 +57,18 @@ Other version of rclone will not work.
Also android tools and scrcpy is required:
```bash
- brew install scrcpy
+brew install scrcpy
brew install android-platform-tools
```
---
+## Contributing to Sidenoder
+
+To contribute to Sidenoder, please reqd our [Contributors Guide](./CONTRIBUTING.md).
+
+---
+
Please report any issues here :
https://github.com/VRPirates/sidenoder/issues
diff --git a/removeLocales.js b/removeLocales.js
index 14301b4..9f61a07 100644
--- a/removeLocales.js
+++ b/removeLocales.js
@@ -3,14 +3,18 @@ const LOCALES = ["en-US.pak", "ru.pak"];
exports.default = async function (context) {
// console.log(context);
- var fs = require("fs");
- var localeDir = context.appOutDir + "/locales/";
+ const fs = require("fs");
+ const localeDir = `${context.appOutDir}/locales/`;
fs.readdir(localeDir, function (err, files) {
//files is array of filenames (basename form)
- if (!(files && files.length)) return;
+ if (!(files && files.length)) {
+ return;
+ }
for (const file of files) {
- if (LOCALES.includes(file)) continue;
+ if (LOCALES.includes(file)) {
+ continue;
+ }
fs.unlinkSync(localeDir + file);
}
});
diff --git a/tools.js b/tools.js
index c83b74e..50cc3db 100644
--- a/tools.js
+++ b/tools.js
@@ -1,36 +1,35 @@
+/* eslint
+ no-unused-vars: [
+ "error", {
+ "caughtErrorsIgnorePattern": "^_",
+ "argsIgnorePattern": "^_"
+ }
+ ]
+*/
const exec = require("child_process").exec;
-// const fs = require('fs');
const fs = require("fs");
const fsp = fs.promises;
const util = require("util");
const path = require("path");
const crypto = require("crypto");
const commandExists = require("command-exists");
-const { dialog } = require("electron");
const ApkReader = require("adbkit-apkreader");
const adbkit = require("@devicefarmer/adbkit").default;
const adb = adbkit.createClient();
-const fetch = (...args) =>
- import("node-fetch").then(({ default: fetch }) => fetch(...args));
const WAE = require("web-auto-extractor").default;
// const HttpProxyAgent = require('https-proxy-agent'); // TODO add https proxy support
const { SocksProxyAgent } = require("socks-proxy-agent");
-const url = require("url");
-// const ApkReader = require('node-apk-parser');
-const fixPath = (...args) =>
- import("fix-path").then(({ default: fixPath }) => fixPath(...args));
-// adb.kill();
const pkg = require("./package.json");
const _sec = 1000;
const _min = 60 * _sec;
-let CHECK_META_PERIOD = 2 * _min;
+const CHECK_META_PERIOD = 2 * _min;
const l = 32;
const configLocationOld = path.join(global.homedir, "sidenoder-config.json");
const configLocation = path.join(global.sidenoderHome, "config.json");
-let agentOculus, agentSteam, agentSQ;
+let agentOculus, agentSteam, agentSQ, tracker;
init();
@@ -45,11 +44,11 @@ let META_VERSION = [];
let QUEST_ICONS = [];
let cacheOculusGames = false;
let KMETAS = {};
-let KMETAS2 = {};
+const KMETAS2 = {};
let adbCmd = "adb";
let grep_cmd = "| grep ";
-if (platform == "win") {
+if (platform === "win") {
grep_cmd = "| findstr ";
}
@@ -63,6 +62,7 @@ module.exports = {
trackDevices,
checkDeps,
checkMount,
+ destroy,
mount,
killRClone,
getDir,
@@ -145,7 +145,9 @@ async function getDeviceInfo() {
async function getFwInfo() {
console.log("getFwInfo()");
const res = await adbShell("getprop ro.build.branch");
- if (!res) return false;
+ if (!res) {
+ return false;
+ }
return {
version: res.replace("releases-oculus-", ""),
@@ -155,20 +157,44 @@ async function getFwInfo() {
async function getBatteryInfo() {
console.log("getBatteryInfo()");
const res = await adbShell("dumpsys battery");
- if (!res) return false;
+ if (!res) {
+ return false;
+ }
+
+ const opts = {};
+ for (const line of res.replace(/ /g, "").split("\n")) {
+ const splitLine = line.split(":");
+ const key = splitLine[0];
+ let value = splitLine[1];
+
+ if (value === "true") {
+ value = true;
+ }
+ if (value === "false") {
+ value = false;
+ }
+ if (isFinite(+value)) {
+ value = +value;
+ }
+
+ opts[key] = value;
+ }
- return parceOutOptions(res);
+ return opts;
}
async function getUserInfo() {
- if (global.currentConfiguration.userHide)
+ if (global.currentConfiguration.userHide) {
return {
name: "hidden",
};
+ }
console.log("getUserInfo()");
const res = await adbShell("dumpsys user | grep UserInfo");
- if (!res) return false;
+ if (!res) {
+ return false;
+ }
return {
name: res.split(":")[1],
@@ -177,7 +203,7 @@ async function getUserInfo() {
async function deviceTweaksGet(arg) {
console.log("deviceTweaksGet()", arg);
- let res = {
+ const res = {
cmd: "get",
// mp_name: '',
// guardian_pause: '0',
@@ -192,29 +218,37 @@ async function deviceTweaksGet(arg) {
// gSSO: '1440x1584',
};
- if (arg.key === "mp_name")
+ if (arg.key === "mp_name") {
res.mp_name = await adbShell("settings get global username");
- if (arg.key === "guardian_pause")
+ }
+ if (arg.key === "guardian_pause") {
res.guardian_pause = await adbShell("getprop debug.oculus.guardian_pause");
- if (arg.key === "frc")
+ }
+ if (arg.key === "frc") {
res.frc = await adbShell("getprop debug.oculus.fullRateCapture");
- if (arg.key === "gRR")
+ }
+ if (arg.key === "gRR") {
res.gRR = await adbShell("getprop debug.oculus.refreshRate");
- if (arg.key === "gCA")
+ }
+ if (arg.key === "gCA") {
res.gCA = await adbShell("getprop debug.oculus.forceChroma");
- if (arg.key === "gFFR")
+ }
+ if (arg.key === "gFFR") {
res.gFFR = await adbShell("getprop debug.oculus.foveation.level");
- if (arg.key === "CPU")
+ }
+ if (arg.key === "CPU") {
res.CPU = await adbShell("getprop debug.oculus.cpuLevel");
- if (arg.key === "GPU")
+ }
+ if (arg.key === "GPU") {
res.GPU = await adbShell("getprop debug.oculus.gpuLevel");
- if (arg.key === "vres")
+ }
+ if (arg.key === "vres") {
res.vres = await adbShell("getprop debug.oculus.videoResolution");
+ }
if (arg.key === "cres") {
- let captureDims =
- (await adbShell("getprop debug.oculus.capture.width")) +
- "x" +
- (await adbShell("getprop debug.oculus.capture.height"));
+ let captureDims = `${await adbShell(
+ "getprop debug.oculus.capture.width",
+ )}x${await adbShell("getprop debug.oculus.capture.height")}`;
// Default when not set
if (captureDims === "x") {
@@ -222,11 +256,11 @@ async function deviceTweaksGet(arg) {
}
res.cres = captureDims;
}
- if (arg.key === "gSSO")
- res.gSSO =
- (await adbShell("getprop debug.oculus.textureWidth")) +
- "x" +
- (await adbShell("getprop debug.oculus.textureHeight"));
+ if (arg.key === "gSSO") {
+ res.gSSO = `${await adbShell(
+ "getprop debug.oculus.textureWidth",
+ )}x${await adbShell("getprop debug.oculus.textureHeight")}`;
+ }
//oculus.capture.bitrate
return res;
@@ -234,60 +268,60 @@ async function deviceTweaksGet(arg) {
async function deviceTweaksSet(arg) {
console.log("deviceTweaksSet()", arg);
- let res = { cmd: "set" };
- if (typeof arg.mp_name != "undefined") {
- res.mp_name = await adbShell("settings put global username " + arg.mp_name);
+ const res = { cmd: "set" };
+ if (typeof arg.mp_name !== "undefined") {
+ res.mp_name = await adbShell(`settings put global username ${arg.mp_name}`);
}
- if (typeof arg.guardian_pause != "undefined") {
+ if (typeof arg.guardian_pause !== "undefined") {
res.guardian_pause = await adbShell(
- "setprop debug.oculus.guardian_pause " + (arg.guardian_pause ? "1" : "0"),
+ `setprop debug.oculus.guardian_pause ${arg.guardian_pause ? "1" : "0"}`,
);
}
- if (typeof arg.frc != "undefined") {
+ if (typeof arg.frc !== "undefined") {
res.frc = await adbShell(
- "setprop debug.oculus.fullRateCapture " + (arg.frc ? "1" : "0"),
+ `setprop debug.oculus.fullRateCapture ${arg.frc ? "1" : "0"}`,
);
}
- if (typeof arg.gRR != "undefined") {
- res.gRR = await adbShell("setprop debug.oculus.refreshRate " + arg.gRR);
+ if (typeof arg.gRR !== "undefined") {
+ res.gRR = await adbShell(`setprop debug.oculus.refreshRate ${arg.gRR}`);
}
- if (typeof arg.gCA != "undefined") {
- res.gCA = await adbShell("setprop debug.oculus.forceChroma " + arg.gCA);
+ if (typeof arg.gCA !== "undefined") {
+ res.gCA = await adbShell(`setprop debug.oculus.forceChroma ${arg.gCA}`);
}
- if (typeof arg.gFFR != "undefined") {
+ if (typeof arg.gFFR !== "undefined") {
res.gFFR = await adbShell(
- "setprop debug.oculus.foveation.level " + arg.gFFR,
+ `setprop debug.oculus.foveation.level ${arg.gFFR}`,
);
}
- if (typeof arg.CPU != "undefined") {
- res.CPU = await adbShell("setprop debug.oculus.cpuLevel " + arg.CPU);
+ if (typeof arg.CPU !== "undefined") {
+ res.CPU = await adbShell(`setprop debug.oculus.cpuLevel ${arg.CPU}`);
}
- if (typeof arg.GPU != "undefined") {
- res.GPU = await adbShell("setprop debug.oculus.gpuLevel " + arg.GPU);
+ if (typeof arg.GPU !== "undefined") {
+ res.GPU = await adbShell(`setprop debug.oculus.gpuLevel ${arg.GPU}`);
}
- if (typeof arg.vres != "undefined") {
+ if (typeof arg.vres !== "undefined") {
res.vres = await adbShell(
- "setprop debug.oculus.videoResolution " + arg.vres,
+ `setprop debug.oculus.videoResolution ${arg.vres}`,
);
}
- if (typeof arg.cres != "undefined") {
+ if (typeof arg.cres !== "undefined") {
const [width, height] = arg.cres.split("x");
- await adbShell("setprop debug.oculus.capture.width " + width);
- res.cres = await adbShell("setprop debug.oculus.capture.height " + height);
+ await adbShell(`setprop debug.oculus.capture.width ${width}`);
+ res.cres = await adbShell(`setprop debug.oculus.capture.height ${height}`);
}
- if (typeof arg.gSSO != "undefined") {
+ if (typeof arg.gSSO !== "undefined") {
const [width, height] = arg.gSSO.split("x");
- await adbShell("setprop debug.oculus.textureWidth " + width);
- await adbShell("setprop debug.oculus.textureHeight " + height);
+ await adbShell(`setprop debug.oculus.textureWidth ${width}`);
+ await adbShell(`setprop debug.oculus.textureHeight ${height}`);
res.gSSO = await adbShell(
"settings put system font_scale 0.85 && settings put system font_scale 1.0",
);
@@ -300,13 +334,15 @@ async function getStorageInfo() {
console.log("getStorageInfo()");
const linematch = await adbShell('df -h | grep "/storage/emulated"');
- if (!linematch) return false;
+ if (!linematch) {
+ return false;
+ }
- const refree = new RegExp("([0-9(.{1})]+[a-zA-Z%])", "g");
+ const refree = /([0-9(.{1})]+[a-zA-Z%])/g;
const storage = linematch.match(refree);
console.log(storage);
- if (storage.length == 3) {
+ if (storage.length === 3) {
return {
size: storage[0],
used: storage[1],
@@ -331,13 +367,15 @@ async function getLaunchActivity(pkg) {
return startActivity(activity);
}
-async function getActivities(pkg, activity = false) {
+async function getActivities(pkg) {
console.log("getActivities()", pkg);
let activities = await adbShell(
`dumpsys package | grep -Eo '^[[:space:]]+[0-9a-f]+[[:space:]]+${pkg}/[^[:space:]]+' | grep -oE '[^[:space:]]+$'`,
);
- if (!activities) return false;
+ if (!activities) {
+ return false;
+ }
activities = activities.split("\n");
// activities.pop();
@@ -389,7 +427,7 @@ async function checkAppTools(pkg) {
if (await fsp.exists(backupPath)) {
try {
availableRestore = await fsp.readFile(`${backupPath}/time.txt`, "utf8");
- } catch (err) {
+ } catch (_err) {
availableRestore = 1;
}
}
@@ -444,13 +482,17 @@ async function getDeviceIp() {
`ip -o route get to 8.8.8.8 | sed -n 's/.*src \\([0-9.]\\+\\).*/\\1/p'`,
);
console.log({ ip });
- if (ip) return ip;
+ if (ip) {
+ return ip;
+ }
ip = await adbShell(
`ip addr show wlan0 | grep 'inet ' | cut -d ' ' -f 6 | cut -d / -f 1`,
);
console.log({ ip });
- if (ip) return ip;
+ if (ip) {
+ return ip;
+ }
return false;
}
@@ -464,7 +506,6 @@ async function wifiEnable(enable) {
}
async function connectWireless() {
- const on = await adbShell("settings get global wifi_on");
if (!(await wifiGetStat())) {
console.error("connectWireless", "wifi disabled");
await wifiEnable(true);
@@ -475,7 +516,9 @@ async function connectWireless() {
// TODO: save ip & try use it
const ip = await getDeviceIp();
console.log({ ip });
- if (!ip) return false;
+ if (!ip) {
+ return false;
+ }
try {
if (global.adbDevice) {
@@ -500,7 +543,9 @@ async function connectWireless() {
async function disconnectWireless() {
const ip = await getDeviceIp();
- if (!ip) return false;
+ if (!ip) {
+ return false;
+ }
try {
const res = await adb.disconnect(ip, 5555);
@@ -519,8 +564,12 @@ async function isWireless() {
try {
const devices = await adb.listDevices();
for (const device of devices) {
- if (!device.id.includes(":5555")) continue;
- if (["offline", "authorizing"].includes(device.type)) continue;
+ if (!device.id.includes(":5555")) {
+ continue;
+ }
+ if (["offline", "authorizing"].includes(device.type)) {
+ continue;
+ }
if (["unauthorized"].includes(device.type)) {
win.webContents.send(
"alert",
@@ -553,7 +602,9 @@ async function isIdle() {
}
async function wakeUp() {
- if (!(await isIdle())) return;
+ if (!(await isIdle())) {
+ return;
+ }
return adbShell(`input keyevent KEYCODE_POWER`);
}
@@ -568,22 +619,23 @@ async function startSCRCPY() {
}
const scrcpyCmd =
- `"${global.currentConfiguration.scrcpyPath || "scrcpy"}" ` +
- (global.currentConfiguration.scrcpyCrop
- ? `--crop ${global.currentConfiguration.scrcpyCrop} `
- : "") +
- `-b ${global.currentConfiguration.scrcpyBitrate || 1}M ` +
- (global.currentConfiguration.scrcpyFps
- ? `--max-fps ${global.currentConfiguration.scrcpyFps} `
- : "") +
- (global.currentConfiguration.scrcpySize
- ? `--max-size ${global.currentConfiguration.scrcpySize} `
- : "") +
- (!global.currentConfiguration.scrcpyWindow ? "-f " : "") +
- (global.currentConfiguration.scrcpyOnTop ? "--always-on-top " : "") +
- (!global.currentConfiguration.scrcpyControl ? "-n " : "") +
- '--window-title "SideNoder Stream" ' +
- `-s ${global.adbDevice} `;
+ `"${global.currentConfiguration.scrcpyPath || "scrcpy"}" ${
+ global.currentConfiguration.scrcpyCrop
+ ? `--crop ${global.currentConfiguration.scrcpyCrop} `
+ : ""
+ }-b ${global.currentConfiguration.scrcpyBitrate || 1}M ${
+ global.currentConfiguration.scrcpyFps
+ ? `--max-fps ${global.currentConfiguration.scrcpyFps} `
+ : ""
+ }${
+ global.currentConfiguration.scrcpySize
+ ? `--max-size ${global.currentConfiguration.scrcpySize} `
+ : ""
+ }${!global.currentConfiguration.scrcpyWindow ? "-f " : ""}${
+ global.currentConfiguration.scrcpyOnTop ? "--always-on-top " : ""
+ }${
+ !global.currentConfiguration.scrcpyControl ? "-n " : ""
+ }--window-title "SideNoder Stream" ` + `-s ${global.adbDevice} `;
console.log({ scrcpyCmd });
wakeUp();
exec(scrcpyCmd, (error, stdout, stderr) => {
@@ -626,14 +678,16 @@ async function sideloadFile(path) {
return res;
}
-async function getDeviceSync(attempt = 0) {
+async function getDeviceSync() {
try {
// const lastDevice = global.adbDevice;
const devices = await adb.listDevices();
console.log({ devices });
global.adbDevice = false;
for (const device of devices) {
- if (["offline", "authorizing"].includes(device.type)) continue;
+ if (["offline", "authorizing"].includes(device.type)) {
+ continue;
+ }
if (["unauthorized"].includes(device.type)) {
win.webContents.send(
"alert",
@@ -644,18 +698,14 @@ async function getDeviceSync(attempt = 0) {
if (
!global.currentConfiguration.allowOtherDevices &&
- (await adbShell("getprop ro.product.brand", device.id)) != "oculus"
- )
+ (await adbShell("getprop ro.product.brand", device.id)) !== "oculus"
+ ) {
continue;
+ }
global.adbDevice = device.id;
}
- /*if (!global.adbDevice && devices.length > 0 && attempt < 1) {
- return setTimeout(()=> getDeviceSync(attempt + 1), 1000);
- }*/
- // if (lastDevice == global.adbDevice) return;
-
win.webContents.send("check_device", { success: global.adbDevice });
return global.adbDevice;
@@ -687,14 +737,16 @@ async function adbShell(cmd, deviceId = global.adbDevice, skipRead = false) {
output = await output.toString();
// output = output.split('\n');
// const end = output.pop();
- // if (end != '') output.push();
+ // if (end !== '') output.push();
console.log(`adbShell[${deviceId}]`, { cmd, output });
- if (output.substr(-1) == "\n") return output.slice(0, -1);
+ if (output.substr(-1) === "\n") {
+ return output.slice(0, -1);
+ }
return output;
} catch (err) {
console.error(`adbShell[${deviceId}]: err`, { cmd }, err);
global.adbError = err;
- if (err.toString() == `FailError: Failure: 'device offline'`) {
+ if (err.toString() === `FailError: Failure: 'device offline'`) {
getDeviceSync();
}
@@ -702,22 +754,6 @@ async function adbShell(cmd, deviceId = global.adbDevice, skipRead = false) {
}
}
-function parceOutOptions(line) {
- let opts = {};
- for (let l of line.split("\n")) {
- l = l.split(" ").join("");
- let [k, v] = l.split(":");
-
- if (v == "true") v = true;
- if (v == "false") v = false;
- if (!isNaN(+v)) v = +v;
-
- opts[k] = v;
- }
-
- return opts;
-}
-
// on empty dirrectory return false
async function adbFileExists(path) {
const r = await adbShell(`ls "${path}" 1>&1 2> /dev/null`);
@@ -733,7 +769,9 @@ async function adbPull(orig, dest, sync = false) {
let c = 0;
transfer.on("progress", (stats) => {
c++;
- if (c % 40 != 1) return; // skip 20 events
+ if (c % 40 !== 1) {
+ return;
+ } // skip 20 events
// console.log(orig + ' pulled', stats);
const res = {
@@ -769,7 +807,7 @@ async function adbPullFolder(orig, dest, sync = false) {
sync = await adb.getDevice(global.adbDevice).syncService();
}*/
- let actions = [];
+ const actions = [];
await fsp.mkdir(dest, { recursive: true });
const files = sync
? await sync.readdir(orig)
@@ -805,7 +843,9 @@ async function adbPush(orig, dest, sync = false) {
let c = 0;
transfer.on("progress", (stats) => {
c++;
- if (c % 40 != 1) return; // skip 20 events
+ if (c % 40 !== 1) {
+ return;
+ } // skip 20 events
// console.log(orig + ' pushed', stats);
const res = {
@@ -837,7 +877,9 @@ async function adbPushFolder(orig, dest, sync = false) {
const stat = await fsp.lstat(orig);
console.log({ orig, stat }, stat.isFile());
- if (stat.isFile()) return adbPush(orig, dest);
+ if (stat.isFile()) {
+ return adbPush(orig, dest);
+ }
/*let need_close = false;
if (!sync) {
@@ -845,7 +887,7 @@ async function adbPushFolder(orig, dest, sync = false) {
sync = await adb.getDevice(global.adbDevice).syncService();
}*/
- let actions = [];
+ const actions = [];
await adbShell(`mkdir -p ${dest}`, global.adbDevice, true);
const files = await fsp.readdir(orig, { withFileTypes: true });
for (const file of files) {
@@ -886,7 +928,9 @@ function execShellCommand(cmd, ignoreError = false, buffer = 100) {
return new Promise((resolve, reject) => {
exec(cmd, { maxBuffer: 1024 * buffer }, (error, stdout, stderr) => {
if (error) {
- if (ignoreError) return resolve(false);
+ if (ignoreError) {
+ return resolve(false);
+ }
console.error("exec_error", cmd, error);
return reject(error);
}
@@ -894,11 +938,12 @@ function execShellCommand(cmd, ignoreError = false, buffer = 100) {
if (stdout || !stderr) {
console.log("exec_stdout", cmd, stdout);
return resolve(stdout);
- } else {
- if (ignoreError) return resolve(false);
- console.error("exec_stderr", cmd, stderr);
- return reject(stderr);
}
+ if (ignoreError) {
+ return resolve(false);
+ }
+ console.error("exec_stderr", cmd, stderr);
+ return reject(stderr);
});
});
}
@@ -908,7 +953,7 @@ async function trackDevices() {
await getDeviceSync();
try {
- const tracker = await adb.trackDevices();
+ tracker = await adb.trackDevices();
tracker.on("add", async (device) => {
console.log("Device was plugged in", device.id);
// await getDeviceSync();
@@ -927,7 +972,6 @@ async function trackDevices() {
tracker.on("end", () => {
console.error("Tracking stopped");
- trackDevices();
});
} catch (err) {
console.error("Something went wrong:", err.stack);
@@ -935,11 +979,22 @@ async function trackDevices() {
}
}
+async function destroy() {
+ try {
+ await killRClone();
+ } catch (err) {
+ console.log("rclone not started");
+ }
+
+ tracker.end();
+ tracker = null;
+}
+
async function appInfo(args) {
const { res, pkg } = args;
const app = KMETAS[pkg];
- let data = {
+ const data = {
res,
pkg,
id: 0,
@@ -955,9 +1010,11 @@ async function appInfo(args) {
};
try {
- if (res == "steam") {
+ if (res === "steam") {
const steam = app && app.steam;
- if (!steam || !steam.id) throw "incorrect args";
+ if (!steam || !steam.id) {
+ throw "incorrect args";
+ }
data.id = steam.id;
data.url = `https://store.steampowered.com/app/${data.id}/`;
@@ -966,7 +1023,7 @@ async function appInfo(args) {
`https://store.steampowered.com/api/appdetails?appids=${data.id}`,
{
headers: {
- "Accept-Language": global.locale + ",en-US;q=0.5,en;q=0.3",
+ "Accept-Language": `${global.locale},en-US;q=0.5,en;q=0.3`,
},
agent: agentSteam,
},
@@ -977,9 +1034,11 @@ async function appInfo(args) {
Object.assign(data, json[data.id].data);
}
- if (res == "oculus") {
+ if (res === "oculus") {
const oculus = app && app.oculus;
- if (!oculus || !oculus.id) throw "incorrect args";
+ if (!oculus || !oculus.id) {
+ throw "incorrect args";
+ }
// console.log({ oculus });
data.id = oculus.id;
@@ -993,7 +1052,7 @@ async function appInfo(args) {
method: "POST",
body: `access_token=OC|1317831034909742|&variables={"itemId":"${oculus.id}","first":1}&doc_id=5373392672732392`,
headers: {
- "Accept-Language": global.locale + ",en-US;q=0.5,en;q=0.3",
+ "Accept-Language": `${global.locale},en-US;q=0.5,en;q=0.3`,
"Content-Type": "application/x-www-form-urlencoded",
Origin: "https://www.oculus.com",
},
@@ -1001,12 +1060,16 @@ async function appInfo(args) {
},
);
try {
- let json = await resp.json();
+ const json = await resp.json();
// console.log('json', json);
- if (json.error) throw json.error;
+ if (json.error) {
+ throw json.error;
+ }
const meta = json.data.node;
- if (!meta) throw "empty json.data.node";
+ if (!meta) {
+ throw "empty json.data.node";
+ }
data.name = meta.appName;
data.detailed_description =
@@ -1066,13 +1129,17 @@ async function appInfo(args) {
// console.log(jsonld);
if (jsonld) {
- if (jsonld.name) data.name = jsonld.name;
+ if (jsonld.name) {
+ data.name = jsonld.name;
+ }
data.detailed_description =
jsonld.description && jsonld.description.split("\n").join("
");
if (jsonld.image) {
for (const id in jsonld.image) {
- if (["0", "1", "2"].includes(id)) continue; // skip resizes of header
+ if (["0", "1", "2"].includes(id)) {
+ continue;
+ } // skip resizes of header
data.screenshots.push({
id,
@@ -1084,9 +1151,11 @@ async function appInfo(args) {
}
}
- if (res == "sq") {
+ if (res === "sq") {
const sq = app && app.sq;
- if (!sq || !sq.id) throw "incorrect args";
+ if (!sq || !sq.id) {
+ throw "incorrect args";
+ }
// console.log({ sq });
data.id = sq.id;
@@ -1096,7 +1165,7 @@ async function appInfo(args) {
method: "POST",
body: JSON.stringify({ apps_id: data.id }),
headers: {
- "Accept-Language": global.locale + ",en-US;q=0.5,en;q=0.3",
+ "Accept-Language": `${global.locale},en-US;q=0.5,en;q=0.3`,
"Content-Type": "application/json",
Origin: "https://sidequestvr.com",
Cookie:
@@ -1112,7 +1181,7 @@ async function appInfo(args) {
data.header_image = meta.image_url;
data.short_description = meta.summary;
data.detailed_description = meta.description.split("\n").join("
");
- if (meta.video_url)
+ if (meta.video_url) {
data.youtube = [
meta.video_url
.replace("youtube.com", "youtube.com/embed")
@@ -1120,6 +1189,7 @@ async function appInfo(args) {
.replace("/embed/embed", "/embed")
.replace("/watch?v=", "/"),
];
+ }
const resp_img = await fetchTimeout(
`https://api.sidequestvr.com/get-app-screenshots`,
@@ -1155,16 +1225,18 @@ async function appInfo(args) {
async function appInfoEvents(args) {
const { res, pkg } = args;
const app = KMETAS[pkg];
- let data = {
+ const data = {
res,
pkg,
events: [],
};
try {
- if (res == "steam") {
+ if (res === "steam") {
const steam = app && app.steam;
- if (!steam || !steam.id) throw "incorrect args";
+ if (!steam || !steam.id) {
+ throw "incorrect args";
+ }
data.url = `https://store.steampowered.com/news/app/${steam.id}/`;
@@ -1172,7 +1244,7 @@ async function appInfoEvents(args) {
`http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002?appid=${steam.id}`,
{
headers: {
- "Accept-Language": global.locale + ",en-US;q=0.5,en;q=0.3",
+ "Accept-Language": `${global.locale},en-US;q=0.5,en;q=0.3`,
},
agent: agentSteam,
},
@@ -1218,9 +1290,11 @@ async function appInfoEvents(args) {
}
}
- if (res == "oculus") {
+ if (res === "oculus") {
const oculus = app && app.oculus;
- if (!oculus || !oculus.id) throw "incorrect args";
+ if (!oculus || !oculus.id) {
+ throw "incorrect args";
+ }
// data.url = `https://store.steampowered.com/news/app/${steam.id}/`;
@@ -1230,7 +1304,7 @@ async function appInfoEvents(args) {
method: "POST",
body: `access_token=OC|1317831034909742|&variables={"id":"${oculus.id}"}&doc_id=1586217024733717`,
headers: {
- "Accept-Language": global.locale + ",en-US;q=0.5,en;q=0.3",
+ "Accept-Language": `${global.locale},en-US;q=0.5,en;q=0.3`,
"Content-Type": "application/x-www-form-urlencoded",
Origin: "https://www.oculus.com",
},
@@ -1238,8 +1312,10 @@ async function appInfoEvents(args) {
},
);
try {
- let json = await resp.json();
- if (json.error) throw json.error;
+ const json = await resp.json();
+ if (json.error) {
+ throw json.error;
+ }
// console.log({ json });
const events = json.data.node.supportedBinaries.edges;
@@ -1255,7 +1331,9 @@ async function appInfoEvents(args) {
// author: '',
};
- if (e.richChangeLog) console.log("RICHCHANGELOG", e.richChangeLog);
+ if (e.richChangeLog) {
+ console.log("RICHCHANGELOG", e.richChangeLog);
+ }
data.events.push(event);
}
@@ -1266,14 +1344,16 @@ async function appInfoEvents(args) {
resp = await fetch(
`https://computerelite.github.io/tools/Oculus/OlderAppVersions/${oculus.id}.json`,
);
- json = await resp.json();
+ const json = await resp.json();
// console.log({ json });
const events = json.data.node.binaries.edges;
for (const { node } of events) {
const e = node;
let found = false;
for (const i in data.events) {
- if (data.events[i].id != e.id) continue;
+ if (data.events[i].id !== e.id) {
+ continue;
+ }
data.events[i].date = new Date(
e.created_date * _sec,
@@ -1281,7 +1361,9 @@ async function appInfoEvents(args) {
found = true;
break;
}
- if (found) continue;
+ if (found) {
+ continue;
+ }
const event = {
id: e.id,
@@ -1296,9 +1378,11 @@ async function appInfoEvents(args) {
}
}
- if (res == "sq") {
+ if (res === "sq") {
const sq = app && app.sq;
- if (!sq || !sq.id) throw "incorrect args";
+ if (!sq || !sq.id) {
+ throw "incorrect args";
+ }
// console.log({ sq });
for (const is_news of [true, false]) {
@@ -1308,7 +1392,7 @@ async function appInfoEvents(args) {
method: "POST",
body: JSON.stringify({ apps_id: sq.id, is_news }),
headers: {
- "Accept-Language": global.locale + ",en-US;q=0.5,en;q=0.3",
+ "Accept-Language": `${global.locale},en-US;q=0.5,en;q=0.3`,
"Content-Type": "application/json",
Origin: "https://sidequestvr.com",
Cookie:
@@ -1332,7 +1416,7 @@ async function appInfoEvents(args) {
};
if (e.event_image) {
- event.contents += `