diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cb9c257..b3c7a9e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -49,12 +49,11 @@ jobs: run: | echo ${{ github.repository }} echo "overwriting names for release testing" - curl -f -L --output ./pouch/kando.webhapp /~https://github.com/holochain-apps/kando/releases/download/v0.10.9/kando.webhapp + curl -f -L --output ./pouch/ziptest.webhapp /~https://github.com/holochain-apps/ziptest/releases/download/ziptest-v0.0.9/ziptest.webhapp node ./scripts/overwrite-with-test-name.js - name: Retrieve version run: | - yarn write:configs echo "Retrieved App version: $(node -p -e "require('./package.json').version")" echo "APP_VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_OUTPUT id: version @@ -142,7 +141,6 @@ jobs: gh release upload "v${{ steps.version.outputs.APP_VERSION }}" "latest-linux.yml" --clobber gh release upload "v${{ steps.version.outputs.APP_VERSION }}" "dist/${{ steps.appId.outputs.APP_ID }}_${{ steps.version.outputs.APP_VERSION }}_amd64.deb" --clobber - # Windows #--------------------------------------------------------------------------------------- - name: build, sign and upload the app (Windows) diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..c2fd2a8 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,3 @@ +singleQuote: true +semi: true +printWidth: 100 diff --git a/README.md b/README.md index 1c72261..bfafd1b 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,19 @@ This repository let's you easily convert your Holochain app into a standalone, e **Note:** Support for non-breaking updates to happ coordinator zomes is currently not built into the kangaroo. -**Holochain Version**: Kangaroo Electron currently uses holochain 0.3.3. +# Holochain Versions + +Depending on which Holochain minor version you want to use you should use the corresponding branch of this repository. + +- Holochain 0.3.x (stable): [main-0.3](/~https://github.com/holochain/kangaroo-electron/tree/main-0.3) +- Holochain 0.4.x: [main](/~https://github.com/holochain/kangaroo-electron/tree/main) # Instructions ## Setup and Testing Locally 1. Either use this repository as a template (by clicking on the green "Use this template" button) or fork it. -Using it as a template allows you to start with a clean git history and the contributors of this repository won't show up as contributors to your new repository. **Forking has the advantage of being able to relatively easily pull in updates from this parent repository at a later point in time.** If you fork it, it may be smart to work off a different branch than the main branch in your forked repository in order to be able to keep the main branch in sync with this parent repository and selectively merge into your working branch as needed. + Using it as a template allows you to start with a clean git history and the contributors of this repository won't show up as contributors to your new repository. **Forking has the advantage of being able to relatively easily pull in updates from this parent repository at a later point in time.** If you fork it, it may be smart to work off a different branch than the main branch in your forked repository in order to be able to keep the main branch in sync with this parent repository and selectively merge into your working branch as needed. 2. In your local copy of the repository, run @@ -23,12 +28,13 @@ yarn setup 3. In the `kangaroo.config.ts` file, replace the `appId` and `productName` fields with names appropriate for your own app. -4. Choose a version number in the `version` field of `kangaroo.config.ts`. And **Read* the section [Versioning](#Versioning) below to understand the implications. +4. Choose a version number in the `version` field of `kangaroo.config.ts`. And **Read** the section [Versioning](#Versioning) below to understand the implications. -4. Paste the `.webhapp` file of your holochain app into the `pouch` folder. -**Note**: The kangaroo expects a 1024x1024 pixel `icon.png` at the root level of your webhapp's UI assets. +5. Paste the `.webhapp` file of your holochain app into the `pouch` folder. + **Note**: The kangaroo expects a 1024x1024 pixel `icon.png` at the root level of your webhapp's UI assets. + +6. To test it, run -5. To test it, run ``` yarn dev ``` @@ -36,6 +42,7 @@ yarn dev ## Build the Distributable ### Build locally + To build the app locally for your platform, run the build command for your respecive platform: ``` @@ -47,6 +54,7 @@ yarn build:mac # or yarn build:windows ``` + ### Build on CI for all platforms The general workflow goes as follows: @@ -56,6 +64,7 @@ The general workflow goes as follows: 2. Merge the main branch into the release branch and push it to github to trigger the release workflow. If you do this for the first time you will need to create the `release` branch first: + ``` git checkout -b release git merge main @@ -63,6 +72,7 @@ git push --set-upstream origin release ``` For subsequent releases after that you can run + ``` git checkout release git merge main @@ -74,17 +84,18 @@ git push By default, the kangaroo is set up to check github releases for semver compatible releases by their tag name whenever the app starts up and will prompt to install and restart if one is available. This can be disabled by setting `autoUpdates` to `false` in `kangaroo.config.ts`. > [!NOTE] -> Note that once your app is displayed, this setting can only be turned on again for newer releases and users will have to manually install new versions. +> Note that once your app is deployed, this setting can only be turned on again for newer releases and users will have to manually install new versions. ## Versioning -To allow for subsequent incompatible releases of your app (for example due to switching to a new Holochain version or introducing a breaking change in the integrity zomes of your .happ) without having to change the app's name or identifier, the kangaroo is set up such that semver incompatible versions of your app will be able to run fully independently from each other and store their data in dedicated locations on disk. +To allow for subsequent incompatible releases of your app (for example due to switching to a new Holochain version or introducing a breaking change in the integrity zomes of your .happ) without having to change the app's name or identifier, the kangaroo is set up to use semver to support incompatible versions of your app running fully independently from each other and store their data in dedicated locations on disk. Examples: -* version 0.0.2 and 0.0.3 of your app will store their data in independent locations on disk and version 0.0.3 will not have access to any data created/obtained in version 0.0.2 -* version 0.3.4 will reuse the same Holochain conductor and data as version 0.3.2 -* versions 0.3.0-alpha and 0.3.0-beta will *not* share data -* versions 0.3.0-alpha.0 and 0.3.0-alpha.1 *will* share data + +- version 0.0.2 and 0.0.3 of your app will store their data in independent locations on disk and version 0.0.3 will not have access to any data created/obtained in version 0.0.2 +- version 0.3.4 will reuse the same Holochain conductor and data as version 0.3.2 +- versions 0.3.0-alpha and 0.3.0-beta will _not_ share data +- versions 0.3.0-alpha.0 and 0.3.0-alpha.1 _will_ share data > [!NOTE] > It is your responsibility to make sure that if you mark two versions of your app as semver compatible they actually are compatible (e.g. that you don't try to run a new incompatible version of Holochain on existing databases). @@ -97,27 +108,52 @@ To use code signing on macOS for your release in CI you will have to 1. Set the `macOSCodeSigning` field to `true` in `kangaroo.config.ts` 2. Add the following secrets to your github repository with the appropriate values: + - `APPLE_DEV_IDENTITY` - `APPLE_ID_EMAIL` - `APPLE_ID_PASSWORD` - `APPLE_TEAM_ID` - ### Windows If you want to code sign your app with an EV certificate, you can follow [this guide](https://melatonin.dev/blog/how-to-code-sign-windows-installers-with-an-ev-cert-on-github-actions/) to get your EV certificate hosted on Azure Key Vault and then 1. Set the `windowsEVCodeSigning` field to `true` in `kangaroo.config.ts` 2. Add all the necessary secrets to the repository: + - `AZURE_KEY_VAULT_URI` - `AZURE_CERT_NAME` - `AZURE_TENANT_ID` - `AZURE_CLIENT_ID` - `AZURE_CLIENT_SECRET` - ## Permissions on macOS Access to things like camera and microphone on macOS require special permissions to be set in the .plist file. For this, uncomment the corresponding permissions in `./templates/electron-builder-template.yml` as needed. +## Run your App from the command line +If you want to customize some runtime parameters you can run your app via the terminal and pass additional options: + +``` +Options: + -V, --version output the version number + -p, --profile Runs Holochain Kangaroo Electron (Test) with a custom profile with its own dedicated data store. + -n, --network-seed If this is the first time running kangaroo with the given profile, this installs the happ with the + provided network seed. + --holochain-path Runs Holochain Kangaroo Electron (Test) with the holochain binary at the provided path. Use with caution + since this may potentially corrupt your databases if the binary you use is not compatible with existing + databases. + --lair-path Runs the Holochain Kangaroo Electron (Test) with the lair binary at the provided path. Use with caution + since this may potentially corrupt your databases if the binary you use is not compatible with existing + databases. + --holochain-rust-log RUST_LOG value to pass to the holochain binary + --holochain-wasm-log WASM_LOG value to pass to the holochain binary + --lair-rust-log RUST_LOG value to pass to the lair keystore binary + -b, --bootstrap-url URL of the bootstrap server to use (not persisted across restarts). + -s, --signaling-url URL of the signaling server to use (not persisted across restarts). + --ice-urls Comma separated string of ICE server URLs to use. Is ignored if an external holochain binary is being used + (not persisted across restarts). + --print-holochain-logs Print holochain logs directly to the terminal (they will be still written to the logfile as well) + -h, --help display help for command +``` diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 1e4d794..de99f0c 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -1,16 +1,16 @@ -import { defineConfig, externalizeDepsPlugin } from "electron-vite"; -import { resolve } from "path"; +import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; +import { resolve } from 'path'; export default defineConfig({ main: { - plugins: [externalizeDepsPlugin({ exclude: ["@holochain/client", "get-port", "nanoid"] })], + plugins: [externalizeDepsPlugin({ exclude: ['@holochain/client', 'get-port', 'nanoid'] })], }, preload: { build: { rollupOptions: { input: { - happ: resolve(__dirname, "src/preload/happ.ts"), - splashscreen: resolve(__dirname, "src/preload/splashscreen.ts"), + happ: resolve(__dirname, 'src/preload/happ.ts'), + splashscreen: resolve(__dirname, 'src/preload/splashscreen.ts'), }, }, }, @@ -19,9 +19,12 @@ export default defineConfig({ build: { rollupOptions: { input: { - indexNotFound: resolve(__dirname, "src/renderer/indexNotFound.html"), - indexNotFound2: resolve(__dirname, "src/renderer/indexNotFound2.html"), - splashscreen: resolve(__dirname, "src/renderer/splashscreen.html"), + indexNotFound: resolve(__dirname, 'src/renderer/indexNotFound.html'), + indexNotFound2: resolve(__dirname, 'src/renderer/indexNotFound2.html'), + loading: resolve(__dirname, 'src/renderer/loading.html'), + setupPassword: resolve(__dirname, 'src/renderer/setupPassword.html'), + setupPasswordOptional: resolve(__dirname, 'src/renderer/setupPasswordOptional.html'), + enterPassword: resolve(__dirname, 'src/renderer/enterPassword.html'), }, }, }, diff --git a/kangaroo.config.ts b/kangaroo.config.ts index 424cfcc..73a605d 100644 --- a/kangaroo.config.ts +++ b/kangaroo.config.ts @@ -1,38 +1,36 @@ -import { defineConfig } from "./src/main/defineConfig"; +import { defineConfig } from './src/main/defineConfig'; export default defineConfig({ - appId: "org.holochain.kangaroo-electron", - productName: "Holochain Kangaroo Electron", - version: "0.1.1", + appId: 'org.holochain.kangaroo-electron', + productName: 'Holochain Kangaroo Electron', + version: '0.1.0', macOSCodeSigning: false, windowsEVCodeSigning: false, fallbackToIndexHtml: true, autoUpdates: true, + systray: true, + passwordMode: 'password-optional', bins: { holochain: { - version: "0.3.3", + version: '0.4.0', sha256: { - "x86_64-unknown-linux-gnu": - "889f517e5353287e6656b9516582ae806194eddaae86e2a546afcb8007c6adc1", - "x86_64-pc-windows-msvc.exe": - "d8702733568791e4e42afaa6bb49b9a992fc0874498fe87b818c64d5e9848e6a", - "x86_64-apple-darwin": - "b5444f43056abf545176dcea724afe43790d5531e5d1ecde4425d3ecabbcb24c", - "aarch64-apple-darwin": - "93ddcc2beb19e13dd3789b322bba5dcaa9cefd52cd60f1505a764a090673c993", + 'x86_64-unknown-linux-gnu': + 'f2e5d5c5c90a0c5eb85641bd8ab207396ddbbc304aaa51982dfde24037a0d0a9', + 'x86_64-pc-windows-msvc.exe': + '9b893527d1f4c1e69fa3c9fbd605987065087ca15ebc72bbe598edef9ab5b27a', + 'x86_64-apple-darwin': '9d8e4996dd86441ec859faf9fa90c31bdfaddd49d5079bf67492d9c93d17c503', + 'aarch64-apple-darwin': '1d206d09ddf90c85b781457514df3c87d8c7e38a6dab4e53f01a8f089acfa3d1', }, }, lair: { - version: "0.4.5", + version: '0.5.3', sha256: { - "x86_64-unknown-linux-gnu": - "67b5a8d06575fc14c6295fec05cd2dcd338de76a051ceac6dd7b03e921ee1762", - "x86_64-pc-windows-msvc.exe": - "77cb4e51a9816048520a30293760214483a0a372ab554ad496955167e6009c99", - "x86_64-apple-darwin": - "60c81104bbaa37e69749a7f53b079d06414d07418a1514a3c676503ce2861c4a", - "aarch64-apple-darwin": - "f6e427557271d13ab32bdd8672f0408b8b248117840adc64c858d55a6ae56583", + 'x86_64-unknown-linux-gnu': + '96a28b9b37c73ef46d8b5c56b9d799d558fd2fe77b41c577e2bcb37685a46396', + 'x86_64-pc-windows-msvc.exe': + '68b6453a19921072aac04dae52a4e94e725e7482005d2f54f907aec680e078de', + 'x86_64-apple-darwin': 'a53bfb8e501431870b99243cbac24f6103d67f8be094930f174829bb249f34c4', + 'aarch64-apple-darwin': '6b15d977408847ac977c2e060c7aab84a69e6e90c79390098dd40a6b75256e50', }, }, }, diff --git a/package.json b/package.json index bc01582..47d1e77 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "org.holochain.kangaroo-electron2", + "name": "org.holochain.kangaroo-electron", "version": "0.1.0", "license": "CAL-1.0", "main": "./out/main/index.js", @@ -23,13 +23,14 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^3.0.0", - "@holochain/client": "0.17.1", - "@holochain/hc-spin-rust-utils": "0.300.1", - "@lightningrodlabs/we-rust-utils": "0.300.2", + "@holochain/client": "0.18.0-rc.1", + "@holochain/hc-spin-rust-utils": "0.400.0", + "@lightningrodlabs/we-rust-utils": "0.400.1", "@matthme/electron-updater": "6.3.0-alpha.1", "@msgpack/msgpack": "^2.8.0", "adm-zip": "0.5.14", "bufferutil": "4.0.8", + "commander": "12.1.0", "electron-context-menu": "3.6.1", "get-port": "7.0.0", "nanoid": "5.0.4", @@ -49,6 +50,7 @@ "electron-vite": "^2.3.0", "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.0", + "jimp": "^1.6.0", "js-yaml": "4.1.0", "make-dir-cli": "^3.1.0", "ncp": "^2.0.0", diff --git a/scripts/create-icons.js b/scripts/create-icons.js index f26c9eb..4d6430e 100644 --- a/scripts/create-icons.js +++ b/scripts/create-icons.js @@ -1,25 +1,25 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const fs = require("fs"); -const path = require("path"); -const png2icons = require("png2icons"); +const fs = require('fs'); +const path = require('path'); +const png2icons = require('png2icons'); +const jimp = require('jimp'); generateIcons(); -function generateIcons() { - const uiDir = path.join("resources", "ui"); +async function generateIcons() { + const uiDir = path.join('resources', 'ui'); const buildDir = 'build'; - const pngPath = path.join(uiDir, "icon.png"); - const icoPath = path.join(uiDir, "icon.ico"); - const icnsPath = path.join(uiDir, "icon.icns"); + const pngPath = path.join(uiDir, 'icon.png'); + const icoPath = path.join(uiDir, 'icon.ico'); + const icnsPath = path.join(uiDir, 'icon.icns'); - - const pngOutPath = path.join(buildDir, "icon.png"); - const icoOutPath = path.join(buildDir, "icon.ico"); - const icnsOutPath = path.join(buildDir, "icon.icns"); + const pngOutPath = path.join(buildDir, 'icon.png'); + const icoOutPath = path.join(buildDir, 'icon.ico'); + const icnsOutPath = path.join(buildDir, 'icon.icns'); if (!fs.existsSync(pngPath)) { - console.warn("WARNING: No icon.png found in your webhapp's UI assets."); + console.warn("WARNING: No icon.png found. If you're using the systray option, an icon.png (256x256 pixel) is required to be provided at the root level of your webhapp's UI assets."); return; } @@ -28,20 +28,34 @@ function generateIcons() { const pngBuffer = fs.readFileSync(pngPath); if (!fs.existsSync(icoPath)) { - console.log("Generating icon.ico"); - const icoIcon = png2icons.createICO( - pngBuffer, - png2icons.BICUBIC2, - 0, - false, - true - ); + console.log('Generating icon.ico'); + const icoIcon = png2icons.createICO(pngBuffer, png2icons.BICUBIC2, 0, false, true); fs.writeFileSync(icoOutPath, icoIcon); + } else { + fs.cpSync(icoPath, icoOutPath); } if (!fs.existsSync(icnsPath)) { - console.log("Generating icon.icns"); + console.log('Generating icon.icns'); const icnsIcon = png2icons.createICNS(pngBuffer, png2icons.BILINEAR, 0); fs.writeFileSync(icnsOutPath, icnsIcon); + } else { + fs.cpSync(icnsPath, icnsOutPath); + } + + // Generate the systray icon + console.log('Generating systray icon'); + const systrayIcon = await jimp.Jimp.fromBuffer(pngBuffer); + systrayIcon.resize({ w: 64, h: 64}); + const iconsDir = path.join('resources', 'icons'); + if (!fs.existsSync(iconsDir)) { + fs.mkdirSync(iconsDir); } + systrayIcon.write(path.join(iconsDir, '32x32@2.png')); + + // Generate the icon for OS notifications + console.log('Generating notifications icon'); + const icon128x128 = await jimp.Jimp.fromBuffer(pngBuffer); + icon128x128.resize({ w: 128, h: 128}); + icon128x128.write(path.join(iconsDir, '128x128.png')); } diff --git a/scripts/extend-deb-postinst.mjs b/scripts/extend-deb-postinst.mjs index 673baf6..e37ad98 100644 --- a/scripts/extend-deb-postinst.mjs +++ b/scripts/extend-deb-postinst.mjs @@ -73,7 +73,7 @@ profile ${appId} \\"/opt/${productName}/${appId}\\" flags=(unconfined) { fi # SUID chrome-sandbox for Electron 5+ -`, +` ); fs.writeFileSync(posinstPath, postinstScriptModified); diff --git a/scripts/fetch-binaries.js b/scripts/fetch-binaries.js index 1c67a58..efb7730 100644 --- a/scripts/fetch-binaries.js +++ b/scripts/fetch-binaries.js @@ -36,12 +36,11 @@ switch (process.platform) { throw new Error(`Got unexpected OS platform: ${process.platform}`); } -const binariesAppendix = kangarooConfig.appId.slice(0,10).replace(' ', '-'); +const binariesAppendix = kangarooConfig.appId.slice(0, 10).replace(' ', '-'); - -const holochainBinaryFilename = `holochain-v${kangarooConfig.bins.holochain.version}-${binariesAppendix}${ - process.platform === 'win32' ? '.exe' : '' -}`; +const holochainBinaryFilename = `holochain-v${ + kangarooConfig.bins.holochain.version +}-${binariesAppendix}${process.platform === 'win32' ? '.exe' : ''}`; const lairBinaryFilename = `lair-keystore-v${kangarooConfig.bins.lair.version}-${binariesAppendix}${ process.platform === 'win32' ? '.exe' : '' @@ -62,7 +61,7 @@ function downloadFile(url, targetPath, expectedSha256Hex, chmod = false) { const sha256Hex = hasher.digest('hex'); if (sha256Hex !== expectedSha256Hex) throw new Error( - `sha256 does not match the expected sha256. Got ${sha256Hex} but expected ${expectedSha256Hex}`, + `sha256 does not match the expected sha256. Got ${sha256Hex} but expected ${expectedSha256Hex}` ); console.log('Download successful. sha256 of file (hex): ', sha256Hex); @@ -82,7 +81,7 @@ function downloadHolochainBinary() { holochainBinaryUrl, destinationPath, kangarooConfig.bins.holochain.sha256[targetEnding], - true, + true ); } diff --git a/scripts/overwrite-with-test-name.js b/scripts/overwrite-with-test-name.js index 390eb5c..0313d0d 100644 --- a/scripts/overwrite-with-test-name.js +++ b/scripts/overwrite-with-test-name.js @@ -4,18 +4,18 @@ * releases in the official kangaroo repo */ -const path = require("path"); -const fs = require("fs"); +const path = require('path'); +const fs = require('fs'); -let kangarooConfigString = fs.readFileSync("kangaroo.config.ts", "utf-8"); +let kangarooConfigString = fs.readFileSync('kangaroo.config.ts', 'utf-8'); kangarooConfigString = kangarooConfigString.replace( - "org.holochain.kangaroo-electron", - "org.holochain.kangaroo-electron-test" + 'org.holochain.kangaroo-electron', + 'org.holochain.kangaroo-electron-test' ); kangarooConfigString = kangarooConfigString.replace( - "Holochain Kangaroo Electron", - "Holochain Kangaroo Electron (Test)" + 'Holochain Kangaroo Electron', + 'Holochain Kangaroo Electron (Test)' ); fs.writeFileSync('kangaroo.config.ts', kangarooConfigString, 'utf-8'); diff --git a/scripts/read-app-id.js b/scripts/read-app-id.js index 05ba220..96ff7c7 100644 --- a/scripts/read-app-id.js +++ b/scripts/read-app-id.js @@ -6,4 +6,4 @@ tsNode.register(); const kangarooConfig = require(path.join(process.cwd(), 'kangaroo.config.ts')).default; -console.log(kangarooConfig.appId); \ No newline at end of file +console.log(kangarooConfig.appId); diff --git a/scripts/read-macos-code-signing.js b/scripts/read-macos-code-signing.js index 12df872..08e370e 100644 --- a/scripts/read-macos-code-signing.js +++ b/scripts/read-macos-code-signing.js @@ -6,4 +6,4 @@ tsNode.register(); const kangarooConfig = require(path.join(process.cwd(), 'kangaroo.config.ts')).default; -console.log(kangarooConfig.macOSCodeSigning); \ No newline at end of file +console.log(kangarooConfig.macOSCodeSigning); diff --git a/scripts/read-windows-code-signing.js b/scripts/read-windows-code-signing.js index 54c52ca..b6536fb 100644 --- a/scripts/read-windows-code-signing.js +++ b/scripts/read-windows-code-signing.js @@ -6,4 +6,4 @@ tsNode.register(); const kangarooConfig = require(path.join(process.cwd(), 'kangaroo.config.ts')).default; -console.log(kangarooConfig.windowsEVCodeSigning); \ No newline at end of file +console.log(kangarooConfig.windowsEVCodeSigning); diff --git a/scripts/unpack-pouch.js b/scripts/unpack-pouch.js index 919b8a7..f3df470 100644 --- a/scripts/unpack-pouch.js +++ b/scripts/unpack-pouch.js @@ -1,25 +1,19 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const rustUtils = require("@holochain/hc-spin-rust-utils"); -const path = require("path"); -const fs = require("fs"); +const rustUtils = require('@holochain/hc-spin-rust-utils'); +const path = require('path'); +const fs = require('fs'); -const webhappDir = fs.readdirSync(path.join(process.cwd(), "pouch")); -const webhappFilename = webhappDir.find((file) => file.endsWith(".webhapp")); -if (!webhappFilename) throw new Error("No webhapp file found in pouch folder."); -const webhappPath = path.join(process.cwd(), "pouch", webhappFilename); +const webhappDir = fs.readdirSync(path.join(process.cwd(), 'pouch')); +const webhappFilename = webhappDir.find((file) => file.endsWith('.webhapp')); +if (!webhappFilename) throw new Error('No webhapp file found in pouch folder.'); +const webhappPath = path.join(process.cwd(), 'pouch', webhappFilename); -const resourcesDir = path.join(process.cwd(), "resources"); -const uiDir = path.join(resourcesDir, "ui"); +const resourcesDir = path.join(process.cwd(), 'resources'); +const uiDir = path.join(resourcesDir, 'ui'); // remove existing UI directory if (fs.existsSync(uiDir)) { - fs.rmSync(uiDir, { recursive: true}); + fs.rmSync(uiDir, { recursive: true }); } fs.mkdirSync(uiDir, { recursive: true }); - -rustUtils.saveHappOrWebhapp( - webhappPath, - 'kangaroo', - uiDir, - resourcesDir -); +rustUtils.saveHappOrWebhapp(webhappPath, 'kangaroo', uiDir, resourcesDir); diff --git a/scripts/write-builder-config.js b/scripts/write-builder-config.js index 10252dd..796db09 100644 --- a/scripts/write-builder-config.js +++ b/scripts/write-builder-config.js @@ -1,23 +1,20 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const jsYaml = require("js-yaml"); -const fs = require("fs"); -const path = require("path"); -const tsNode = require("ts-node"); +const jsYaml = require('js-yaml'); +const fs = require('fs'); +const path = require('path'); +const tsNode = require('ts-node'); tsNode.register(); -const kangarooConfig = require(path.join( - process.cwd(), - "kangaroo.config.ts" -)).default; +const kangarooConfig = require(path.join(process.cwd(), 'kangaroo.config.ts')).default; if (!process.env.KANGAROO_DEV) { // CHECK THAT NO DEFAULT VALUES ANYMORE - if (kangarooConfig.appId === "org.holochain.kangaroo-electron") + if (kangarooConfig.appId === 'org.holochain.kangaroo-electron') throw new Error( "The appId field in 'kangaroo.config.ts' is still using the default value. Change it to the appId of your app." ); - if (kangarooConfig.productName === "Holochain Kangaroo Electron") + if (kangarooConfig.productName === 'Holochain Kangaroo Electron') throw new Error( "The productName field in 'kangaroo.config.ts' is still using the default value. Change it to the productName of your app." ); @@ -25,35 +22,25 @@ if (!process.env.KANGAROO_DEV) { // Store config to json file fs.writeFileSync( - path.join("resources", "kangaroo.config.json"), + path.join('resources', 'kangaroo.config.json'), JSON.stringify(kangarooConfig, undefined, 2), - "utf-8" + 'utf-8' ); // Overwrite package.json values -const packageJsonString = fs.readFileSync("package.json", "utf-8"); +const packageJsonString = fs.readFileSync('package.json', 'utf-8'); const packageJSON = JSON.parse(packageJsonString); packageJSON.name = kangarooConfig.appId; packageJSON.version = kangarooConfig.version; -fs.writeFileSync( - "package.json", - JSON.stringify(packageJSON, undefined, 2), - "utf-8" -); +fs.writeFileSync('package.json', JSON.stringify(packageJSON, undefined, 2), 'utf-8'); const eletronBuilderYml = jsYaml.load( - fs.readFileSync( - path.join(process.cwd(), "templates", "electron-builder-template.yml") - ) + fs.readFileSync(path.join(process.cwd(), 'templates', 'electron-builder-template.yml')) ); eletronBuilderYml.appId = kangarooConfig.appId; eletronBuilderYml.productName = kangarooConfig.productName; eletronBuilderYml.win.executableName = kangarooConfig.appId; -fs.writeFileSync( - "electron-builder.yml", - jsYaml.dump(eletronBuilderYml), - "utf-8" -); +fs.writeFileSync('electron-builder.yml', jsYaml.dump(eletronBuilderYml), 'utf-8'); diff --git a/src/main/cli.ts b/src/main/cli.ts new file mode 100644 index 0000000..e901de0 --- /dev/null +++ b/src/main/cli.ts @@ -0,0 +1,99 @@ +import { KANGAROO_CONFIG } from './const'; +import { breakingAppVersion } from './filesystem'; +import { app } from 'electron'; + +export interface CliOpts { + profile?: string; + networkSeed?: string; + holochainPath?: string; + lairPath?: string; + holochainRustLog?: string; + holochainWasmLog?: string; + lairRustLog?: string; + bootstrapUrl?: string; + signalingUrl?: string; + iceUrls?: string; + printHolochainLogs?: boolean; +} + +export interface RunOptions { + profile: string | undefined; + networkSeed: string; + bootstrapUrl: URL | undefined; + signalingUrl: URL | undefined; + iceUrls: string[] | undefined; + holochainPath: string | undefined; + lairPath: string | undefined; + holochainRustLog: string | undefined; + holochainWasmLog: string | undefined; + lairRustLog: string | undefined; + printHolochainLogs: boolean; +} + +export function validateArgs(args: CliOpts): RunOptions { + // validate --profile argument + const allowedProfilePattern = /^[0-9a-zA-Z-]+$/; + if (args.profile && !allowedProfilePattern.test(args.profile)) { + throw new Error( + `The --profile argument may only contain digits (0-9), letters (a-z,A-Z) and dashes (-) but got '${args.profile}'` + ); + } + if (args.networkSeed && typeof args.networkSeed !== 'string') { + throw new Error('The --network-seed argument must be of type string.'); + } + if (args.bootstrapUrl && typeof args.bootstrapUrl !== 'string') { + throw new Error('The --bootstrap-url argument must be of type string.'); + } + if (args.signalingUrl && typeof args.signalingUrl !== 'string') { + throw new Error('The --signaling-url argument must be of type string.'); + } + console.log('ICE URLS arg: ', args.iceUrls); + console.log('ICE URLS arg type: ', typeof args.iceUrls); + if (args.iceUrls && typeof args.iceUrls !== 'string') { + throw new Error('The --ice-urls argument must be of type string.'); + } + if (args.holochainPath && typeof args.holochainPath !== 'string') { + throw new Error('The --holochain-path argument must be of type string.'); + } + if (args.lairPath && typeof args.lairPath !== 'string') { + throw new Error('The --lair-path argument must be of type string.'); + } + if (args.holochainRustLog && typeof args.holochainRustLog !== 'string') { + throw new Error('The --holochain-rust-log argument must be of type string.'); + } + if (args.holochainWasmLog && typeof args.holochainWasmLog !== 'string') { + throw new Error('The --holochain-wasm-log argument must be of type string.'); + } + if (args.lairRustLog && typeof args.lairRustLog !== 'string') { + throw new Error('The --lair-rust-log argument must be of type string.'); + } + + const profile = args.profile ? args.profile : undefined; + // If provided take the one provided, otherwise check whether it's applet dev mode + const networkSeed = args.networkSeed ? args.networkSeed : defaultAppNetworkSeed(); + + const bootstrapUrl = args.bootstrapUrl ? new URL(args.bootstrapUrl) : undefined; + const signalingUrl = args.signalingUrl ? new URL(args.signalingUrl) : undefined; + + return { + profile, + networkSeed, + bootstrapUrl, + signalingUrl, + iceUrls: args.iceUrls ? args.iceUrls.split(',') : undefined, + holochainPath: args.holochainPath ? args.holochainPath : undefined, + lairPath: args.lairPath ? args.lairPath : undefined, + holochainRustLog: args.holochainRustLog ? args.holochainRustLog : undefined, + holochainWasmLog: args.holochainWasmLog ? args.holochainWasmLog : undefined, + lairRustLog: args.lairRustLog ? args.lairRustLog : undefined, + printHolochainLogs: args.printHolochainLogs ? true : false, + }; +} + +function defaultAppNetworkSeed() { + let networkSeed = `${KANGAROO_CONFIG.productName}-${breakingAppVersion()}`; + if (!app.isPackaged) { + networkSeed += '-dev'; + } + return networkSeed; +} diff --git a/src/main/const.ts b/src/main/const.ts index a93df08..22424fd 100644 --- a/src/main/const.ts +++ b/src/main/const.ts @@ -1,32 +1,37 @@ -import { app } from "electron"; +import { app } from 'electron'; import path from 'path'; import fs from 'fs'; import { KangarooConfig } from './types'; - const RESOURCES_DIRECTORY = app.isPackaged ? path.join(app.getAppPath(), '../app.asar.unpacked/resources') : path.join(app.getAppPath(), './resources'); -const kangarooConfigString = fs.readFileSync(path.join(RESOURCES_DIRECTORY, 'kangaroo.config.json'), 'utf-8'); +const kangarooConfigString = fs.readFileSync( + path.join(RESOURCES_DIRECTORY, 'kangaroo.config.json'), + 'utf-8' +); export const KANGAROO_CONFIG: KangarooConfig = JSON.parse(kangarooConfigString); export const DEFAULT_BOOTSTRAP_SERVER = 'https://bootstrap.holo.host'; -export const DEFAULT_SIGNALING_SERVER = 'wss://signal.holo.host'; - -const binariesAppendix = KANGAROO_CONFIG.appId.slice(0,10).replace(' ', '-'); +export const DEFAULT_SIGNALING_SERVER = 'wss://sbd.holo.host'; +const binariesAppendix = KANGAROO_CONFIG.appId.slice(0, 10).replace(' ', '-'); const BINARIES_DIRECTORY = path.join(RESOURCES_DIRECTORY, 'bins'); export const HOLOCHAIN_BINARY = path.join( BINARIES_DIRECTORY, - `holochain-v${KANGAROO_CONFIG.bins.holochain.version}-${binariesAppendix}${process.platform === 'win32' ? '.exe' : ''}`, + `holochain-v${KANGAROO_CONFIG.bins.holochain.version}-${binariesAppendix}${ + process.platform === 'win32' ? '.exe' : '' + }` ); export const LAIR_BINARY = path.join( BINARIES_DIRECTORY, - `lair-keystore-v${KANGAROO_CONFIG.bins.lair.version}-${binariesAppendix}${process.platform === 'win32' ? '.exe' : ''}`, + `lair-keystore-v${KANGAROO_CONFIG.bins.lair.version}-${binariesAppendix}${ + process.platform === 'win32' ? '.exe' : '' + }` ); export const HAPP_PATH = path.join(RESOURCES_DIRECTORY, 'kangaroo.happ'); @@ -36,6 +41,9 @@ export const UI_DIRECTORY = path.join(RESOURCES_DIRECTORY, 'ui'); export const ICON_PATH = path.join(RESOURCES_DIRECTORY, 'ui', 'icon.png'); +export const SYSTRAY_ICON_PATH = path.join(RESOURCES_DIRECTORY, 'icons', '32x32@2.png'); +export const NOTIFICATIONS_ICON_PATH = path.join(RESOURCES_DIRECTORY, 'icons', '128x128.png'); + export const isMac = process.platform === 'darwin'; export const isWindows = process.platform === 'win32'; -export const isLinux = process.platform === 'linux'; \ No newline at end of file +export const isLinux = process.platform === 'linux'; diff --git a/src/main/defineConfig.ts b/src/main/defineConfig.ts index 5388bd7..1789a5b 100644 --- a/src/main/defineConfig.ts +++ b/src/main/defineConfig.ts @@ -1,4 +1,4 @@ -import { KangarooConfig } from "./types"; +import { KangarooConfig } from './types'; export function defineConfig(config: KangarooConfig) { return config; diff --git a/src/main/eventEmitter.ts b/src/main/eventEmitter.ts index d8dc303..504d504 100644 --- a/src/main/eventEmitter.ts +++ b/src/main/eventEmitter.ts @@ -41,7 +41,7 @@ export declare interface WeEmitter { | WASM_LOG | HAPP_INSTALLED | string, // arbitrary string, e.g. a one-time event with a unique id - listener: (event: HolochainData | string | Error) => void, + listener: (event: HolochainData | string | Error) => void ): this; } diff --git a/src/main/filesystem.ts b/src/main/filesystem.ts index 7c230e5..ffcb558 100644 --- a/src/main/filesystem.ts +++ b/src/main/filesystem.ts @@ -1,11 +1,11 @@ import path from 'path'; import fs from 'fs'; import semver from 'semver'; +import { nanoid } from 'nanoid'; import { app } from 'electron'; export type Profile = string; - export class KangarooFileSystem { public appDataDir: string; public appConfigDir: string; @@ -55,7 +55,10 @@ export class KangarooFileSystem { createDirIfNotExists(configDir); createDirIfNotExists(dataDir); - console.log('Got logsDir, configDir and dataDir: ', logsDir, configDir, dataDir); + console.log('userData directory (the one to be deleted for a factory reset): ', app.getPath('userData')); + console.log('dataDir: ', dataDir); + console.log('logsDir:', logsDir); + console.log('configDir: ', configDir); const kangarooFs = new KangarooFileSystem(dataDir, configDir, logsDir); @@ -69,6 +72,20 @@ export class KangarooFileSystem { keystoreInitialized = () => { return fs.existsSync(path.join(this.keystoreDir, 'lair-keystore-config.yaml')); }; + + readOrCreatePassword() { + const pwPath = path.join(this.appDataDir, '.pw'); + if (!fs.existsSync(pwPath)) { + const pw = nanoid(); + fs.writeFileSync(pwPath, pw, 'utf-8'); + } + return fs.readFileSync(pwPath, 'utf-8'); + } + + randomPasswordExists() { + const pwPath = path.join(this.appDataDir, '.pw'); + return fs.existsSync(pwPath); + } } function createDirIfNotExists(path: fs.PathLike) { @@ -88,7 +105,9 @@ export function breakingVersion(version: string): string { } const prerelease = semver.prerelease(version); if (prerelease) { - return `${semver.major(version)}.${semver.minor(version)}.${semver.patch(version)}-${prerelease[0]}`; + return `${semver.major(version)}.${semver.minor(version)}.${semver.patch(version)}-${ + prerelease[0] + }`; } switch (semver.major(version)) { case 0: diff --git a/src/main/holochainManager.ts b/src/main/holochainManager.ts index 5451c24..b22c28d 100644 --- a/src/main/holochainManager.ts +++ b/src/main/holochainManager.ts @@ -1,18 +1,15 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -import getPort from "get-port"; -import fs from "fs"; -import * as childProcess from "child_process"; -import { HolochainVersion, KangarooEmitter } from "./eventEmitter"; -import split from "split"; -import { - AdminWebsocket, - AppAuthenticationToken, - AppInfo, -} from "@holochain/client"; -import { breakingAppVersion, KangarooFileSystem } from "./filesystem"; -import { HAPP_APP_ID, HAPP_PATH, KANGAROO_CONFIG } from "./const"; +import getPort from 'get-port'; +import fs from 'fs'; +import * as childProcess from 'child_process'; +import { HolochainVersion, KangarooEmitter } from './eventEmitter'; +import split from 'split'; +import { AdminWebsocket, AppAuthenticationToken, AppInfo } from '@holochain/client'; +import { KangarooFileSystem } from './filesystem'; +import { HAPP_APP_ID, HAPP_PATH } from './const'; -const rustUtils = require("@lightningrodlabs/we-rust-utils"); +import { defaultConductorConfig } from '@lightningrodlabs/we-rust-utils'; +import { app } from 'electron'; export type AdminPort = number; export type AppPort = number; @@ -31,7 +28,7 @@ export class HolochainManager { constructor( processHandle: childProcess.ChildProcessWithoutNullStreams, kangarooEmitter: KangarooEmitter, - mossFileSystem: KangarooFileSystem, + kangarooFileSystem: KangarooFileSystem, adminPort: AdminPort, appPort: AppPort, adminWebsocket: AdminWebsocket, @@ -43,7 +40,7 @@ export class HolochainManager { this.adminPort = adminPort; this.appPort = appPort; this.adminWebsocket = adminWebsocket; - this.fs = mossFileSystem; + this.fs = kangarooFileSystem; this.installedApps = installedApps; this.version = version; } @@ -59,6 +56,7 @@ export class HolochainManager { lairUrl: string, bootstrapUrl: string, signalingUrl: string, + iceUrls?: string[], rustLog?: string, wasmLog?: string ): Promise { @@ -66,47 +64,46 @@ export class HolochainManager { ? parseInt(process.env.ADMIN_PORT, 10) : await getPort(); - const conductorConfig = rustUtils.defaultConductorConfig( + const conductorConfig = defaultConductorConfig( adminPort, rootDir, lairUrl, bootstrapUrl, signalingUrl, - "*" + 'kangaroo', + false, + iceUrls, + undefined ); - console.log("Writing conductor-config.yaml..."); + console.log('Writing conductor-config.yaml...'); fs.writeFileSync(configPath, conductorConfig); - const conductorHandle = childProcess.spawn( - binary, - ["-c", configPath, "-p"], - { - env: { - RUST_LOG: rustLog - ? rustLog - : "warn," + - // this thrashes on startup - "wasmer_compiler_cranelift=error," + - // this gives a bunch of warnings about how long db accesses are taking, tmi - "holochain_sqlite::db::access=error," + - // this gives a lot of "search_and_discover_peer_connect: no peers found, retrying after delay" messages on INFO - "kitsune_p2p::spawn::actor::discover=error", - WASM_LOG: wasmLog ? wasmLog : "warn", - NO_COLOR: "1", - }, - } - ); + const conductorHandle = childProcess.spawn(binary, ['-c', configPath, '-p'], { + env: { + RUST_LOG: rustLog + ? rustLog + : 'warn,' + + // this thrashes on startup + 'wasmer_compiler_cranelift=error,' + + // this gives a bunch of warnings about how long db accesses are taking, tmi + 'holochain_sqlite::db::access=error,' + + // this gives a lot of "search_and_discover_peer_connect: no peers found, retrying after delay" messages on INFO + 'kitsune_p2p::spawn::actor::discover=error', + WASM_LOG: wasmLog ? wasmLog : 'warn', + NO_COLOR: '1', + }, + }); conductorHandle.stdin.write(password); conductorHandle.stdin.end(); - conductorHandle.stdout.pipe(split()).on("data", async (line: string) => { + conductorHandle.stdout.pipe(split()).on('data', async (line: string) => { kangarooEmitter.emitHolochainLog({ version, data: line, }); }); - conductorHandle.stderr.pipe(split()).on("data", (line: string) => { + conductorHandle.stderr.pipe(split()).on('data', (line: string) => { kangarooEmitter.emitHolochainError({ version, data: line, @@ -114,44 +111,38 @@ export class HolochainManager { }); return new Promise((resolve, reject) => { - conductorHandle.stderr.pipe(split()).on("data", async (line: string) => { - if (line.includes("holochain had a problem and crashed")) { + conductorHandle.stderr.pipe(split()).on('data', async (line: string) => { + if (line.includes('holochain had a problem and crashed')) { reject( `Holochain failed to start up and crashed. Check the logs for details (Help > Open Logs).` ); } }); - conductorHandle.stdout.pipe(split()).on("data", async (line: string) => { - if ( - line.includes("could not be parsed, because it is not valid YAML") - ) { + conductorHandle.stdout.pipe(split()).on('data', async (line: string) => { + if (line.includes('could not be parsed, because it is not valid YAML')) { reject( `Holochain failed to start up and crashed. Check the logs for details (Help > Open Logs).` ); } - if (line.includes("Conductor ready.")) { + if (line.includes('Conductor ready.')) { const adminWebsocket = await AdminWebsocket.connect({ url: new URL(`ws://127.0.0.1:${adminPort}`), wsClientOptions: { - origin: "moss-admin-main", + origin: 'kangaroo', }, }); - console.log("Connected to admin websocket."); + console.log('Connected to admin websocket.'); const installedApps = await adminWebsocket.listApps({}); const appInterfaces = await adminWebsocket.listAppInterfaces(); - console.log("Got appInterfaces: ", appInterfaces); + console.log('Got appInterfaces: ', appInterfaces); let appPort; if (appInterfaces.length > 0) { appPort = appInterfaces[0].port; } else { - const attachAppInterfaceResponse = - await adminWebsocket.attachAppInterface({ - allowed_origins: "*", - }); - console.log( - "Attached app interface port: ", - attachAppInterfaceResponse - ); + const attachAppInterfaceResponse = await adminWebsocket.attachAppInterface({ + allowed_origins: app.isPackaged ? 'webhapp://webhappwindow' : '*', + }); + console.log('Attached app interface port: ', attachAppInterfaceResponse); appPort = attachAppInterfaceResponse.port; } resolve( @@ -171,38 +162,29 @@ export class HolochainManager { }); } - async installHappIfNecessary(networkSeed?: string) { + async installHappIfNecessary(networkSeed: string) { const installedApps = await this.adminWebsocket.listApps({}); - if ( - installedApps - .map((appInfo) => appInfo.installed_app_id) - .includes(HAPP_APP_ID) - ) - return; - if (!networkSeed) { - networkSeed = `${KANGAROO_CONFIG.productName}-${breakingAppVersion()}`; - } + if (installedApps.map((appInfo) => appInfo.installed_app_id).includes(HAPP_APP_ID)) return; console.log(`Installing happ...`); const pubKey = await this.adminWebsocket.generateAgentPubKey(); const appInfo = await this.adminWebsocket.installApp({ agent_key: pubKey, installed_app_id: HAPP_APP_ID, - membrane_proofs: {}, path: HAPP_PATH, network_seed: networkSeed, }); - try { - await this.adminWebsocket.enableApp({ - installed_app_id: appInfo.installed_app_id, - }); - const installedApps = await this.adminWebsocket.listApps({}); - this.installedApps = installedApps; - this.kangarooEmitter.emitHappInstalled(); - } catch (e) { - throw new Error( - `Failed to enable appstore: ${e}.\nIf you encounter this in dev mode your local bootstrap server may not be running or at a different port than the one specified.` - ); + if (appInfo.status !== 'awaiting_memproofs') { + try { + await this.adminWebsocket.enableApp({ + installed_app_id: appInfo.installed_app_id, + }); + } catch (e) { + throw new Error(`Failed to enable happ: ${e}.`); + } } + const installedAppsNew = await this.adminWebsocket.listApps({}); + this.installedApps = installedAppsNew; + this.kangarooEmitter.emitHappInstalled(); } async getAppToken(): Promise { diff --git a/src/main/index.ts b/src/main/index.ts index fe65976..1d72ef7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,48 +5,88 @@ import { ipcMain, IpcMainInvokeEvent, Menu, + nativeImage, protocol, -} from "electron"; -import childProcess from "child_process"; -import { - ZomeCallNapi, - ZomeCallSigner, - ZomeCallUnsignedNapi, -} from "@holochain/hc-spin-rust-utils"; -import { autoUpdater, UpdateCheckResult } from "@matthme/electron-updater"; -import contextMenu from "electron-context-menu"; -import { encode } from "@msgpack/msgpack"; + Tray, + Notification, + Event +} from 'electron'; +import childProcess from 'child_process'; +import { ZomeCallNapi, ZomeCallSigner, ZomeCallUnsignedNapi } from '@holochain/hc-spin-rust-utils'; +import contextMenu from 'electron-context-menu'; +import { encode } from '@msgpack/msgpack'; import { CallZomeRequest, CallZomeRequestSigned, getNonceExpiration, randomNonce, -} from "@holochain/client"; -import { breakingVersion, KangarooFileSystem } from "./filesystem"; -import { KangarooEmitter } from "./eventEmitter"; -import { setupLogs } from "./logs"; -import { HolochainManager } from "./holochainManager"; -import { createHappWindow, createSplashWindow } from "./windows"; -import { - DEFAULT_BOOTSTRAP_SERVER, - DEFAULT_SIGNALING_SERVER, - HAPP_APP_ID, - HOLOCHAIN_BINARY, - KANGAROO_CONFIG, - LAIR_BINARY, - UI_DIRECTORY, -} from "./const"; -import { initializeLairKeystore, launchLairKeystore } from "./lairKeystore"; -import { kangarooMenu } from "./menu"; -import semver from "semver"; +} from '@holochain/client'; +import { Command } from 'commander'; +import semver from 'semver'; + +import { breakingVersion, KangarooFileSystem } from './filesystem'; +import { KangarooEmitter } from './eventEmitter'; +import { setupLogs } from './logs'; +import { HolochainManager } from './holochainManager'; +import { createSplashWindow } from './windows'; +import { KANGAROO_CONFIG, NOTIFICATIONS_ICON_PATH, SYSTRAY_ICON_PATH } from './const'; +import { kangarooMenu } from './menu'; +import { validateArgs } from './cli'; +import { autoUpdater, UpdateCheckResult } from '@matthme/electron-updater'; +import { launch } from './launch'; +import { PasswordType, SplashScreenType } from './types'; // Read CLI options +const kangarooCli = new Command(); + +kangarooCli + .name(KANGAROO_CONFIG.productName) + .description(`Run ${KANGAROO_CONFIG.productName} via the command line`) + .version(KANGAROO_CONFIG.version) + .option( + '-p, --profile ', + `Runs ${KANGAROO_CONFIG.productName} with a custom profile with its own dedicated data store.` + ) + .option( + '-n, --network-seed ', + 'If this is the first time running kangaroo with the given profile, this installs the happ with the provided network seed.' + ) + .option( + '--holochain-path ', + `Runs ${KANGAROO_CONFIG.productName} with the holochain binary at the provided path. Use with caution since this may potentially corrupt your databases if the binary you use is not compatible with existing databases.` + ) + .option( + '--lair-path ', + `Runs the ${KANGAROO_CONFIG.productName} with the lair binary at the provided path. Use with caution since this may potentially corrupt your databases if the binary you use is not compatible with existing databases.` + ) + .option('--holochain-rust-log ', 'RUST_LOG value to pass to the holochain binary') + .option('--holochain-wasm-log ', 'WASM_LOG value to pass to the holochain binary') + .option('--lair-rust-log ', 'RUST_LOG value to pass to the lair keystore binary') + .option( + '-b, --bootstrap-url ', + 'URL of the bootstrap server to use (not persisted across restarts).' + ) + .option( + '-s, --signaling-url ', + 'URL of the signaling server to use (not persisted across restarts).' + ) + .option( + '--ice-urls ', + 'Comma separated string of ICE server URLs to use. Is ignored if an external holochain binary is being used (not persisted across restarts).' + ) + .option( + '--print-holochain-logs', + 'Print holochain logs directly to the terminal (they will be still written to the logfile as well)' + ); + +kangarooCli.parse(); + +const RUN_OPTIONS = validateArgs(kangarooCli.opts()); + // Read and validate the config file to check that the content does not contain // default values -const LAIR_PASSWORD = "password"; - // Check whether lair is initialized or not and if not, decide based on the config // file whether or not to show the splashscreen or use a default password @@ -66,24 +106,21 @@ contextMenu({ ], }); -const KANGAROO_FILESYSTEM = KangarooFileSystem.connect(app); +const KANGAROO_FILESYSTEM = KangarooFileSystem.connect(app, RUN_OPTIONS.profile); const KANGAROO_EMITTER = new KangarooEmitter(); -setupLogs(KANGAROO_EMITTER, KANGAROO_FILESYSTEM, true); +setupLogs(KANGAROO_EMITTER, KANGAROO_FILESYSTEM, RUN_OPTIONS.printHolochainLogs); protocol.registerSchemesAsPrivileged([ { - scheme: "webhapp", + scheme: 'webhapp', privileges: { standard: true, secure: true, stream: true }, }, ]); -const handleSignZomeCall = async ( - _e: IpcMainInvokeEvent, - request: CallZomeRequest -) => { - if (!ZOME_CALL_SIGNER) throw new Error("Zome call signer undefined."); +const handleSignZomeCall = async (_e: IpcMainInvokeEvent, request: CallZomeRequest) => { + if (!ZOME_CALL_SIGNER) throw new Error('Zome call signer undefined.'); // console.log("Got zome call request: ", request); const zomeCallUnsignedNapi: ZomeCallUnsignedNapi = { provenance: Array.from(request.provenance), @@ -123,17 +160,133 @@ let LAIR_HANDLE: childProcess.ChildProcessWithoutNullStreams | undefined; // eslint-disable-next-line @typescript-eslint/no-unused-vars let MAIN_WINDOW: BrowserWindow | undefined | null; let SPLASH_SCREEN_WINDOW: BrowserWindow | undefined; +let IS_APP_QUITTING = false; Menu.setApplicationMenu(kangarooMenu(KANGAROO_FILESYSTEM)); app.whenReady().then(async () => { - SPLASH_SCREEN_WINDOW = createSplashWindow(); - ipcMain.handle("sign-zome-call", handleSignZomeCall); - ipcMain.handle("exit", () => { + /** + * Figure out which splashscreen to show and whether to start holochain immediately. + */ + let splashScreenType: SplashScreenType; + let startImmediately = false; + + if (KANGAROO_CONFIG.passwordMode === 'no-password') { + splashScreenType = SplashScreenType.LoadingOnly; + startImmediately = true; + } else if (KANGAROO_CONFIG.passwordMode === 'password-required') { + if (KANGAROO_FILESYSTEM.keystoreInitialized()) { + splashScreenType = SplashScreenType.EnterPassword; + } else { + splashScreenType = SplashScreenType.PasswordSetup; + } + } else if (KANGAROO_CONFIG.passwordMode === 'password-optional') { + const keystoreInitialized = KANGAROO_FILESYSTEM.keystoreInitialized(); + const randomPwExists = KANGAROO_FILESYSTEM.randomPasswordExists(); + if (keystoreInitialized && randomPwExists) { + splashScreenType = SplashScreenType.LoadingOnly; + startImmediately = true; + } else if (keystoreInitialized && !randomPwExists) { + splashScreenType = SplashScreenType.EnterPassword; + } else { + splashScreenType = SplashScreenType.PasswordSetupOtional; + } + } else { + throw new Error( + `Unexpected setup state.\nKeystore initialized: ${KANGAROO_FILESYSTEM.keystoreInitialized()}.\nPassword mode: ${ + KANGAROO_CONFIG.passwordMode + }\nRandom pw exists: ${KANGAROO_FILESYSTEM.randomPasswordExists()}` + ); + } + + SPLASH_SCREEN_WINDOW = createSplashWindow(splashScreenType); + SPLASH_SCREEN_WINDOW.on('closed', () => { + // We need to drop the variable here to be able to distinguish + // in other places whether the splah screen window is still open + // or not. + SPLASH_SCREEN_WINDOW = undefined; + }); + + if (KANGAROO_CONFIG.systray) { + const systray = new Tray(SYSTRAY_ICON_PATH); + systray.setToolTip(KANGAROO_CONFIG.productName); + + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Open', + type: 'normal', + click() { + if (SPLASH_SCREEN_WINDOW) { + SPLASH_SCREEN_WINDOW.show(); + } else if(MAIN_WINDOW) { + MAIN_WINDOW.show(); + } + }, + }, + { + label: 'Restart', + type: 'normal', + click() { + const options: Electron.RelaunchOptions = { + args: process.argv, + }; + // /~https://github.com/electron-userland/electron-builder/issues/1727#issuecomment-769896927 + if (process.env.APPIMAGE) { + console.log('process.execPath: ', process.execPath); + options.args?.unshift('--appimage-extract-and-run'); + options.execPath = process.env.APPIMAGE; + } + app.relaunch(options); + app.quit(); + }, + }, + { + label: 'Quit', + type: 'normal', + click() { + app.quit(); + }, + }, + ]); + + systray.setContextMenu(contextMenu); + } + + /** + * IPC handlers + */ + ipcMain.handle('get-name-and-version', () => ({ + productName: KANGAROO_CONFIG.productName, + version: KANGAROO_CONFIG.version, + })); + ipcMain.handle('sign-zome-call', handleSignZomeCall); + ipcMain.handle('exit', () => { app.exit(0); }); + // Will be called by the splashscreen UI in the "password-optional" + // or "user-provided" password modes + ipcMain.handle('launch', async (_e, passwordInput: PasswordType): Promise => { + const { lairHandle, holochainManager, mainWindow, zomeCallSigner } = await launch( + KANGAROO_FILESYSTEM, + KANGAROO_EMITTER, + SPLASH_SCREEN_WINDOW, + passwordInput, + RUN_OPTIONS + ); - // Check for updates + LAIR_HANDLE = lairHandle; + HOLOCHAIN_MANAGER = holochainManager; + MAIN_WINDOW = mainWindow; + ZOME_CALL_SIGNER = zomeCallSigner; + + if (KANGAROO_CONFIG.systray) { + MAIN_WINDOW.on('close', mainWindowCloseHandler); + } + }); + + /** + * Checking for app updates + */ if (app.isPackaged && KANGAROO_CONFIG.autoUpdates) { autoUpdater.allowPrerelease = true; autoUpdater.autoDownload = false; @@ -146,120 +299,75 @@ app.whenReady().then(async () => { console.warn('Failed to check for updates: ', e); } - console.log("updateCheckResult: ", updateCheckResult); + console.log('updateCheckResult: ', updateCheckResult); // We only install semver compatible updates const appVersion = app.getVersion(); if ( updateCheckResult && - breakingVersion(updateCheckResult.updateInfo.version) === - breakingVersion(appVersion) && + breakingVersion(updateCheckResult.updateInfo.version) === breakingVersion(appVersion) && semver.gt(updateCheckResult.updateInfo.version, appVersion) ) { const userDecision = await dialog.showMessageBox({ - title: "Update Available", - type: "question", - buttons: ["Deny", "Install and Restart"], + title: 'Update Available', + type: 'question', + buttons: ['Deny', 'Install and Restart'], defaultId: 0, cancelId: 0, message: `A new compatible version of ${KANGAROO_CONFIG.productName} is available (${updateCheckResult.updateInfo.version}). Do you want to install it?`, }); if (userDecision.response === 1) { // downloading means that with the next start of the application it's automatically going to be installed - autoUpdater.on("update-downloaded", () => autoUpdater.quitAndInstall()); + autoUpdater.on('update-downloaded', () => autoUpdater.quitAndInstall()); await autoUpdater.downloadUpdate(); } } } - if (!KANGAROO_FILESYSTEM.keystoreInitialized()) { - if (SPLASH_SCREEN_WINDOW) - SPLASH_SCREEN_WINDOW.webContents.send( - "loading-progress-update", - "Initializing lair keystore..." - ); - - console.log("initializing lair keystore..."); - await initializeLairKeystore( - LAIR_BINARY, - KANGAROO_FILESYSTEM.keystoreDir, + /** + * If the conditions are fulfilled we can immediately start holochain here, + * otherwise we start holochain when the corresponding splashscreen UI invokes + * the 'launch' IPC command + */ + if (startImmediately) { + const { lairHandle, holochainManager, mainWindow, zomeCallSigner } = await launch( + KANGAROO_FILESYSTEM, KANGAROO_EMITTER, - LAIR_PASSWORD + SPLASH_SCREEN_WINDOW, + { type: 'random' }, + RUN_OPTIONS ); - console.log("lair keystore initialized."); - } - if (SPLASH_SCREEN_WINDOW) - SPLASH_SCREEN_WINDOW.webContents.send( - "loading-progress-update", - "Starting lair keystore..." - ); - - let lairUrl; - - [LAIR_HANDLE, lairUrl] = await launchLairKeystore( - LAIR_BINARY, - KANGAROO_FILESYSTEM.keystoreDir, - KANGAROO_EMITTER, - LAIR_PASSWORD - ); - - ZOME_CALL_SIGNER = await ZomeCallSigner.connect(lairUrl, LAIR_PASSWORD); - - if (SPLASH_SCREEN_WINDOW) - SPLASH_SCREEN_WINDOW.webContents.send( - "loading-progress-update", - "Starting Holochain..." - ); - - HOLOCHAIN_MANAGER = await HolochainManager.launch( - KANGAROO_EMITTER, - KANGAROO_FILESYSTEM, - HOLOCHAIN_BINARY, - LAIR_PASSWORD, - KANGAROO_CONFIG.bins.holochain.version, - KANGAROO_FILESYSTEM.conductorDir, - KANGAROO_FILESYSTEM.conductorConfigPath, - lairUrl, - DEFAULT_BOOTSTRAP_SERVER, - DEFAULT_SIGNALING_SERVER - ); - - // Install happ if necessary - await HOLOCHAIN_MANAGER.installHappIfNecessary(); - - console.log("Happ installed."); - const appToken = await HOLOCHAIN_MANAGER.getAppToken(); + LAIR_HANDLE = lairHandle; + HOLOCHAIN_MANAGER = holochainManager; + MAIN_WINDOW = mainWindow; + ZOME_CALL_SIGNER = zomeCallSigner; - console.log("Starting main window..."); - - SPLASH_SCREEN_WINDOW.close(); - - MAIN_WINDOW = await createHappWindow( - { - type: "path", - path: UI_DIRECTORY, - }, - HAPP_APP_ID, - HOLOCHAIN_MANAGER.appPort, - appToken, - false - ); - // This is just here to make it compile for now. - console.log(MAIN_WINDOW); + if (KANGAROO_CONFIG.systray) { + MAIN_WINDOW.on('close', mainWindowCloseHandler); + } + } }); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. -app.on("window-all-closed", () => { +app.on('window-all-closed', () => { app.quit(); // if (process.platform !== 'darwin') { // app.quit() // } }); -app.on("quit", () => { +// This is here to distinguish in the 'close' listener of the main window, +// for the case that a systray icon is used, whether the main window should +// indeed be closed (if the app is attempted to be quit via the systray menu) +// or only hidden +app.on('before-quit', () => { + IS_APP_QUITTING = true; +}); + +app.on('quit', () => { if (LAIR_HANDLE) { LAIR_HANDLE.kill(); } @@ -268,49 +376,40 @@ app.on("quit", () => { } }); -// app.on("activate", () => { -// // On OS X it's common to re-create a window in the app when the -// // dock icon is clicked and there are no other windows open. -// if (BrowserWindow.getAllWindows().length === 0) { -// createMainWindow(); -// } -// }); - -// const contextMenu = Menu.buildFromTemplate([ -// { -// label: 'Open', -// type: 'normal', -// click() { -// if (SPLASH_SCREEN_WINDOW) { -// SPLASH_SCREEN_WINDOW.show(); -// } else if (MAIN_WINDOW) { -// MAIN_WINDOW.show(); -// } -// }, -// }, -// { -// label: 'Restart', -// type: 'normal', -// click() { -// const options: Electron.RelaunchOptions = { -// args: process.argv, -// }; -// // /~https://github.com/electron-userland/electron-builder/issues/1727#issuecomment-769896927 -// if (process.env.APPIMAGE) { -// console.log('process.execPath: ', process.execPath); -// options.args?.unshift('--appimage-extract-and-run'); -// options.execPath = process.env.APPIMAGE; -// } -// app.relaunch(options); -// app.quit(); -// }, -// }, -// { -// label: 'Quit', -// type: 'normal', -// click() { -// app.quit(); -// }, -// }, -// ]); -// }); +/** + * This handler will make sure that the main window only gets hidden instead of + * closed (to maintain the javascript state) if the systray icon option is + * used. + * + * @param e Window close event + */ +const mainWindowCloseHandler = (e: Event) => { + if (!IS_APP_QUITTING && MAIN_WINDOW) { + e.preventDefault(); + MAIN_WINDOW.hide(); + + const notificationIcon = nativeImage.createFromPath(NOTIFICATIONS_ICON_PATH); + new Notification({ + title: `${KANGAROO_CONFIG.productName} keeps running in the background`, + body: `To close ${KANGAROO_CONFIG.productName} and stop synching with peers, quit from the icon in the system tray.`, + icon: notificationIcon, + }) + .on('click', async () => { + if (MAIN_WINDOW) { + MAIN_WINDOW.show(); + const response = await dialog.showMessageBox(MAIN_WINDOW, { + type: 'info', + message: `${KANGAROO_CONFIG.productName} keeps running in the background if you close the Window.\n\nThis is to keep synchronizing data with peers.\n\nDo you want to quit ${KANGAROO_CONFIG.productName} fully?`, + buttons: ['Keep Running', 'Quit'], + defaultId: 0, + cancelId: 1, + }); + if (response.response === 1) { + app.quit(); + } + } + }) + .show(); + } + console.log("Is main window still defined?", MAIN_WINDOW); +}; diff --git a/src/main/lairKeystore.ts b/src/main/lairKeystore.ts index b024236..1e73137 100644 --- a/src/main/lairKeystore.ts +++ b/src/main/lairKeystore.ts @@ -10,7 +10,7 @@ export async function initializeLairKeystore( lairBinary: string, keystoreDir: string, kangarooEmitter: KangarooEmitter, - password: string, + password: string ): Promise { const lairHandle = childProcess.spawn(lairBinary, ['init', '-p'], { cwd: keystoreDir }); lairHandle.stdin.write(password); @@ -35,7 +35,7 @@ export async function launchLairKeystore( keystoreDir: string, kangarooEmitter: KangarooEmitter, password: string, - rustLog?: string, + rustLog?: string ): Promise<[childProcess.ChildProcessWithoutNullStreams, string]> { // On Unix systems, there is a limit to the path length of a domain socket. Create a symlink to the lair directory from the tempdir // instead and overwrite the connectionUrl in the lair-keystore-config.yaml diff --git a/src/main/launch.ts b/src/main/launch.ts new file mode 100644 index 0000000..bc80d75 --- /dev/null +++ b/src/main/launch.ts @@ -0,0 +1,118 @@ +import * as childProcess from 'child_process'; +import { BrowserWindow } from 'electron'; +import { KangarooEmitter } from './eventEmitter'; +import { KangarooFileSystem } from './filesystem'; +import { PasswordType } from './types'; +import { RunOptions } from './cli'; +import { initializeLairKeystore, launchLairKeystore } from './lairKeystore'; +import { + DEFAULT_BOOTSTRAP_SERVER, + DEFAULT_SIGNALING_SERVER, + HAPP_APP_ID, + HOLOCHAIN_BINARY, + KANGAROO_CONFIG, + LAIR_BINARY, + UI_DIRECTORY, +} from './const'; +import { createHappWindow } from './windows'; +import { ZomeCallSigner } from '@holochain/hc-spin-rust-utils'; +import { HolochainManager } from './holochainManager'; + +export async function launch( + kangarooFs: KangarooFileSystem, + kangarooEmitter: KangarooEmitter, + splashscreenWindow: BrowserWindow | undefined, + passwordInput: PasswordType, + runOptions: RunOptions +): Promise<{ + lairHandle: childProcess.ChildProcessWithoutNullStreams; + holochainManager: HolochainManager; + mainWindow: BrowserWindow; + zomeCallSigner: ZomeCallSigner; +}> { + let password: string; + switch (passwordInput.type) { + case 'random': { + password = kangarooFs.readOrCreatePassword(); + break; + } + case 'user-provided': { + password = passwordInput.password; + break; + } + } + + if (!kangarooFs.keystoreInitialized()) { + if (splashscreenWindow) + splashscreenWindow.webContents.send( + 'loading-progress-update', + 'Initializing lair keystore...' + ); + + console.log('initializing lair keystore...'); + await initializeLairKeystore( + runOptions.lairPath ? runOptions.lairPath : LAIR_BINARY, + kangarooFs.keystoreDir, + kangarooEmitter, + password + ); + console.log('lair keystore initialized.'); + } + if (splashscreenWindow) + splashscreenWindow.webContents.send('loading-progress-update', 'Starting lair keystore...'); + + const [lairHandle, lairUrl] = await launchLairKeystore( + runOptions.lairPath ? runOptions.lairPath : LAIR_BINARY, + kangarooFs.keystoreDir, + kangarooEmitter, + password + ); + + const zomeCallSigner = await ZomeCallSigner.connect(lairUrl, password); + + if (splashscreenWindow) + splashscreenWindow.webContents.send('loading-progress-update', 'Starting Holochain...'); + + const holochainManager = await HolochainManager.launch( + kangarooEmitter, + kangarooFs, + runOptions.holochainPath ? runOptions.holochainPath : HOLOCHAIN_BINARY, + password, + KANGAROO_CONFIG.bins.holochain.version, + kangarooFs.conductorDir, + kangarooFs.conductorConfigPath, + lairUrl, + runOptions.bootstrapUrl ? runOptions.bootstrapUrl.toString() : DEFAULT_BOOTSTRAP_SERVER, + runOptions.signalingUrl ? runOptions.signalingUrl.toString() : DEFAULT_SIGNALING_SERVER, + runOptions.iceUrls ? runOptions.iceUrls : undefined + ); + + // Install happ if necessary + await holochainManager.installHappIfNecessary(runOptions.networkSeed); + + console.log('Happ installed.'); + + const appToken = await holochainManager.getAppToken(); + + console.log('Starting main window...'); + + if (splashscreenWindow) splashscreenWindow.close(); + + const mainWindow = await createHappWindow( + { + type: 'path', + path: UI_DIRECTORY, + }, + HAPP_APP_ID, + holochainManager.appPort, + appToken, + false + ); + + return { + lairHandle, + holochainManager, + mainWindow, + zomeCallSigner, + }; +} diff --git a/src/main/logs.ts b/src/main/logs.ts index 41c9fe9..e25b905 100644 --- a/src/main/logs.ts +++ b/src/main/logs.ts @@ -12,7 +12,7 @@ import { WASM_LOG, KANGAROO_ERROR, KANGAROO_LOG, - KangarooEmitter + KangarooEmitter, } from './eventEmitter'; import { KANGAROO_CONFIG } from './const'; @@ -26,9 +26,12 @@ const HOLOCHAIN_LOGGERS: Record = {}; export function setupLogs( kangarooEmitter: KangarooEmitter, kangarooFs: KangarooFileSystem, - holochainLogsToTerminal: boolean, + holochainLogsToTerminal: boolean ) { - const logFilePath = path.join(kangarooFs.appLogsDir, `${KANGAROO_CONFIG.productName.replace(' ', '_')}.log`); + const logFilePath = path.join( + kangarooFs.appLogsDir, + `${KANGAROO_CONFIG.productName.replace(' ', '_')}.log` + ); if (fs.existsSync(logFilePath)) { const stats = fs.statSync(logFilePath); // If existing logfile is larger than 1GB, delete it @@ -81,7 +84,7 @@ export function setupLogs( function logHolochain( holochainData: HolochainData, logFileTransport: winston.transports.FileTransportInstance, - printToTerminal: boolean, + printToTerminal: boolean ) { const holochainVersion = (holochainData as HolochainData).version; const line = (holochainData as HolochainData).data; @@ -101,7 +104,7 @@ function logHolochain( function createHolochainLogger( holochainVersion: HolochainVersion, - logFileTransport: winston.transports.FileTransportInstance, + logFileTransport: winston.transports.FileTransportInstance ): winston.Logger { return createLogger({ transports: [logFileTransport], @@ -114,13 +117,13 @@ function createHolochainLogger( level, message, }); - }), + }) ), }); } function createLairLogger( - logFileTransport: winston.transports.FileTransportInstance, + logFileTransport: winston.transports.FileTransportInstance ): winston.Logger { return createLogger({ transports: [logFileTransport], @@ -133,7 +136,7 @@ function createLairLogger( level, message, }); - }), + }) ), }); } diff --git a/src/main/menu.ts b/src/main/menu.ts index 66db8b2..96380d7 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -28,7 +28,7 @@ export const kangarooMenu = (kangarooFs: KangarooFileSystem) => { const exportToPathResponse = await dialog.showSaveDialog({ title: 'Export Logs', buttonLabel: 'Export', - defaultPath: `Moss_${app.getVersion()}_logs_${new Date().toISOString()}.zip`, + defaultPath: `${KANGAROO_CONFIG.productName}_${app.getVersion()}_logs_${new Date().toISOString()}.zip`, }); if (exportToPathResponse.filePath) { zip.writeZip(exportToPathResponse.filePath); diff --git a/src/main/types.ts b/src/main/types.ts index d5a2f1b..b7f28b4 100644 --- a/src/main/types.ts +++ b/src/main/types.ts @@ -1,5 +1,3 @@ - - export type KangarooConfig = { /** * App ID. A unique id that will be used by the OS and that defines @@ -9,11 +7,11 @@ export type KangarooConfig = { * * Will also be used as the package name in package.json. */ - appId: string, + appId: string; /** * The name of the app as displayed in places such as the Window bar. */ - productName: string, + productName: string; /** * This version will overwrite the version in package.json. * **IMPORTANT** Breaking version semver changes here will lead to @@ -26,28 +24,50 @@ export type KangarooConfig = { * 0.13.0-alpha.0 -> 0.13.0-beta.0: Breaking change; * 0.13.0-alpha.0 -> 0.13.0-alpha.1: *No* breaking change; */ - version: string, + version: string; + /** + * Which password mode to use. Available modes are: + * + * - "no-password": Users do not have to set up a password and kangaroo + * instead generates a random password under the hood + * to encrypt the conductor database and private keys. + * + * - "password-optional": Users can choose once at setup time whether to use + * a password or not. Cannot be changed later and the + * password cannot be reset. + * + * - "password-required": Users have to set up a password when they start the + * app for the first time. This password cannot be reset + * and if users forget it they lose access to their data + * and private keys. + */ + passwordMode: PasswordMode; /** * Whether to attempt macOS code signing in CI. Requires the corresponding * secrets to be available in the github repository. */ - macOSCodeSigning: boolean, + macOSCodeSigning: boolean; /** * Whether to attempt Windows code signing with an EV certificate in CI. * Assumes that the relevant secrets are available in the repository * in the format of this guide: https://melatonin.dev/blog/how-to-code-sign-windows-installers-with-an-ev-cert-on-github-actions/ */ - windowsEVCodeSigning: boolean, + windowsEVCodeSigning: boolean; /** * Fall back to serving the index.html if a resources is not found. Often required for router-based * frameworks like svelte-kit or vue-router */ - fallbackToIndexHtml: boolean, + fallbackToIndexHtml: boolean; /** - * Whether the app should check for available, semver compatbile releases on github on startup + * Whether the app should check for available, semver compatible releases on github on startup * and prompt to install and restart if a new release is available. */ - autoUpdates: boolean, + autoUpdates: boolean; + /** + * Whether or not to use a systray. If true, the app will remain running in the background + * upon closing its window and can be re-opened via its icon in the systray. + */ + systray: boolean; // /** // * Whether or not the app should have the user set up a password. // */ @@ -65,31 +85,50 @@ export type KangarooConfig = { * network seed will automatically be generated and be based on the * productName and the breaking semver version of your app. */ - networkSeed?: string, - devConfig?: { - happPath: string, - uiPort: string, - } | { - webhappPath: string - }, + networkSeed?: string; + devConfig?: + | { + happPath: string; + uiPort: string; + } + | { + webhappPath: string; + }; author?: { - name?: string, - url?: string, - email?: string, - }, + name?: string; + url?: string; + email?: string; + }; bins: { - holochain: VersionAndSha256, - lair: VersionAndSha256 - } -} - + holochain: VersionAndSha256; + lair: VersionAndSha256; + }; +}; type VersionAndSha256 = { - version: string, + version: string; sha256: { - "x86_64-unknown-linux-gnu": string, - "x86_64-pc-windows-msvc.exe": string, - "x86_64-apple-darwin": string, - "aarch64-apple-darwin": string - } -}; \ No newline at end of file + 'x86_64-unknown-linux-gnu': string; + 'x86_64-pc-windows-msvc.exe': string; + 'x86_64-apple-darwin': string; + 'aarch64-apple-darwin': string; + }; +}; + +export type PasswordMode = 'no-password' | 'password-optional' | 'password-required'; + +export type PasswordType = + | { + type: 'user-provided'; + password: string; + } + | { + type: 'random'; + }; + +export enum SplashScreenType { + LoadingOnly, + EnterPassword, + PasswordSetup, + PasswordSetupOtional, +} diff --git a/src/main/windows.ts b/src/main/windows.ts index 935f31b..d935022 100644 --- a/src/main/windows.ts +++ b/src/main/windows.ts @@ -1,7 +1,7 @@ -import path from "path"; -import fs from "fs"; -import url from "url"; -import { AppAuthenticationToken, InstalledAppId } from "@holochain/client"; +import path from 'path'; +import fs from 'fs'; +import url from 'url'; +import { AppAuthenticationToken, InstalledAppId } from '@holochain/client'; import { BrowserWindow, NativeImage, @@ -9,17 +9,18 @@ import { net, session, shell, -} from "electron"; -import { is } from "@electron-toolkit/utils"; -import { ICON_PATH, KANGAROO_CONFIG } from "./const"; +} from 'electron'; +import { is } from '@electron-toolkit/utils'; +import { ICON_PATH, KANGAROO_CONFIG } from './const'; +import { SplashScreenType } from './types'; export type UISource = | { - type: "path"; + type: 'path'; path: string; } | { - type: "port"; + type: 'port'; port: number; }; @@ -31,31 +32,31 @@ export const createHappWindow = async ( openDevtools: boolean ): Promise => { // TODO create mapping between installed-app-id's and window ids - if (!appPort) throw new Error("App port not defined."); + if (!appPort) throw new Error('App port not defined.'); - if (uiSource.type === "path") { + if (uiSource.type === 'path') { const ses = session.defaultSession; - ses.protocol.handle("webhapp", async (request) => { - const uriWithoutProtocol = request.url.slice("webhapp://".length); - const filePathComponents = uriWithoutProtocol.split("/").slice(1); + ses.protocol.handle('webhapp', async (request) => { + const uriWithoutProtocol = request.url.slice('webhapp://'.length); + const filePathComponents = uriWithoutProtocol.split('/').slice(1); const relativeFilePath = path.join(...filePathComponents); const absoluteFilePath = path.join(uiSource.path, relativeFilePath); - const fallbackToIndexHtml = KANGAROO_CONFIG.fallbackToIndexHtml ? !fs.existsSync(absoluteFilePath) : false; + const fallbackToIndexHtml = KANGAROO_CONFIG.fallbackToIndexHtml + ? !fs.existsSync(absoluteFilePath) + : false; - if (!relativeFilePath.endsWith("index.html") && !fallbackToIndexHtml) { - return net.fetch( - url.pathToFileURL(absoluteFilePath).toString() - ); + if (!relativeFilePath.endsWith('index.html') && !fallbackToIndexHtml) { + return net.fetch(url.pathToFileURL(absoluteFilePath).toString()); } else { const indexHtmlResponse = await net.fetch(url.pathToFileURL(absoluteFilePath).toString()); const content = await indexHtmlResponse.text(); let modifiedContent = content.replace( - "", + '', `` ); // remove title attribute to be able to set title to app id later - modifiedContent = modifiedContent.replace(/.*?<\/title>/i, ""); + modifiedContent = modifiedContent.replace(/<title>.*?<\/title>/i, ''); return new Response(modifiedContent, indexHtmlResponse); } }); @@ -63,29 +64,27 @@ export const createHappWindow = async ( let icon: NativeImage | undefined; - if (uiSource.type === "path") { + if (uiSource.type === 'path') { if (fs.existsSync(ICON_PATH)) { icon = nativeImage.createFromPath(ICON_PATH); } } else { try { - const iconResponse = await net.fetch( - `http://localhost:${uiSource.port}/icon.png` - ); + const iconResponse = await net.fetch(`http://localhost:${uiSource.port}/icon.png`); if (iconResponse.status === 404) { console.warn( - "No icon.png found. It is recommended to put an icon.png file (1024x1024 pixel) in the root of your UI assets directory." + 'No icon.png found. It is recommended to put an icon.png file (1024x1024 pixel) in the root of your UI assets directory.' ); } else { const buffer = await iconResponse.arrayBuffer(); icon = nativeImage.createFromBuffer(Buffer.from(buffer)); } } catch (e) { - console.error("Failed to get icon.png: ", e); + console.error('Failed to get icon.png: ', e); } } - console.log("Instantiating browser window"); + console.log('Instantiating browser window'); const happWindow = new BrowserWindow({ width: 1200, @@ -94,59 +93,53 @@ export const createHappWindow = async ( icon, title: KANGAROO_CONFIG.productName, webPreferences: { - preload: path.resolve(__dirname, "../preload/happ.js"), + preload: path.resolve(__dirname, '../preload/happ.js'), }, }); - console.log("setLinkOpenHandlers"); + console.log('setLinkOpenHandlers'); setLinkOpenHandlers(happWindow); - happWindow.on("page-title-updated", (evt) => { + happWindow.on('page-title-updated', (evt) => { evt.preventDefault(); }); if (openDevtools) happWindow.webContents.openDevTools(); - if (uiSource.type === "port") { + if (uiSource.type === 'port') { try { // Check whether dev server is responsive and index.html exists await net.fetch(`http://localhost:${uiSource.port}/index.html`); } catch (e) { - console.error( - `No index.html file found at http://localhost:${uiSource.port}/index.html`, - e - ); - if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]); + console.error(`No index.html file found at http://localhost:${uiSource.port}/index.html`, e); + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']); } else { - happWindow.loadFile( - path.join(__dirname, "../renderer/indexNotFound.html") - ); + happWindow.loadFile(path.join(__dirname, '../renderer/indexNotFound.html')); } happWindow.show(); return happWindow; } await happWindow.loadURL(`http://localhost:${uiSource.port}`); - } else if (uiSource.type === "path") { + } else if (uiSource.type === 'path') { try { - console.log("loading URL"); + console.log('loading URL'); await happWindow.loadURL(`webhapp://webhappwindow/index.html`); - console.log("URL loaded"); + console.log('URL loaded'); } catch (e) { - console.error("[ERROR] Failed to fetch index.html"); + console.error('[ERROR] Failed to fetch index.html'); - if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - happWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]); + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + happWindow.loadURL(process.env['ELECTRON_RENDERER_URL']); } else { - happWindow.loadFile( - path.join(__dirname, "../renderer/indexNotFound2.html") - ); + happWindow.loadFile(path.join(__dirname, '../renderer/indexNotFound2.html')); } happWindow.show(); return happWindow; } } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any throw new Error(`Unsupported uiSource type: ${(uiSource as any).type}`); } @@ -158,36 +151,30 @@ export const createHappWindow = async ( export function setLinkOpenHandlers(browserWindow: BrowserWindow): void { // links should open in the system default application // instead of the webview - browserWindow.webContents.on("will-navigate", (e) => { - if ( - e.url.startsWith("http://localhost") || - e.url.startsWith("http://127.0.0.1") - ) { + browserWindow.webContents.on('will-navigate', (e) => { + if (e.url.startsWith('http://localhost') || e.url.startsWith('http://127.0.0.1')) { // ignore dev server reload return; } if ( - e.url.startsWith("http://") || - e.url.startsWith("https://") || - e.url.startsWith("mailto://") + e.url.startsWith('http://') || + e.url.startsWith('https://') || + e.url.startsWith('mailto://') ) { e.preventDefault(); shell.openExternal(e.url); } }); - browserWindow.webContents.on("will-frame-navigate", (e) => { - if ( - e.url.startsWith("http://localhost") || - e.url.startsWith("http://127.0.0.1") - ) { + browserWindow.webContents.on('will-frame-navigate', (e) => { + if (e.url.startsWith('http://localhost') || e.url.startsWith('http://127.0.0.1')) { // ignore dev server reload return; } if ( - e.url.startsWith("http://") || - e.url.startsWith("https://") || - e.url.startsWith("mailto://") + e.url.startsWith('http://') || + e.url.startsWith('https://') || + e.url.startsWith('mailto://') ) { e.preventDefault(); shell.openExternal(e.url); @@ -197,17 +184,34 @@ export function setLinkOpenHandlers(browserWindow: BrowserWindow): void { // Links with target=_blank should open in the system default browser and // happ windows are not allowed to spawn new electron windows browserWindow.webContents.setWindowOpenHandler((details) => { - if ( - details.url.startsWith("http://") || - details.url.startsWith("https://") - ) { + if (details.url.startsWith('http://') || details.url.startsWith('https://')) { shell.openExternal(details.url); } - return { action: "deny" }; + return { action: 'deny' }; }); } -export const createSplashWindow = (): BrowserWindow => { +export const createSplashWindow = (type: SplashScreenType): BrowserWindow => { + let htmlFile: string; + switch (type) { + case SplashScreenType.EnterPassword: { + htmlFile = 'enterPassword.html'; + break; + } + case SplashScreenType.LoadingOnly: { + htmlFile = 'loading.html'; + break; + } + case SplashScreenType.PasswordSetup: { + htmlFile = 'setupPassword.html'; + break; + } + case SplashScreenType.PasswordSetupOtional: { + htmlFile = 'setupPasswordOptional.html'; + break; + } + } + const splashWindow = new BrowserWindow({ height: 450, width: 800, @@ -215,31 +219,23 @@ export const createSplashWindow = (): BrowserWindow => { resizable: false, frame: false, show: false, - backgroundColor: "#fbf9f7", + backgroundColor: '#fbf9f7', webPreferences: { - preload: path.join(__dirname, "../preload/splashscreen.js"), + preload: path.join(__dirname, '../preload/splashscreen.js'), }, }); - // and load the splashscreen.html of the app. - if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - splashWindow.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}/splashscreen.html` - ); + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + splashWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/${htmlFile}`); } else { - splashWindow.loadFile( - path.join(__dirname, "../renderer/splashscreen.html") - ); + splashWindow.loadFile(path.join(__dirname, `../renderer/${htmlFile}`)); } - splashWindow.once("ready-to-show", () => { - splashWindow.webContents.send( - "name-and-version", - { - productName: KANGAROO_CONFIG.productName, - version: KANGAROO_CONFIG.version, - } - ); + splashWindow.once('ready-to-show', () => { + splashWindow.webContents.send('name-and-version', { + productName: KANGAROO_CONFIG.productName, + version: KANGAROO_CONFIG.version, + }); splashWindow.show(); }); diff --git a/src/preload/happ.ts b/src/preload/happ.ts index 24b11d9..3555c14 100644 --- a/src/preload/happ.ts +++ b/src/preload/happ.ts @@ -6,4 +6,4 @@ import { CallZomeRequestUnsigned } from '@holochain/client'; contextBridge.exposeInMainWorld('__HC_ZOME_CALL_SIGNER__', { signZomeCall: (zomeCall: CallZomeRequestUnsigned) => ipcRenderer.invoke('sign-zome-call', zomeCall), -}); \ No newline at end of file +}); diff --git a/src/preload/splashscreen.ts b/src/preload/splashscreen.ts index ff881d1..5f878f6 100644 --- a/src/preload/splashscreen.ts +++ b/src/preload/splashscreen.ts @@ -1,12 +1,14 @@ // See the Electron documentation for details on how to use preload scripts: // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts import { contextBridge, ipcRenderer } from 'electron'; +import { PasswordType } from '../main/types'; contextBridge.exposeInMainWorld('electronAPI', { onProgressUpdate: (callback) => ipcRenderer.on('loading-progress-update', callback), onNameAndVersion: (callback) => ipcRenderer.on('name-and-version', callback), - lairSetupRequired: () => ipcRenderer.invoke('lair-setup-required'), - launch: (password: string) => ipcRenderer.invoke('launch', password), getProfile: () => ipcRenderer.invoke('get-profile'), + getNameAndVersion: () => ipcRenderer.invoke('get-name-and-version'), + lairSetupRequired: () => ipcRenderer.invoke('lair-setup-required'), + launch: (passwordInput: PasswordType) => ipcRenderer.invoke('launch', passwordInput), exit: () => ipcRenderer.invoke('exit'), }); diff --git a/src/renderer/enterPassword.html b/src/renderer/enterPassword.html new file mode 100644 index 0000000..cc440f1 --- /dev/null +++ b/src/renderer/enterPassword.html @@ -0,0 +1,129 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="index.css" /> + </head> + <body> + <div id="app" class="splash-wrapper"> + <div class="splash-content-wrapper"> + <div> + <div id="productName" class="splash-product-name"></div> + <div id="version" class="splash-version"></div> + </div> + <div class="content column"> + <div class="column items-center" id="password-creation-container"> + <!-- Choose password --> + <div class="column items-center" id="enter-password-div"> + <h2>Enter password</h2> + <input + class="pw-input" + type="password" + id="enter-password-input" + placeholder="password" + /> + <div class="warning" id="warning-1"></div> + <button style="margin-top: 5px" id="launch-btn" disabled>Launch</button> + </div> + </div> + + <!-- Loading message --> + <div class="splash-loading-message hidden" id="activity">Loading...</div> + </div> + <div class="splash-license">Licensed under the Cryptographic Autonomy License v1.0</div> + </div> + <img + style="position: absolute; bottom: 5px; right: 10px; width: 300px" + src="img/powered_by_holochain.png" + /> + <img + tabindex="0" + id="close-btn" + title="close" + style="cursor: pointer; position: absolute; top: 5px; right: 5px; width: 35px" + src="img/close.svg" + /> + </div> + <script> + // TODO add event listener for holochain fatal panic case + + let password = ''; + + (async () => { + const { productName, version } = await window.electronAPI.getNameAndVersion(); + document.getElementById('productName').innerHTML = productName; + document.getElementById('version').innerHTML = `version ${version}`; + })(); + + const activitEl = document.getElementById('activity'); + window.electronAPI.onProgressUpdate((event, msg) => { + console.log('status', msg); + activitEl.innerHTML = msg; + }); + + const closeBtn = document.getElementById('close-btn'); + closeBtn.addEventListener('click', () => { + window.electronAPI.exit(); + }); + closeBtn.addEventListener('keypress', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + window.electronAPI.exit(); + } + }); + + const enterPwDiv = document.getElementById('enter-password-div'); + + const warningEl = document.getElementById('warning-1'); + + const enterPwInput = document.getElementById('enter-password-input'); + + enterPwInput.focus(); + + const handleLaunch = async () => { + enterPwDiv.classList.add('hidden'); + activitEl.classList.remove('hidden'); + try { + await window.electronAPI.launch({ type: 'user-provided', password }); + } catch (e) { + console.error('Failed to launch: ', e); + activitEl.innerHTML = ''; + activitEl.classList.add('hidden'); + enterPwDiv.classList.remove('hidden'); + if (e.toString().includes('Wrong password.')) { + password = ''; + enterPwInput.value = ''; + warningEl.innerHTML = 'Wrong Password.'; + setTimeout(() => { + enterPwInput.focus(); + }); + } else { + // TODO add logic to offer exporting log files + warningEl.innerHTML = `ERROR: Failed to launch Holochain.`; + } + } + }; + + enterPwInput.addEventListener('input', (e) => { + password = enterPwInput.value; + if (!password) { + launchBtn.disabled = true; + } else { + launchBtn.disabled = false; + } + warningEl.innerHTML = ''; + }); + + enterPwDiv.addEventListener('keypress', async (e) => { + if (e.key === 'Enter') { + await handleLaunch(password); + } + }); + + // Add event listener to "Launch" button + const launchBtn = document.getElementById('launch-btn'); + launchBtn.addEventListener('click', async () => { + await handleLaunch(); + }); + </script> + </body> +</html> diff --git a/src/renderer/index.css b/src/renderer/index.css index c9c63e6..767e87f 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -11,7 +11,34 @@ body { margin: 0; padding: 0; /* to match the main window */ - background-color: #f7f5f3; + font-family: 'gilroyextrabold', Helvetica, sans-serif; + /* background: linear-gradient(90deg, #0edad3, #4622e3); */ +} + +.flex { + display: flex; +} + +.column { + display: flex; + flex-direction: column; +} + +.row { + display: flex; + flex-direction: row; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.hidden { + display: none; } .splash-wrapper { @@ -20,11 +47,6 @@ body { } .splash-image-wrapper { - background-color: #fff; - /* background-image: url('img/iuliu-illes-MkMvM2T0gVE-unsplash.jpg'); */ - background-repeat: no-repeat; - background-size: cover; - background-position: center; width: 320px; height: 450px; padding: 30px; @@ -86,4 +108,33 @@ body { position: absolute; bottom: 3px; left: 5px; +} + +.pw-input { + height: 30px; + width: 200px; + border-radius: 5px; + font-size: 16px; +} + +button { + height: 26px; + min-width: 80px; + cursor: pointer; +} + +.warning { + color: rgb(193, 0, 0); + font-size: 14px; + min-height: 16px; +} + +.red { + color: rgb(193, 0, 0); +} + +.previous-step-btn { + position: fixed; + top: 6px; + left: 6px; } \ No newline at end of file diff --git a/src/renderer/indexNotFound.html b/src/renderer/indexNotFound.html index 6bf7e10..82715a9 100644 --- a/src/renderer/indexNotFound.html +++ b/src/renderer/indexNotFound.html @@ -1,4 +1,4 @@ -<!doctype html> +<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> @@ -13,7 +13,8 @@ <div class="container"> <h1>index.html not found.</h1> <div style="margin-bottom: 100px"> - Is your dev server running at the port specified with the <span class="code">devConfig</span> section of the kangaroo config file? + Is your dev server running at the port specified with the + <span class="code">devConfig</span> section of the kangaroo config file? </div> </div> </body> diff --git a/src/renderer/indexNotFound2.html b/src/renderer/indexNotFound2.html index a29a99d..db3f785 100644 --- a/src/renderer/indexNotFound2.html +++ b/src/renderer/indexNotFound2.html @@ -1,4 +1,4 @@ -<!doctype html> +<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> diff --git a/src/renderer/splashscreen.html b/src/renderer/loading.html similarity index 59% rename from src/renderer/splashscreen.html rename to src/renderer/loading.html index 1619cc1..f4b63f9 100644 --- a/src/renderer/splashscreen.html +++ b/src/renderer/loading.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> - <meta charset="utf-8"> + <meta charset="utf-8" /> <link rel="stylesheet" type="text/css" href="index.css" /> </head> <body> @@ -11,36 +11,40 @@ <div id="productName" class="splash-product-name"></div> <div id="version" class="splash-version"></div> </div> - <div class="splash-loading-message" id="activity"> - Setting up Holochain... - </div> - <div class="splash-license"> - Licensed under the Cryptographic Autonomy License v1.0 - </div> + <div class="splash-loading-message" id="activity">Setting up Holochain...</div> + <div class="splash-license">Licensed under the Cryptographic Autonomy License v1.0</div> </div> - <img style="position: absolute; bottom: 5px; right: 10px; width: 300px;" src="img/powered_by_holochain.png" /> - <img tabindex="0" id="close-btn" style="cursor: pointer; position: absolute; top: 5px; right: 5px; width: 35px;" src="img/close.svg" /> - + <img + style="position: absolute; bottom: 5px; right: 10px; width: 300px" + src="img/powered_by_holochain.png" + /> + <img + tabindex="0" + id="close-btn" + title="close" + style="cursor: pointer; position: absolute; top: 5px; right: 5px; width: 35px" + src="img/close.svg" + /> </div> <script> window.electronAPI.onNameAndVersion((event, msg) => { - console.log("got name and version: ", msg); + console.log('got name and version: ', msg); document.getElementById('productName').innerHTML = msg.productName; document.getElementById('version').innerHTML = `version ${msg.version}`; - }) + }); window.electronAPI.onProgressUpdate((event, msg) => { console.log('status', msg); document.getElementById('activity').innerHTML = msg; - }) + }); const closeBtn = document.getElementById('close-btn'); closeBtn.addEventListener('click', () => { window.electronAPI.exit(); - }) + }); closeBtn.addEventListener('keypress', (e) => { - if (e.key === "Enter" || e.key === " ") { + if (e.key === 'Enter' || e.key === ' ') { window.electronAPI.exit(); } - }) + }); </script> </body> </html> diff --git a/src/renderer/setupPassword.html b/src/renderer/setupPassword.html new file mode 100644 index 0000000..40172be --- /dev/null +++ b/src/renderer/setupPassword.html @@ -0,0 +1,192 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="index.css" /> + </head> + <body> + <div id="app" class="splash-wrapper"> + <div class="splash-content-wrapper"> + <div> + <div id="productName" class="splash-product-name"></div> + <div id="version" class="splash-version"></div> + </div> + <div class="content column"> + <div class="column items-center" id="password-creation-container"> + <!-- Choose password --> + <div class="column items-center" id="choose-password-div"> + <h2>Choose a password</h2> + <div style="text-align: center; margin-bottom: 20px"> + This password will be used to encrypt your data and private keys on your device.<br /> + <b + >It cannot be reset and is not backed up by any central authority.<br /><u + >If you forget it you lose access to your data</u + ></b + > + </div> + <input + class="pw-input" + type="password" + id="choose-password-input" + placeholder="password" + /> + <div class="warning" id="warning-1"></div> + <button style="margin-top: 5px" id="continue-btn" disabled>Continue</button> + </div> + + <!-- Confirm password --> + <div class="column items-center hidden" id="confirm-password-div"> + <h2>Confirm password</h2> + <button class="previous-step-btn" id="previous-step-btn">< previous step</button> + <input + class="pw-input" + type="password" + id="confirm-password-input" + placeholder="password" + /> + <div class="warning" id="warning-2"></div> + <div class="row" style="max-width: 400px; align-items: flex-start;" id="understood-div"> + <input type="checkbox" id="understood-checkbox"/> + <span style="margin-left: 5px;">I understood that I cannot reset this password because there is no central authority storing it for me and that <b>I will lose access to my data if I forget it</b>.</span> + </div> + <button style="margin-top: 5px" id="launch-btn" disabled>Launch</button> + </div> + </div> + + <!-- Loading message --> + <div class="splash-loading-message hidden" id="activity">Setting up Holochain...</div> + </div> + <div class="splash-license">Licensed under the Cryptographic Autonomy License v1.0</div> + </div> + <img + style="position: absolute; bottom: 5px; right: 10px; width: 300px" + src="img/powered_by_holochain.png" + /> + <img + tabindex="0" + id="close-btn" + title="close" + style="cursor: pointer; position: absolute; top: 5px; right: 5px; width: 35px" + src="img/close.svg" + /> + </div> + <script> + // TODO add event listener for holochain fatal panic case + + let password = ''; + let confirmedPassword = ''; + + (async () => { + const { productName, version } = await window.electronAPI.getNameAndVersion(); + document.getElementById('productName').innerHTML = productName; + document.getElementById('version').innerHTML = `version ${version}`; + })(); + const activitEl = document.getElementById('activity'); + window.electronAPI.onProgressUpdate((event, msg) => { + console.log('status', msg); + activitEl.innerHTML = msg; + }); + const closeBtn = document.getElementById('close-btn'); + closeBtn.addEventListener('click', () => { + window.electronAPI.exit(); + }); + closeBtn.addEventListener('keypress', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + window.electronAPI.exit(); + } + }); + + const choosePwDiv = document.getElementById('choose-password-div'); + const confirmPwDiv = document.getElementById('confirm-password-div'); + const continueBtn = document.getElementById('continue-btn'); + const choosePwInput = document.getElementById('choose-password-input'); + const confirmPwInput = document.getElementById('confirm-password-input'); + const warning1El = document.getElementById('warning-1'); + const warning2El = document.getElementById('warning-2'); + const launchBtn = document.getElementById('launch-btn'); + const previousStepBtn = document.getElementById('previous-step-btn'); + const understoodDiv = document.getElementById('understood-div'); + const understoodCheckbox = document.getElementById('understood-checkbox'); + + const handleLaunch = async () => { + if (password !== confirmedPassword) { + warning2El.innerHTML = "Passwords don't match!"; + confirmPwInput.focus(); + } else if (!understoodCheckbox.checked) { + understoodDiv.classList.add('red'); + } else { + confirmPwDiv.classList.add('hidden'); + activitEl.classList.remove('hidden'); + try { + await window.electronAPI.launch({ type: "user-provided", password }); + } catch (e) { + console.error('Failed to launch: ', e); + activitEl.innerHTML = ''; + password = ''; + choosePwInput.value = ''; + confirmedPassword = ''; + confirmPwInput.value = ''; + activitEl.classList.add('hidden'); + choosePwDiv.classList.remove('hidden'); + warning1El.innerHTML = `ERROR: Failed to launch Holochain.`; + setTimeout(() => choosePwInput.focus()); + } + } + }; + + continueBtn.addEventListener('click', () => { + choosePwDiv.classList.add('hidden'); + confirmPwDiv.classList.remove('hidden'); + confirmPwInput.focus(); + }); + + choosePwInput.addEventListener('input', (e) => { + password = choosePwInput.value; + if (!password) { + continueBtn.disabled = true; + } else { + continueBtn.disabled = false; + } + }); + + choosePwInput.addEventListener('keypress', async (e) => { + if (e.key === 'Enter') { + choosePwDiv.classList.add('hidden'); + confirmPwDiv.classList.remove('hidden'); + confirmPwInput.focus(); + } + }); + + confirmPwInput.addEventListener('input', (e) => { + confirmedPassword = confirmPwInput.value; + warning2El.innerHTML = ''; + if (!confirmedPassword) { + launchBtn.disabled = true; + } else { + launchBtn.disabled = false; + } + }); + + previousStepBtn.addEventListener('click', () => { + password = ''; + confirmedPassword = ''; + choosePwInput.value = ''; + confirmPwInput.value = ''; + warning2El.innerHTML = ''; + choosePwDiv.classList.remove('hidden'); + confirmPwDiv.classList.add('hidden'); + understoodDiv.classList.remove('red'); + understoodCheckbox.checked = false; + choosePwInput.focus(); + }); + + launchBtn.addEventListener('click', handleLaunch); + + confirmPwInput.addEventListener('keypress', async (e) => { + if (e.key === 'Enter') { + await handleLaunch(); + } + }); + </script> + </body> +</html> diff --git a/src/renderer/setupPasswordOptional.html b/src/renderer/setupPasswordOptional.html new file mode 100644 index 0000000..906f02f --- /dev/null +++ b/src/renderer/setupPasswordOptional.html @@ -0,0 +1,274 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="index.css" /> + </head> + <body> + <div id="app" class="splash-wrapper"> + <div class="splash-content-wrapper"> + <div> + <div id="productName" class="splash-product-name"></div> + <div id="version" class="splash-version"></div> + </div> + <div class="content column"> + <div class="column items-center" id="password-creation-container"> + <!-- Choose setup method --> + <div class="column items-center" id="choose-setup-div"> + <h2>Setup</h2> + <div + style="max-width: 700px; text-align: center; line-height: 1.5" + id="choose-setup-sentence" + ></div> + <div style="max-width: 500px; text-align: center; line-height: 1.5"> + The password will be used to encrypt your data locally on your device. + </div> + <div + style=" + max-width: 500px; + text-align: center; + line-height: 1.5; + font-weight: bold; + margin-bottom: 20px; + " + > + A password cannot be added, changed or removed later. + </div> + <button style="margin-bottom: 2px" id="with-password-btn">Setup With Password</button> + <button style="margin-top: 5px" id="without-password-btn"> + Setup Without Password + </button> + <div class="warning" id="warning-1" style="margin-top: 5px"></div> + </div> + + <!-- Choose password --> + <div class="column items-center hidden" id="choose-password-div"> + <button class="previous-step-btn" id="previous-step-btn-0">< previous step</button> + <h2>Choose a password</h2> + <div style="text-align: center; margin-bottom: 20px"> + This password will be used to encrypt your data and private keys on your device.<br /> + <b + >It cannot be reset and is not backed up by any central authority.<br /><u + >If you forget it you lose access to your data</u + ></b + > + </div> + <input + class="pw-input" + type="password" + id="choose-password-input" + placeholder="password" + /> + <div class="warning" id="warning-1"></div> + <button style="margin-top: 5px" id="continue-btn" disabled>Continue</button> + </div> + + <!-- Confirm password --> + <div class="column items-center hidden" id="confirm-password-div"> + <h2>Confirm password</h2> + <button class="previous-step-btn" id="previous-step-btn">< previous step</button> + <input + class="pw-input" + type="password" + id="confirm-password-input" + placeholder="password" + /> + <div class="warning" id="warning-2"></div> + <div + class="row" + style="max-width: 400px; align-items: flex-start" + id="understood-div" + > + <input type="checkbox" id="understood-checkbox" /> + <span style="margin-left: 5px" + >I understood that I cannot reset this password because there is no central + authority storing it for me and that + <b>I will lose access to my data if I forget it</b>.</span + > + </div> + <button style="margin-top: 5px" id="launch-btn" disabled>Launch</button> + </div> + </div> + + <!-- Loading message --> + <div class="splash-loading-message hidden" id="activity">Setting up Holochain...</div> + </div> + <div class="splash-license">Licensed under the Cryptographic Autonomy License v1.0</div> + </div> + <img + style="position: absolute; bottom: 5px; right: 10px; width: 300px" + src="img/powered_by_holochain.png" + /> + <img + tabindex="0" + id="close-btn" + title="close" + style="cursor: pointer; position: absolute; top: 5px; right: 5px; width: 35px" + src="img/close.svg" + /> + </div> + <script> + // TODO add event listener for holochain fatal panic case + + let password = ''; + let confirmedPassword = ''; + + (async () => { + const { productName, version } = await window.electronAPI.getNameAndVersion(); + document.getElementById('productName').innerHTML = productName; + document.getElementById('version').innerHTML = `version ${version}`; + document.getElementById( + 'choose-setup-sentence' + ).innerHTML = `Choose whether you want to set up ${productName} with or without a password.`; + })(); + + const activitEl = document.getElementById('activity'); + window.electronAPI.onProgressUpdate((event, msg) => { + console.log('status', msg); + activitEl.innerHTML = msg; + }); + const closeBtn = document.getElementById('close-btn'); + closeBtn.addEventListener('click', () => { + window.electronAPI.exit(); + }); + closeBtn.addEventListener('keypress', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + window.electronAPI.exit(); + } + }); + + const chooseSetupDiv = document.getElementById('choose-setup-div'); + const withPwBtn = document.getElementById('with-password-btn'); + const withoutPwBtn = document.getElementById('without-password-btn'); + const warning0El = document.getElementById('warning-0'); + + const choosePwDiv = document.getElementById('choose-password-div'); + const choosePwInput = document.getElementById('choose-password-input'); + const continueBtn = document.getElementById('continue-btn'); + const warning1El = document.getElementById('warning-1'); + const previousStepBtn0 = document.getElementById('previous-step-btn-0'); + + const confirmPwDiv = document.getElementById('confirm-password-div'); + const confirmPwInput = document.getElementById('confirm-password-input'); + const launchBtn = document.getElementById('launch-btn'); + const warning2El = document.getElementById('warning-2'); + const previousStepBtn = document.getElementById('previous-step-btn'); + const understoodDiv = document.getElementById('understood-div'); + const understoodCheckbox = document.getElementById('understood-checkbox'); + + const handleLaunch = async () => { + if (password !== confirmedPassword) { + warning2El.innerHTML = "Passwords don't match!"; + confirmPwInput.focus(); + } else if (!understoodCheckbox.checked) { + understoodDiv.classList.add('red'); + } else { + confirmPwDiv.classList.add('hidden'); + activitEl.classList.remove('hidden'); + try { + await window.electronAPI.launch({ type: "user-provided", password }); + } catch (e) { + console.error('Failed to launch: ', e); + activitEl.innerHTML = ''; + password = ''; + choosePwInput.value = ''; + confirmedPassword = ''; + confirmPwInput.value = ''; + activitEl.classList.add('hidden'); + confirmPwDiv.classList.remove('hidden'); + warning1El.innerHTML = `ERROR: Failed to launch Holochain.`; + setTimeout(() => choosePwInput.focus()); + } + } + }; + + withPwBtn.addEventListener('click', () => { + chooseSetupDiv.classList.add('hidden'); + choosePwDiv.classList.remove('hidden'); + }); + + previousStepBtn0.addEventListener('click', () => { + password = ''; + confirmedPassword = ''; + choosePwInput.value = ''; + confirmPwInput.value = ''; + choosePwDiv.classList.add('hidden'); + chooseSetupDiv.classList.remove('hidden'); + }); + + withoutPwBtn.addEventListener('click', async () => { + try { + chooseSetupDiv.classList.add('hidden'); + activitEl.classList.remove('hidden'); + await window.electronAPI.launch({ type: 'random' }); + } catch (e) { + console.error('Failed to launch: ', e); + activitEl.innerHTML = ''; + password = ''; + choosePwInput.value = ''; + confirmedPassword = ''; + confirmPwInput.value = ''; + activitEl.classList.add('hidden'); + chooseSetupDiv.classList.remove('hidden'); + warning1El.innerHTML = `ERROR: Failed to launch Holochain.`; + setTimeout(() => choosePwInput.focus()); + confirmPwInput.focus(); + } + }); + + continueBtn.addEventListener('click', () => { + choosePwDiv.classList.add('hidden'); + confirmPwDiv.classList.remove('hidden'); + confirmPwInput.focus(); + }); + + choosePwInput.addEventListener('input', (e) => { + password = choosePwInput.value; + if (!password) { + continueBtn.disabled = true; + } else { + continueBtn.disabled = false; + } + }); + + choosePwInput.addEventListener('keypress', async (e) => { + if (e.key === 'Enter') { + choosePwDiv.classList.add('hidden'); + confirmPwDiv.classList.remove('hidden'); + confirmPwInput.focus(); + } + }); + + confirmPwInput.addEventListener('input', (e) => { + confirmedPassword = confirmPwInput.value; + warning2El.innerHTML = ''; + if (!confirmedPassword) { + launchBtn.disabled = true; + } else { + launchBtn.disabled = false; + } + }); + + previousStepBtn.addEventListener('click', () => { + password = ''; + confirmedPassword = ''; + choosePwInput.value = ''; + confirmPwInput.value = ''; + warning2El.innerHTML = ''; + choosePwDiv.classList.remove('hidden'); + confirmPwDiv.classList.add('hidden'); + understoodDiv.classList.remove('red'); + understoodCheckbox.checked = false; + choosePwInput.focus(); + }); + + launchBtn.addEventListener('click', handleLaunch); + + confirmPwInput.addEventListener('keypress', async (e) => { + if (e.key === 'Enter') { + await handleLaunch(); + } + }); + </script> + </body> +</html> diff --git a/templates/electron-builder-template.yml b/templates/electron-builder-template.yml index 94ae534..7aefebb 100644 --- a/templates/electron-builder-template.yml +++ b/templates/electron-builder-template.yml @@ -19,10 +19,10 @@ mac: entitlementsInherit: build/entitlements.mac.plist # Uncomment selectively for your application's purposes # extendInfo: - # - NSCameraUsageDescription: Application requests access to the device's camera. - # - NSMicrophoneUsageDescription: Application requests access to the device's microphone. - # - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. - # - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + # - NSCameraUsageDescription: Application requests access to the device's camera. + # - NSMicrophoneUsageDescription: Application requests access to the device's microphone. + # - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. + # - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. notarize: false dmg: artifactName: ${name}-${version}.${ext} diff --git a/yarn.lock b/yarn.lock index 5c8d131..588ce88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -570,10 +570,10 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@holochain/client@0.17.1": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@holochain/client/-/client-0.17.1.tgz#c3b1e0296e1a226a1b2a68fc106ce48cc9bc1820" - integrity sha512-Jvh6DN+OdktV3KQH+tioRzvPT+LWJFBp1klMFG9vaccphIVvJN1LepJfRB8OyxW5iGAZ/TFCTwF19XSJuM1fQQ== +"@holochain/client@0.18.0-rc.1": + version "0.18.0-rc.1" + resolved "https://registry.yarnpkg.com/@holochain/client/-/client-0.18.0-rc.1.tgz#35f67657858593ef380dfccca200ee366a451407" + integrity sha512-01Xh5cpN0lHdtV41V2RtCUfTFEA9K9GWK5vvMPtdqj52WcSNO6gcrfVERKgSG/su8xJ3VmULPYNQyrEMmBReww== dependencies: "@bitgo/blake2b" "^3.2.4" "@holochain/serialization" "^0.1.0-beta-rc.3" @@ -585,35 +585,35 @@ lodash-es "^4.17.21" ws "^8.14.2" -"@holochain/hc-spin-rust-utils-darwin-arm64@0.300.1": - version "0.300.1" - resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-darwin-arm64/-/hc-spin-rust-utils-darwin-arm64-0.300.1.tgz#e183439230a6e98c7c1400d76aa19076d3c55ac7" - integrity sha512-IOt+hTVRByaqpzuZ+pN7tTAqNPXsPxE6oc+Hqjo5D4aWP3oeD2b6bEEDRfBicMuUTp7spIqKgT/4Dj0pQtLvyw== - -"@holochain/hc-spin-rust-utils-darwin-x64@0.300.1": - version "0.300.1" - resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-darwin-x64/-/hc-spin-rust-utils-darwin-x64-0.300.1.tgz#bd2c7fcec4d5f23e0cf6d406dc63f7ccfd22eef0" - integrity sha512-ffxAB9o5XhvwcXi1F6MsURoxhxJbo3HvlBpD0F2VNfyv/zCd15gb8HvcskhcvQFKlCEHigRrilOxHP5Aks8I3w== - -"@holochain/hc-spin-rust-utils-linux-x64-gnu@0.300.1": - version "0.300.1" - resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-linux-x64-gnu/-/hc-spin-rust-utils-linux-x64-gnu-0.300.1.tgz#410a59bf28974cd7361785bc0631d15186be7a49" - integrity sha512-SKUJdvKIRkqixgaChnkLvBNWgZgFGLI5tNeV/8WMdxCKqD2kLJzUFSIafl1arck9964iSfsCRMG8/5fpfE+sZg== - -"@holochain/hc-spin-rust-utils-win32-x64-msvc@0.300.1": - version "0.300.1" - resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-win32-x64-msvc/-/hc-spin-rust-utils-win32-x64-msvc-0.300.1.tgz#abdd794bc44a62d2b3c47953eb4277369a5a3ee0" - integrity sha512-S+PcygFnkVbR1/5FF66P7gBju3+PfOd1BLgfDpF6kghSJfb3qSN1h93vPujxeoQcs1qtT3DqMarG2BmyCe9RPA== - -"@holochain/hc-spin-rust-utils@0.300.1": - version "0.300.1" - resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils/-/hc-spin-rust-utils-0.300.1.tgz#12da10b356c1adb9254305a6c3e774fad2c681ad" - integrity sha512-vMC6cgqJ8gOT7dGunTyYd6frqax+XQO9v4ZB5YL6amws9xu9lL6OqG1igNJfGimAbqCaQTd1bq8Ctr+HF8AYyg== +"@holochain/hc-spin-rust-utils-darwin-arm64@0.400.0": + version "0.400.0" + resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-darwin-arm64/-/hc-spin-rust-utils-darwin-arm64-0.400.0.tgz#7773c68bb0e9d040b0c08b4979fa84d04a2755d8" + integrity sha512-FL/pnutrsj7AfIyml4flM2FpMMjm6KCDfaR+e+xB9d4dyFxAc1h9YcuUDHZkAGBhqCiLJ5FtiIOHqu770XCQIg== + +"@holochain/hc-spin-rust-utils-darwin-x64@0.400.0": + version "0.400.0" + resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-darwin-x64/-/hc-spin-rust-utils-darwin-x64-0.400.0.tgz#2ae2a28e07844a74e94c41500784ba4bf146346a" + integrity sha512-FThpisR49A/lMFA1yv5IDrCSZQe7/s48xRf85iVNYl1aUM/ftKOcEFzwEQe+cN+nzJLhi8vJzZDAyBVdCKB7Mg== + +"@holochain/hc-spin-rust-utils-linux-x64-gnu@0.400.0": + version "0.400.0" + resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-linux-x64-gnu/-/hc-spin-rust-utils-linux-x64-gnu-0.400.0.tgz#464239e843d55eaf3bd39e4c957fe0e7cadcb248" + integrity sha512-13d/Wn0/73OdQ+B8Zf8xM3naiZlqIva/8rQvgUQTRQszgMrIuTz64hZ0goAG0rhxR+TzfY3MPUys17KfWIjSBw== + +"@holochain/hc-spin-rust-utils-win32-x64-msvc@0.400.0": + version "0.400.0" + resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils-win32-x64-msvc/-/hc-spin-rust-utils-win32-x64-msvc-0.400.0.tgz#735b1470e6a675f11ad8588f02b6fa42ca0e4e30" + integrity sha512-5/GxzAEAQyVy9ZXF6Py+jUA3d5RktjhYSWx+FF6OqOkTKmkg5e/XX8srDFWdL5GEi/gW/47NRZVqdjD3LYvjEA== + +"@holochain/hc-spin-rust-utils@0.400.0": + version "0.400.0" + resolved "https://registry.yarnpkg.com/@holochain/hc-spin-rust-utils/-/hc-spin-rust-utils-0.400.0.tgz#6215f2b2ff6d8e083e17d1cc248b0af210e7fc79" + integrity sha512-yNfrqAD3l7rgr2abhuPcb/7/zi4VR4QQCCJMvsE6rwl2D2NtFC0PMiBk34kVCdABQqJyFRgaPoDAA0ll9Yic2Q== optionalDependencies: - "@holochain/hc-spin-rust-utils-darwin-arm64" "0.300.1" - "@holochain/hc-spin-rust-utils-darwin-x64" "0.300.1" - "@holochain/hc-spin-rust-utils-linux-x64-gnu" "0.300.1" - "@holochain/hc-spin-rust-utils-win32-x64-msvc" "0.300.1" + "@holochain/hc-spin-rust-utils-darwin-arm64" "0.400.0" + "@holochain/hc-spin-rust-utils-darwin-x64" "0.400.0" + "@holochain/hc-spin-rust-utils-linux-x64-gnu" "0.400.0" + "@holochain/hc-spin-rust-utils-win32-x64-msvc" "0.400.0" "@holochain/serialization@^0.1.0-beta-rc.3": version "0.1.0-beta-rc.3" @@ -651,6 +651,279 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@jimp/core@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-1.6.0.tgz#3ef241bf02f40431bb382aea665e5187a2c05eef" + integrity sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w== + dependencies: + "@jimp/file-ops" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + await-to-js "^3.0.0" + exif-parser "^0.1.12" + file-type "^16.0.0" + mime "3" + +"@jimp/diff@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/diff/-/diff-1.6.0.tgz#f8d058bfad64751c5e5c135499d1a784f797c5c8" + integrity sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw== + dependencies: + "@jimp/plugin-resize" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + pixelmatch "^5.3.0" + +"@jimp/file-ops@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/file-ops/-/file-ops-1.6.0.tgz#ae9c6aa65b2c9a5a16515a8fdf83b55f51100087" + integrity sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ== + +"@jimp/js-bmp@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/js-bmp/-/js-bmp-1.6.0.tgz#ff7c4306e764745063e249ee926d0dd807924abf" + integrity sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + bmp-ts "^1.0.9" + +"@jimp/js-gif@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/js-gif/-/js-gif-1.6.0.tgz#0efa5d83317a89d6eda936e2ae1df2b7d122a38d" + integrity sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + gifwrap "^0.10.1" + omggif "^1.0.10" + +"@jimp/js-jpeg@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz#e47da6758346548079f0ac8ff215d0d9d1ec435e" + integrity sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + jpeg-js "^0.4.4" + +"@jimp/js-png@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/js-png/-/js-png-1.6.0.tgz#c857adfdbfcb7107a6511c3b2939ffbad0fefedc" + integrity sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + pngjs "^7.0.0" + +"@jimp/js-tiff@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/js-tiff/-/js-tiff-1.6.0.tgz#f18fa3d59f52fda339acfdcadbe7363bed912e81" + integrity sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + utif2 "^4.1.0" + +"@jimp/plugin-blit@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz#fed35aefbb5757599a4299a9ff6c06cc3466f46f" + integrity sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA== + dependencies: + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-blur@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz#781b3be9de2744e5eb6ab86ec05ee7d2ce5092e8" + integrity sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/utils" "1.6.0" + +"@jimp/plugin-circle@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz#2314dc7955068cb4a000de4eceb02890eb131c88" + integrity sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw== + dependencies: + "@jimp/types" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-color@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-1.6.0.tgz#927c83ee932070ad285266840728c21ac39bf27b" + integrity sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + tinycolor2 "^1.6.0" + zod "^3.23.8" + +"@jimp/plugin-contain@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz#d08900ecf85ac564a6f9f3fc0d61cc8d5e43626e" + integrity sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/plugin-blit" "1.6.0" + "@jimp/plugin-resize" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-cover@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz#07ffb2f3d6ac53616c66f1131cd66ced17e3ca3e" + integrity sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/plugin-crop" "1.6.0" + "@jimp/plugin-resize" "1.6.0" + "@jimp/types" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-crop@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz#59f2b20869330fd768d1743d845b8ba9ed9bc52a" + integrity sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-displace@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz#41b3257a6c0f64c749c29c1a2e64ba7df31a7a25" + integrity sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q== + dependencies: + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-dither@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz#10c17070dcbec565904f11b7986e90ae20850b6f" + integrity sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ== + dependencies: + "@jimp/types" "1.6.0" + +"@jimp/plugin-fisheye@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz#2831c0060598b27bf004bf8a70adfeec003d4fcc" + integrity sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA== + dependencies: + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-flip@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz#75c87bdb0f0ca9db44b320cc9671aa201e52b5c3" + integrity sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg== + dependencies: + "@jimp/types" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-hash@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz#8de89dfbbb6be671f9cdb2b59816acf3f07c4298" + integrity sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/js-bmp" "1.6.0" + "@jimp/js-jpeg" "1.6.0" + "@jimp/js-png" "1.6.0" + "@jimp/js-tiff" "1.6.0" + "@jimp/plugin-color" "1.6.0" + "@jimp/plugin-resize" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + any-base "^1.1.0" + +"@jimp/plugin-mask@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz#2b5a437e5d9a9906dcabb7a7baf4d5cd7d2361b1" + integrity sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA== + dependencies: + "@jimp/types" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-print@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-1.6.0.tgz#ccef327f53afb47617aa66ca65435447380faf34" + integrity sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/js-jpeg" "1.6.0" + "@jimp/js-png" "1.6.0" + "@jimp/plugin-blit" "1.6.0" + "@jimp/types" "1.6.0" + parse-bmfont-ascii "^1.0.6" + parse-bmfont-binary "^1.0.6" + parse-bmfont-xml "^1.1.6" + simple-xml-to-json "^1.2.2" + zod "^3.23.8" + +"@jimp/plugin-quantize@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz#880095fc0ead41321d94bf54895e366dd7d079d6" + integrity sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg== + dependencies: + image-q "^4.0.0" + zod "^3.23.8" + +"@jimp/plugin-resize@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz#331e8912ed68746846145019bc6e2ea057e6f175" + integrity sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/types" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-rotate@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz#de271f39a3ac9e853b02e01d3d44ab086d12e099" + integrity sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/plugin-crop" "1.6.0" + "@jimp/plugin-resize" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/plugin-threshold@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz#11479cf59131ea95dcaff6a1403af1964593a3fa" + integrity sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/plugin-color" "1.6.0" + "@jimp/plugin-hash" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + zod "^3.23.8" + +"@jimp/types@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-1.6.0.tgz#27022730fd673653e1430e6bd8ac6f6de1596f89" + integrity sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg== + dependencies: + zod "^3.23.8" + +"@jimp/utils@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-1.6.0.tgz#e196f3953ea1ebc88f50cf0d490adb24aeffe596" + integrity sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA== + dependencies: + "@jimp/types" "1.6.0" + tinycolor2 "^1.6.0" + "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -691,35 +964,35 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@lightningrodlabs/we-rust-utils-darwin-arm64@0.300.2": - version "0.300.2" - resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-darwin-arm64/-/we-rust-utils-darwin-arm64-0.300.2.tgz#d2c6915fa0bbc957e18c01ff989d8ba5b38a1596" - integrity sha512-2QSeR3O42xSNQBPHG4y+marCPflmrXIBITK5eWNv9bwWteWdbUWSO9ZoEayzFexz1ZqqGqF4zbM78sj+CmLmNQ== - -"@lightningrodlabs/we-rust-utils-darwin-x64@0.300.2": - version "0.300.2" - resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-darwin-x64/-/we-rust-utils-darwin-x64-0.300.2.tgz#b3e31e0e664bb1f40d5a92ebec4ca658e3363a0f" - integrity sha512-a/UuqTTP1Te2UMAZKszCpgpkoTnB1QSHLBb6+83aV1P/1bX/OHhFSdhVOg3qtkECk3xXZdRC4Z2q/+WORODs6w== - -"@lightningrodlabs/we-rust-utils-linux-x64-gnu@0.300.2": - version "0.300.2" - resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-linux-x64-gnu/-/we-rust-utils-linux-x64-gnu-0.300.2.tgz#8174093f392efc47e3e5c716f0696312d5fcaae1" - integrity sha512-rhPP1Ua0n4kCO0DtiUl9KfxdLNAgU5HqmYhSt4J2aWcsrG4dGUTx5MHtPjC0xU0c5+ccr0x8iwMmbkEl1PWjPA== - -"@lightningrodlabs/we-rust-utils-win32-x64-msvc@0.300.2": - version "0.300.2" - resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-win32-x64-msvc/-/we-rust-utils-win32-x64-msvc-0.300.2.tgz#60b635155ddfe88b6d58b0c73d21d3fdff732e6b" - integrity sha512-D4jkfaFylA03jz/cGVXViQkSfXUPys3aaCRR/JbsfpvWEvr0D3TR9DIqF1qU7eWQ7rWDLocpBDSXz+f4/auxNQ== - -"@lightningrodlabs/we-rust-utils@0.300.2": - version "0.300.2" - resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils/-/we-rust-utils-0.300.2.tgz#6681b1fb372c2991c665563c3ecc53d2d4190585" - integrity sha512-+NSe3MSbKQFiArxRQn66mzRkNi/r4B9SAhwzHAT0k2o4tVRbHQ9SeO4V/aXq0VpE83pAEgaRUo9mAkp9ZSkhhw== +"@lightningrodlabs/we-rust-utils-darwin-arm64@0.400.1": + version "0.400.1" + resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-darwin-arm64/-/we-rust-utils-darwin-arm64-0.400.1.tgz#4c024f18326dee2b7ed8cb9bba4503be579693e4" + integrity sha512-gBmkFmioy8oXN5iQySGzZq8tjfJ2SGs6X9flp3vsbYgX1gtIqFSUTsdxJz+ZjIzxsH2Jvj8Kz++/wGVuT09dPg== + +"@lightningrodlabs/we-rust-utils-darwin-x64@0.400.1": + version "0.400.1" + resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-darwin-x64/-/we-rust-utils-darwin-x64-0.400.1.tgz#4518eed480e2e6edd94d2e5625706f7aa29e62c1" + integrity sha512-sBagev9/+YKR7wLYMOBUyZZzWMgZDieTnT7fiP5AGM1iwJ2CLMgAIQ0xiaYQFDCQ3rLg7tUdX+2iMxLrtTtQ/w== + +"@lightningrodlabs/we-rust-utils-linux-x64-gnu@0.400.1": + version "0.400.1" + resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-linux-x64-gnu/-/we-rust-utils-linux-x64-gnu-0.400.1.tgz#9cdeb4b895c0486e30203bdac5de226132cf5587" + integrity sha512-KirgXwKi75mtVGnUzj4aYUN9US0yNM+Az0+vAD8rubPSysHV+47HL1GWLAOHqHwvQ0fT6VWOKB4qSkngJP2b7A== + +"@lightningrodlabs/we-rust-utils-win32-x64-msvc@0.400.1": + version "0.400.1" + resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils-win32-x64-msvc/-/we-rust-utils-win32-x64-msvc-0.400.1.tgz#2113e12e73f10b5498b184580f96985477bf914c" + integrity sha512-t/p+Nzt+LpIJqo4oM6ofO0Kj51ptWQWzisWX3/5Ch+NDUaKZp3RODzLok+pM1vvoxsL+eyt3MaOLo5oNC8UgUg== + +"@lightningrodlabs/we-rust-utils@0.400.1": + version "0.400.1" + resolved "https://registry.yarnpkg.com/@lightningrodlabs/we-rust-utils/-/we-rust-utils-0.400.1.tgz#d2417b7bdb3abffe4d39710a497b74a6d4e35e99" + integrity sha512-Dv86gqeVHg+RATe5LO3uVdElqU2SgQxD8PMc3Ao+g2CQRK/RBc5ls2CGAxFDoTlTFJgVpqT65I6BSgayCIeU7g== optionalDependencies: - "@lightningrodlabs/we-rust-utils-darwin-arm64" "0.300.2" - "@lightningrodlabs/we-rust-utils-darwin-x64" "0.300.2" - "@lightningrodlabs/we-rust-utils-linux-x64-gnu" "0.300.2" - "@lightningrodlabs/we-rust-utils-win32-x64-msvc" "0.300.2" + "@lightningrodlabs/we-rust-utils-darwin-arm64" "0.400.1" + "@lightningrodlabs/we-rust-utils-darwin-x64" "0.400.1" + "@lightningrodlabs/we-rust-utils-linux-x64-gnu" "0.400.1" + "@lightningrodlabs/we-rust-utils-win32-x64-msvc" "0.400.1" "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" @@ -1020,6 +1293,11 @@ dependencies: defer-to-connect "^2.0.0" +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -1126,6 +1404,11 @@ dependencies: undici-types "~6.19.2" +"@types/node@16.9.1": + version "16.9.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708" + integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== + "@types/node@^20.9.0": version "20.16.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.10.tgz#0cc3fdd3daf114a4776f54ba19726a01c907ef71" @@ -1435,6 +1718,11 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +any-base@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" + integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== + app-builder-bin@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0" @@ -1596,6 +1884,11 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +await-to-js@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/await-to-js/-/await-to-js-3.0.0.tgz#70929994185616f4675a91af6167eb61cc92868f" + integrity sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -1623,6 +1916,11 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bmp-ts@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/bmp-ts/-/bmp-ts-1.0.9.tgz#0fd124ba812be9b786b29e5b186ee76d74ff5538" + integrity sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw== + boolean@^3.0.1: version "3.2.0" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" @@ -1911,6 +2209,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" @@ -2621,6 +2924,11 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +exif-parser@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" + integrity sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw== + ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -2709,6 +3017,15 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-type@^16.0.0: + version "16.5.4" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" + integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" + filelist@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" @@ -2881,6 +3198,14 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +gifwrap@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.10.1.tgz#9ed46a5d51913b482d4221ce9c727080260b681e" + integrity sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw== + dependencies: + image-q "^4.0.0" + omggif "^1.0.10" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3110,6 +3435,13 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +image-q@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/image-q/-/image-q-4.0.0.tgz#31e075be7bae3c1f42a85c469b4732c358981776" + integrity sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw== + dependencies: + "@types/node" "16.9.1" + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -3352,6 +3684,44 @@ jake@^10.8.5: filelist "^1.0.4" minimatch "^3.1.2" +jimp@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-1.6.0.tgz#7c7e5133c8dc06706e1ed35e771c685af393bfd2" + integrity sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg== + dependencies: + "@jimp/core" "1.6.0" + "@jimp/diff" "1.6.0" + "@jimp/js-bmp" "1.6.0" + "@jimp/js-gif" "1.6.0" + "@jimp/js-jpeg" "1.6.0" + "@jimp/js-png" "1.6.0" + "@jimp/js-tiff" "1.6.0" + "@jimp/plugin-blit" "1.6.0" + "@jimp/plugin-blur" "1.6.0" + "@jimp/plugin-circle" "1.6.0" + "@jimp/plugin-color" "1.6.0" + "@jimp/plugin-contain" "1.6.0" + "@jimp/plugin-cover" "1.6.0" + "@jimp/plugin-crop" "1.6.0" + "@jimp/plugin-displace" "1.6.0" + "@jimp/plugin-dither" "1.6.0" + "@jimp/plugin-fisheye" "1.6.0" + "@jimp/plugin-flip" "1.6.0" + "@jimp/plugin-hash" "1.6.0" + "@jimp/plugin-mask" "1.6.0" + "@jimp/plugin-print" "1.6.0" + "@jimp/plugin-quantize" "1.6.0" + "@jimp/plugin-resize" "1.6.0" + "@jimp/plugin-rotate" "1.6.0" + "@jimp/plugin-threshold" "1.6.0" + "@jimp/types" "1.6.0" + "@jimp/utils" "1.6.0" + +jpeg-js@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== + js-base64@^3.7.5: version "3.7.7" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" @@ -3634,6 +4004,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +mime@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mime@^2.5.2: version "2.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" @@ -3865,6 +4240,11 @@ octokit@4.0.2: "@octokit/request-error" "^6.0.0" "@octokit/types" "^13.0.0" +omggif@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" + integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3915,6 +4295,11 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== +pako@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -3922,6 +4307,24 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-bmfont-ascii@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz#11ac3c3ff58f7c2020ab22769079108d4dfa0285" + integrity sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA== + +parse-bmfont-binary@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz#d038b476d3e9dd9db1e11a0b0e53a22792b69006" + integrity sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA== + +parse-bmfont-xml@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz#016b655da7aebe6da38c906aca16bf0415773767" + integrity sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA== + dependencies: + xml-parse-from-string "^1.0.0" + xml2js "^0.5.0" + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -3965,6 +4368,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +peek-readable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" + integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -3980,6 +4388,13 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pixelmatch@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.3.0.tgz#5e5321a7abedfb7962d60dbf345deda87cb9560a" + integrity sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q== + dependencies: + pngjs "^6.0.0" + plist@^3.0.4, plist@^3.0.5: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" @@ -3994,6 +4409,16 @@ png2icons@2.0.1: resolved "https://registry.yarnpkg.com/png2icons/-/png2icons-2.0.1.tgz#09d8f10b71302e98ca178d3324bc4deff9b90124" integrity sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA== +pngjs@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" + integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== + +pngjs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-7.0.0.tgz#a8b7446020ebbc6ac739db6c5415a65d17090e26" + integrity sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow== + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -4099,7 +4524,7 @@ read-pkg@^6.0.0: parse-json "^5.2.0" type-fest "^1.0.1" -readable-stream@^3.4.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -4119,6 +4544,13 @@ readable-stream@^4.5.2: process "^0.11.10" string_decoder "^1.3.0" +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + redent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9" @@ -4259,7 +4691,7 @@ sanitize-filename@^1.6.3: dependencies: truncate-utf8-bytes "^1.0.0" -sax@^1.2.4: +sax@>=0.6.0, sax@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== @@ -4274,7 +4706,7 @@ semver@^6.2.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -4349,6 +4781,11 @@ simple-update-notifier@2.0.0: dependencies: semver "^7.5.3" +simple-xml-to-json@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz#79c7188ff99ae209a267b70ee0db06b0e4597787" + integrity sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -4448,16 +4885,7 @@ stat-mode@^1.0.0: resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4510,14 +4938,7 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4548,6 +4969,14 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strtok3@^6.2.4: + version "6.3.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" + integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.1.0" + sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -4622,6 +5051,14 @@ tiny-typed-emitter@^2.1.0: resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA== +<<<<<<< HEAD +======= +tinycolor2@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + +>>>>>>> main tmp-promise@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" @@ -4646,6 +5083,14 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +token-types@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" + integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + trim-newlines@^4.0.2: version "4.1.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.1.1.tgz#28c88deb50ed10c7ba6dc2474421904a00139125" @@ -4855,6 +5300,13 @@ utf8-byte-length@^1.0.1: resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== +utif2@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/utif2/-/utif2-4.1.0.tgz#e768d37bd619b995d56d9780b5d2b4611a3d932b" + integrity sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w== + dependencies: + pako "^1.0.11" + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4953,16 +5405,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -4990,11 +5433,29 @@ ws@^8.14.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +xml-parse-from-string@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" + integrity sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g== + +xml2js@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" @@ -5050,3 +5511,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.23.8: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==