diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index eaf98f7..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,38 +0,0 @@
-module.exports = {
- root: true,
- env: {
- node: true
- },
- extends: [
- "plugin:vue/vue3-essential",
- "eslint:recommended",
- "@vue/typescript/recommended"
- ],
- parserOptions: {
- ecmaVersion: 2020
- },
- rules: {
- "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
- "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": [
- "error",
- { "argsIgnorePattern": "^_" }
- ]
- },
- overrides: [
- {
- files: [
- "**/__tests__/*.{j,t}s?(x)",
- "**/tests/unit/**/*.spec.{j,t}s?(x)",
- "*.js"
- ],
- rules: {
- "@typescript-eslint/no-var-requires": "off",
- },
- env: {
- jest: true
- }
- }
- ]
-};
diff --git a/.gitignore b/.gitignore
index 20128fc..f8cd566 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,5 @@
.DS_Store
-node_modules
-/dist
-/crate/target/*
+/node_modules
/.log
crash.log
@@ -13,8 +11,6 @@ crash.log
# Log files
npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
pnpm-debug.log*
# Editor directories and files
@@ -26,12 +22,8 @@ pnpm-debug.log*
*.sln
*.sw?
-#Electron-builder output
-/dist_electron
+# Dist
+*.app
-#Electron-builder config
-electron-builder.yml
-
-#Window and session states
-session.json
-window.json
+*.sublime-workspace
+*.sublime-project
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 2c0d22c..0000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-stages:
- - Build
- - Upload
- - Release
-
-build:
- stage: Build
- tags:
- # Use a macos runner to build.
- - darwin
- before_script:
- - yarn
- script:
- - export VERSION=$(node -e "console.log(require('./package.json').version)")
- - echo "VERSION=$VERSION" >> variables.env
- - export APPNAME=$(node -e "console.log(require('./package.json').productName)")
- - echo "APPNAME=$APPNAME" >> variables.env
- - yarn build
- artifacts:
- reports:
- dotenv: variables.env
- name: $CI_COMMIT_REF_SLUG
- paths:
- - dist_electron/*.dmg
- - dist_electron/*.zip
- - dist_electron/*.yml
- when: on_success
- only:
- - main
-
-variables:
- PACKAGE: '${APPNAME}-${VERSION}.dmg'
- PACKAGE_REGISTRY_URL: '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${APPNAME}/${VERSION}'
-
-# Upload package to gitlab registry.
-upload:
- stage: Upload
- needs:
- - job: build
- artifacts: true
- rules:
- - if: $CI_COMMIT_TAG
- when: never # Do not run this job when a tag is created manually
- - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when when commits are pushed to the default branch
- script:
- # Take the package we built and place it in the package registry.
- - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "dist_electron/${PACKAGE}" "${PACKAGE_REGISTRY_URL}/${PACKAGE}"'
-
-auto-release-master:
- image: registry.gitlab.com/gitlab-org/release-cli
- needs:
- - job: build
- artifacts: true
- - job: upload
- artifacts: true
- stage: Release
- rules:
- - if: $CI_COMMIT_TAG
- when: never # Do not run this job when a tag is created manually
- - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when when commits are pushed to the default branch
- script:
- - echo "Release $VERSION"
- - release-cli create --name "Release $VERSION" --tag-name v$VERSION --description "Release $CI_COMMIT_TITLE" --ref $CI_COMMIT_SHA --assets-link "{\"name\":\"${APPNAME}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${PACKAGE}\"}"
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..ce46d51
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+@crimata:registry=https://gitlab.com/api/v4/projects/28849281/packages/npm/
+//gitlab.com/api/v4/projects/28849281/packages/npm/:_authToken=${API_TOKEN}
\ No newline at end of file
diff --git a/README.md b/README.md
index c13a8ab..e9fefb0 100644
--- a/README.md
+++ b/README.md
@@ -27,3 +27,40 @@ yarn lint
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
+
+## Sign and Notarize
+
+Prerequisites:
+* Apple Developer Account ($99)
+* Valid Apple Developer ID Application Certificate on keychain
+
+Clear debris from app before signing:
+
+ xattr -cr Electron.app
+
+Sign and Notarize:
+
+ node notarize.js
+
+Debug electron-osx-sign:
+
+ export DEBUG=electron-osx-sign*
+
+### Some things to note:
+
+Notarize only when distributing outside the Mac App Store.
+
+#### Gatekeeper Assess
+Electron-osx-sign seems to have a bug where the sign will fail if "gatekeeper-assess" is true (default).
+
+#### Entitlements.plist
+Not to be confused with Info.plist, this file specifies entitlements the app can have in hardened runtime (e.g. Can I use the microphone?). Only including the ones Electron reccommends seems to work (including audio of course and omitting allow-unsigned-executable-memory).
+
+#### The Benifit of Electron OSX Sign
+Without this package, we would have to manually go in and figure out how to sign each level of the app. While it didn't work out of the box (gatekeeper-assess) it seems to be good otherwise. However, there is a bug where it puts multiple runtime flags on each command - not catastrophic though.
+
+#### Apple Password
+This must be an app specific password, not your normal Apple ID password.
+
+#### --Signiture-Flags OSX Sign
+Not exactly sure what this does or why its important, but Electron includes it.
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index e955840..0000000
--- a/babel.config.js
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = {
- presets: [
- '@vue/cli-plugin-babel/preset'
- ]
-}
diff --git a/entitlements.plist b/entitlements.plist
new file mode 100644
index 0000000..0555172
--- /dev/null
+++ b/entitlements.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.debugger
+
+ com.apple.security.device.audio-input
+
+
+
+
diff --git a/icon.png b/icon.png
new file mode 100644
index 0000000..2897b98
Binary files /dev/null and b/icon.png differ
diff --git a/jest.config.js b/jest.config.js
deleted file mode 100644
index d7f13cc..0000000
--- a/jest.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
- transform: {
- '^.+\\.vue$': 'vue-jest'
- }
-}
diff --git a/jsconfig.json b/jsconfig.json
deleted file mode 100644
index 5df65d0..0000000
--- a/jsconfig.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "typeAcquisition": {
-
- "enable": true
- }
-}
\ No newline at end of file
diff --git a/notarize.js b/notarize.js
new file mode 100644
index 0000000..cd3b19a
--- /dev/null
+++ b/notarize.js
@@ -0,0 +1,52 @@
+const exec = require('child_process').exec;
+const sign = require("electron-osx-sign").signAsync;
+const notarize = require("electron-notarize").notarize;
+
+const app = "Crimata.app";
+const dir = `${app}/Contents/Resources/app`;
+
+const runShellCommand = (cmd) => (new Promise((resolve, reject) => {
+ exec(cmd, (error, stdout, stderr) => {
+ error ? reject(stderr) : resolve(stdout);
+ });
+}));
+
+const signConfig = {
+ "app": app,
+ "hardened-runtime": true,
+ "gatekeeper-assess": false,
+ "signature-flags": "library",
+ "entitlements": "entitlements.plist",
+ "entitlements-inherit": "entitlements.plist",
+};
+
+const notarizeConfig = {
+ appPath: app,
+ appleId: "gundersena@crimata.com",
+ appBundleId: "com.crimata.CrimataMessenger",
+ appleIdPassword: process.env["AC_PASSWORD"]
+};
+
+(async function () {
+
+ try
+ {
+ console.log("Building...");
+ await runShellCommand(`rm -rf ${dir} && mkdir ${dir} && cp -r {package.json,src,node_modules} ${dir}
+ `);
+
+ console.log("Cleaning...");
+ await runShellCommand(`xattr -cr ${app}`);
+
+ console.log("Signing...");
+ await sign(signConfig);
+
+ console.log("Notarizing...");
+ await notarize(notarizeConfig);
+ }
+ catch (e)
+ {
+ console.log(e);
+ }
+
+})();
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..cc9403e
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1059 @@
+{
+ "name": "Crimata",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "Crimata",
+ "version": "1.0.0",
+ "dependencies": {
+ "@crimata/nodeaudio": "0.0.0",
+ "axios": "^0.21.1",
+ "base64-arraybuffer": "^1.0.1",
+ "electron-store": "^8.0.0",
+ "ws": "^7.3.1"
+ },
+ "devDependencies": {
+ "electron-notarize": "^1.1.1",
+ "electron-osx-sign": "^0.6.0"
+ }
+ },
+ "node_modules/@crimata/nodeaudio": {
+ "version": "0.0.0",
+ "resolved": "https://gitlab.com/api/v4/projects/28849281/packages/npm/@crimata/nodeaudio/-/@crimata/nodeaudio-0.0.0.tgz",
+ "integrity": "sha1-4lPe1QynEwebXcjezyAPhfwI+VM=",
+ "hasInstallScript": true,
+ "dependencies": {
+ "bindings": "~1.2.1",
+ "sleep": "^6.3.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.6.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
+ "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/atomically": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
+ "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+ "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "dependencies": {
+ "follow-redirects": "^1.14.0"
+ }
+ },
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
+ "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/bindings": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
+ "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
+ "node_modules/buffer-alloc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+ "dev": true,
+ "dependencies": {
+ "buffer-alloc-unsafe": "^1.1.0",
+ "buffer-fill": "^1.0.0"
+ }
+ },
+ "node_modules/buffer-alloc-unsafe": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+ "dev": true
+ },
+ "node_modules/buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+ "dev": true
+ },
+ "node_modules/compare-version": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
+ "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/conf": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.2.tgz",
+ "integrity": "sha512-iyy4ArqyQ/yrzNASNBN+jaylu53JRuq0ztvL6KAWYHj4iN56BVuhy2SrzEEHBodNbacZr2Pd/4nWhoAwc66T1g==",
+ "dependencies": {
+ "ajv": "^8.1.0",
+ "ajv-formats": "^2.0.2",
+ "atomically": "^1.7.0",
+ "debounce-fn": "^4.0.0",
+ "dot-prop": "^6.0.1",
+ "env-paths": "^2.2.1",
+ "json-schema-typed": "^7.0.3",
+ "onetime": "^5.1.2",
+ "pkg-up": "^3.1.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/debounce-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
+ "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
+ "dependencies": {
+ "mimic-fn": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dot-prop": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+ "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+ "dependencies": {
+ "is-obj": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/electron-notarize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.1.1.tgz",
+ "integrity": "sha512-kufsnqh86CTX89AYNG3NCPoboqnku/+32RxeJ2+7A4Rbm4bbOx0Nc7XTy3/gAlBfpj9xPAxHfhZLOHgfi6cJVw==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-osx-sign": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz",
+ "integrity": "sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==",
+ "dev": true,
+ "dependencies": {
+ "bluebird": "^3.5.0",
+ "compare-version": "^0.1.2",
+ "debug": "^2.6.8",
+ "isbinaryfile": "^3.0.2",
+ "minimist": "^1.2.0",
+ "plist": "^3.0.1"
+ },
+ "bin": {
+ "electron-osx-flat": "bin/electron-osx-flat.js",
+ "electron-osx-sign": "bin/electron-osx-sign.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/electron-osx-sign/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/electron-osx-sign/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "node_modules/electron-store": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.0.0.tgz",
+ "integrity": "sha512-ZgRPUZkfrrjWSqxZeaxu7lEvmYf6tgl49dLMqxXGnEmliSiwv3u4rJPG+mH3fBQP9PBqgSh4TCuxHZImMMUgWg==",
+ "dependencies": {
+ "conf": "^10.0.0",
+ "type-fest": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.14.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
+ "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.9",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
+ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
+ "dev": true
+ },
+ "node_modules/is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isbinaryfile": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz",
+ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
+ "dev": true,
+ "dependencies": {
+ "buffer-alloc": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
+ "node_modules/json-schema-typed": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
+ "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+ "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/nan": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
+ "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/onetime/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "dependencies": {
+ "find-up": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
+ "integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
+ "dev": true,
+ "dependencies": {
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^9.0.7"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sleep": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/sleep/-/sleep-6.3.0.tgz",
+ "integrity": "sha512-+WgYl951qdUlb1iS97UvQ01pkauoBK9ML9I/CMPg41v0Ze4EyMlTgFTDDo32iYj98IYqxIjDMRd+L71lawFfpQ==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "nan": "^2.14.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
+ "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ },
+ "dependencies": {
+ "@crimata/nodeaudio": {
+ "version": "0.0.0",
+ "resolved": "https://gitlab.com/api/v4/projects/28849281/packages/npm/@crimata/nodeaudio/-/@crimata/nodeaudio-0.0.0.tgz",
+ "integrity": "sha1-4lPe1QynEwebXcjezyAPhfwI+VM=",
+ "requires": {
+ "bindings": "~1.2.1",
+ "sleep": "^6.3.0"
+ }
+ },
+ "ajv": {
+ "version": "8.6.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz",
+ "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "requires": {
+ "ajv": "^8.0.0"
+ }
+ },
+ "at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true
+ },
+ "atomically": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
+ "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w=="
+ },
+ "axios": {
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+ "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "requires": {
+ "follow-redirects": "^1.14.0"
+ }
+ },
+ "base64-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz",
+ "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA=="
+ },
+ "base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true
+ },
+ "bindings": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
+ "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
+ },
+ "bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
+ "buffer-alloc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc-unsafe": "^1.1.0",
+ "buffer-fill": "^1.0.0"
+ }
+ },
+ "buffer-alloc-unsafe": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+ "dev": true
+ },
+ "buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+ "dev": true
+ },
+ "compare-version": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
+ "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=",
+ "dev": true
+ },
+ "conf": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.2.tgz",
+ "integrity": "sha512-iyy4ArqyQ/yrzNASNBN+jaylu53JRuq0ztvL6KAWYHj4iN56BVuhy2SrzEEHBodNbacZr2Pd/4nWhoAwc66T1g==",
+ "requires": {
+ "ajv": "^8.1.0",
+ "ajv-formats": "^2.0.2",
+ "atomically": "^1.7.0",
+ "debounce-fn": "^4.0.0",
+ "dot-prop": "^6.0.1",
+ "env-paths": "^2.2.1",
+ "json-schema-typed": "^7.0.3",
+ "onetime": "^5.1.2",
+ "pkg-up": "^3.1.0",
+ "semver": "^7.3.5"
+ }
+ },
+ "debounce-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
+ "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
+ "requires": {
+ "mimic-fn": "^3.0.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "dot-prop": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+ "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+ "requires": {
+ "is-obj": "^2.0.0"
+ }
+ },
+ "electron-notarize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.1.1.tgz",
+ "integrity": "sha512-kufsnqh86CTX89AYNG3NCPoboqnku/+32RxeJ2+7A4Rbm4bbOx0Nc7XTy3/gAlBfpj9xPAxHfhZLOHgfi6cJVw==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1"
+ }
+ },
+ "electron-osx-sign": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz",
+ "integrity": "sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.0",
+ "compare-version": "^0.1.2",
+ "debug": "^2.6.8",
+ "isbinaryfile": "^3.0.2",
+ "minimist": "^1.2.0",
+ "plist": "^3.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "electron-store": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.0.0.tgz",
+ "integrity": "sha512-ZgRPUZkfrrjWSqxZeaxu7lEvmYf6tgl49dLMqxXGnEmliSiwv3u4rJPG+mH3fBQP9PBqgSh4TCuxHZImMMUgWg==",
+ "requires": {
+ "conf": "^10.0.0",
+ "type-fest": "^1.0.2"
+ }
+ },
+ "env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.14.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
+ "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
+ },
+ "fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "requires": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.9",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
+ "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
+ "dev": true
+ },
+ "is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
+ },
+ "isbinaryfile": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz",
+ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc": "^1.2.0"
+ }
+ },
+ "json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
+ "json-schema-typed": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
+ "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="
+ },
+ "jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+ "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ=="
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
+ "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
+ },
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ },
+ "dependencies": {
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ }
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+ },
+ "pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "plist": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz",
+ "integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^9.0.7"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
+ },
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "sleep": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/sleep/-/sleep-6.3.0.tgz",
+ "integrity": "sha512-+WgYl951qdUlb1iS97UvQ01pkauoBK9ML9I/CMPg41v0Ze4EyMlTgFTDDo32iYj98IYqxIjDMRd+L71lawFfpQ==",
+ "requires": {
+ "nan": "^2.14.1"
+ }
+ },
+ "type-fest": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+ "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="
+ },
+ "universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "ws": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
+ "integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==",
+ "requires": {}
+ },
+ "xmlbuilder": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ }
+}
diff --git a/package.json b/package.json
index 24d13b1..6b6c329 100644
--- a/package.json
+++ b/package.json
@@ -1,104 +1,25 @@
{
"name": "Crimata",
- "version": "0.9.9",
+ "productName": "crimata-messenger",
+ "version": "1.0.0",
"private": true,
- "description": "Cross-platform messenger application built with electron, vue3, and TS.",
+ "description": "Cross-platform messenger app (Electron, Vue3)",
"author": {
- "name": "Enrique Hernandez"
+ "name": "Andrew Gundersen"
},
+ "main": "src/main.js",
"scripts": {
- "build": "vue-cli-service electron:build",
- "dev": "vue-cli-service electron:serve",
- "postinstall": "electron-builder install-app-deps",
- "postuninstall": "electron-builder install-app-deps"
+ "start": "electron ."
},
- "main": "init.js",
"dependencies": {
- "@google-cloud/speech": "^4.2.0",
- "@types/animejs": "^3.1.2",
- "@types/bindings": "^1.3.0",
- "@types/dom-mediacapture-record": "^1.0.7",
- "@types/node": "^14.14.25",
- "@types/uuid": "^8.3.0",
- "@types/ws": "^7.2.7",
- "animejs": "^3.2.0",
+ "@crimata/nodeaudio": "0.0.0",
"axios": "^0.21.1",
- "core-js": "^3.6.5",
- "electron-is-dev": "^2.0.0",
+ "base64-arraybuffer": "^1.0.1",
"electron-store": "^8.0.0",
- "electron-updater": "^4.3.8",
- "mitt": "^2.1.0",
- "naudiodon": "^2.3.2",
- "node-record-lpcm16": "^1.0.1",
- "update-electron-app": "^2.0.1",
- "uuid": "^8.3.2",
- "vue": "^3.0.0-0",
- "vue-router": "^4.0.0-0",
- "vuex": "^4.0.0-0",
"ws": "^7.3.1"
},
"devDependencies": {
- "@types/axios": "^0.14.0",
- "@types/electron-devtools-installer": "^2.2.0",
- "@types/jest": "^24.0.19",
- "@typescript-eslint/eslint-plugin": "^2.33.0",
- "@typescript-eslint/parser": "^2.33.0",
- "@vue/cli-plugin-babel": "~4.5.0",
- "@vue/cli-plugin-eslint": "~4.5.0",
- "@vue/cli-plugin-router": "~4.5.0",
- "@vue/cli-plugin-typescript": "~4.5.0",
- "@vue/cli-plugin-unit-jest": "~4.5.0",
- "@vue/cli-plugin-vuex": "~4.5.0",
- "@vue/cli-service": "~4.5.0",
- "@vue/compiler-sfc": "^3.0.0-0",
- "@vue/eslint-config-typescript": "^5.0.2",
- "@vue/test-utils": "^2.0.0-0",
- "@wasm-tool/wasm-pack-plugin": "^1.3.1",
- "electron": "^9.0.0",
- "electron-devtools-installer": "^3.1.0",
- "electron-log": "^4.3.4",
- "eslint": "^6.7.2",
- "eslint-plugin-vue": "^7.0.0-0",
- "lint-staged": "^9.5.0",
- "node-sass": "^4.12.0",
- "optimize-wasm-webpack-plugin": "^1.0.12",
- "sass-loader": "^8.0.2",
- "spectron": "11.0.0",
- "typescript": "~3.9.3",
- "vue-cli-plugin-electron-builder": "~2.0.0-rc.6",
- "vue-jest": "^5.0.0-0"
- },
- "vue": {
- "lintOnSave": false,
- "pluginOptions": {
- "electronBuilder": {
- "mainProcessFile": "./src/init.ts",
- "rendererProcessFile": "./src/render/main.ts",
- "preload": "./src/render/preload.ts",
- "builderOptions": {
- "appId": "com.crimata.ElectronUpdaterApp",
- "artifactName": "${productName}-${version}.${ext}",
- "publish": {
- "provider": "generic",
- "url": "https://gitlab.com/api/v4/projects/25637892/jobs/artifacts/main/raw/dist_electron?job=build"
- }
- }
- }
- }
- },
- "gitHooks": {
- "pre-commit": "lint-staged"
- },
- "lint-staged": {
- "*.{js,jsx,vue,ts,tsx}": [
- "vue-cli-service lint",
- "git add"
- ]
- },
- "productName": "crimata-messenger",
- "repository": {
- "type": "git",
- "url": "https://gitlab.com/crimata/electron-app.git",
- "release": "latest"
+ "electron-notarize": "^1.1.1",
+ "electron-osx-sign": "^0.6.0"
}
}
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index 8f79d27..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
- <%= htmlWebpackPlugin.options.title %>
-
-
-
-
-
-
-
diff --git a/src/account.js b/src/account.js
new file mode 100644
index 0000000..e339db2
--- /dev/null
+++ b/src/account.js
@@ -0,0 +1,41 @@
+const { ipcMain } = require("electron");
+
+const { post } = require("./api");
+const store = require("./utils/store");
+const { launchSession, endSession } = require("./session");
+const { backgroundMitt, ipcEmit } = require("./utils/emitter");
+
+async function auth(_e, creds)
+{
+ const { error, data } = await post("/auth", creds);
+
+ if (error)
+ {
+ return data;
+ }
+
+ store.set("account", data);
+ ipcEmit("account", data);
+ launchSession(data);
+};
+
+function logout(_e, reason)
+{
+ store.delete("account");
+ ipcEmit("account", false, reason);
+ endSession();
+};
+
+function initAccount()
+{
+ ipcMain.handle("auth", auth);
+ ipcMain.on("logout", logout);
+
+ const account = store.get("account");
+ if (account) launchSession(account);
+}
+
+backgroundMitt.on("logout", (reason) => logout(null, reason));
+
+
+exports.initAccount = initAccount;
diff --git a/src/account.ts b/src/account.ts
deleted file mode 100644
index 581da0e..0000000
--- a/src/account.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-
-import { postAuth, postLogin, postLogout } from "@/api/account";
-import { endSession, launchSession } from "@/session";
-import { getToken, setToken, setProfile, getProfile, clearStore } from "./store";
-import { parseAuthRes } from "./auth";
-import { ipcEmit } from "@/composables/useEmitter";
-
-export const accountAuth = async (): Promise => {
-
- /* attempt to get a login token from the store */
- const token = getToken();
-
- /* try to login with it, returns platform secret and new token on success */
- if (token) {
- try {
-
- const res = await postAuth(token);
-
- const parsed = parseAuthRes(res);
-
- setToken(parsed.token)
- setProfile(parsed.profile);
-
- return {
- profile: parsed.profile,
- token: parsed.token
- };
-
- } catch(e) {
- console.log('[ACCOUNT]', e);
- clearStore();
- throw(new Error('Failed to authenticate.'));
-
- }
- } else {
- throw(new Error('Unable to authenticate.'));
- }
-};
-
-export const accountLogin: IpcHandlerCallback = async (payload) => {
- const account = payload as AccountCredentials;
- try {
-
- // attempt login with email password
- const res = await postLogin(account.email, account.password);
- const parsed = parseAuthRes(res);
-
- // save jwt token and profile
- setToken(parsed.token);
- setProfile(parsed.profile);
-
- // launch session
- launchSession(parsed.token);
-
- // return profile to renderer
- return parsed.profile;
-
- } catch(e) {
- clearStore();
- throw e;
- }
-}
-
-
-export const accountLogout = async (): Promise => {
-
- try {
- // post logout to backend
- await postLogout();
-
- // remove key and crimataId
- clearStore();
-
- // kill crimata platform session
- endSession();
-
- return;
-
- } catch(e) {
- console.log('[ACCOUNT]', e);
- return (new Error('Failed to logout. Please try again.'));
- }
-
-}
-
-export const updateAppState = (): void => {
-
- const profile = getProfile();
-
- ipcEmit("set-profile", profile);
-
- // ipcEmit('messages') etc
-
-}
-
diff --git a/src/api.js b/src/api.js
new file mode 100644
index 0000000..785c304
--- /dev/null
+++ b/src/api.js
@@ -0,0 +1,38 @@
+const axios = require('axios').default;
+
+const config = require("./config");
+
+
+async function post(route, body)
+{
+ let error;
+ let data;
+
+ try
+ {
+ const res = await axios.post(config.API + route, body);
+ data = res.data;
+ error = false;
+ }
+ catch (e)
+ {
+ if (e.response)
+ {
+ data = e.response.data;
+ }
+ else if (e.request)
+ {
+ data = "Can't connect to the server."
+ }
+ else
+ {
+ data = "Unknown error occured."
+ }
+
+ error = true;
+ }
+
+ return { error, data };
+}
+
+exports.post = post;
diff --git a/src/api/account.ts b/src/api/account.ts
deleted file mode 100644
index 45baca2..0000000
--- a/src/api/account.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-
-import useHttp from "@/composables/useHttp";
-import axios from "axios";
-import {config} from "@/config";
-
-const { post } = useHttp();
-
-export const postAuth = async (token: string) => (
- await axios({
- url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate',
- headers: {
- Cookie: `crimataCookie=${token}`
- },
- method: 'POST',
- })
-);
-
-
-export const postLogin = async (email: string, password: string) => (
- await post('/account/login', { email, password })
-);
-
-
-export const postLogout =
- async (): Promise => (await post('/account/logout'));
-
-
-
-
-
-
diff --git a/src/assets/icon.icns b/src/assets/icon.icns
new file mode 100644
index 0000000..b173533
Binary files /dev/null and b/src/assets/icon.icns differ
diff --git a/src/assets/tray/000.png b/src/assets/tray/000.png
new file mode 100644
index 0000000..e6d5d32
Binary files /dev/null and b/src/assets/tray/000.png differ
diff --git a/src/assets/tray/000@2x.png b/src/assets/tray/000@2x.png
new file mode 100644
index 0000000..6d61d7f
Binary files /dev/null and b/src/assets/tray/000@2x.png differ
diff --git a/src/assets/tray/001.png b/src/assets/tray/001.png
new file mode 100644
index 0000000..8d65b38
Binary files /dev/null and b/src/assets/tray/001.png differ
diff --git a/src/assets/tray/001@2x.png b/src/assets/tray/001@2x.png
new file mode 100644
index 0000000..b7f0e2c
Binary files /dev/null and b/src/assets/tray/001@2x.png differ
diff --git a/src/assets/tray/010.png b/src/assets/tray/010.png
new file mode 100644
index 0000000..bc5718c
Binary files /dev/null and b/src/assets/tray/010.png differ
diff --git a/src/assets/tray/010@2x.png b/src/assets/tray/010@2x.png
new file mode 100644
index 0000000..6b97193
Binary files /dev/null and b/src/assets/tray/010@2x.png differ
diff --git a/src/assets/tray/011.png b/src/assets/tray/011.png
new file mode 100644
index 0000000..c2f8612
Binary files /dev/null and b/src/assets/tray/011.png differ
diff --git a/src/assets/tray/011@2x.png b/src/assets/tray/011@2x.png
new file mode 100644
index 0000000..befcd5f
Binary files /dev/null and b/src/assets/tray/011@2x.png differ
diff --git a/src/assets/tray/100.png b/src/assets/tray/100.png
new file mode 100644
index 0000000..99df7df
Binary files /dev/null and b/src/assets/tray/100.png differ
diff --git a/src/assets/tray/100@2x.png b/src/assets/tray/100@2x.png
new file mode 100644
index 0000000..2d26144
Binary files /dev/null and b/src/assets/tray/100@2x.png differ
diff --git a/src/assets/tray/101.png b/src/assets/tray/101.png
new file mode 100644
index 0000000..523bc93
Binary files /dev/null and b/src/assets/tray/101.png differ
diff --git a/src/assets/tray/101@2x.png b/src/assets/tray/101@2x.png
new file mode 100644
index 0000000..91a15be
Binary files /dev/null and b/src/assets/tray/101@2x.png differ
diff --git a/src/assets/tray/110.png b/src/assets/tray/110.png
new file mode 100644
index 0000000..ddd2524
Binary files /dev/null and b/src/assets/tray/110.png differ
diff --git a/src/assets/tray/110@2x.png b/src/assets/tray/110@2x.png
new file mode 100644
index 0000000..fd073dd
Binary files /dev/null and b/src/assets/tray/110@2x.png differ
diff --git a/src/assets/tray/111.png b/src/assets/tray/111.png
new file mode 100644
index 0000000..d2dd686
Binary files /dev/null and b/src/assets/tray/111.png differ
diff --git a/src/assets/tray/111@2x.png b/src/assets/tray/111@2x.png
new file mode 100644
index 0000000..2bc77fd
Binary files /dev/null and b/src/assets/tray/111@2x.png differ
diff --git a/src/audio.js b/src/audio.js
new file mode 100644
index 0000000..73223b2
--- /dev/null
+++ b/src/audio.js
@@ -0,0 +1,134 @@
+const nodeAudio = require("@crimata/nodeaudio");
+const { globalShortcut, ipcMain } = require("electron");
+
+const { sendMessage } = require("./io");
+const { updateTray } = require("./tray");
+const { encode, decode } = require("./codec");
+const { backgroundMitt, ipcEmit } = require("./utils/emitter");
+
+let inputDevice;
+let outputDevice;
+
+let setWriteId;
+let setStreamsId;
+
+let autoStopId; /* keep track of recording time */
+
+let playbackId; /* UID of the message being played */
+
+const streamState = { rec: false, pb: false };
+
+const /** @type {Int16Array[]} */ chunks = [];
+
+backgroundMitt.on("data", (int16Arr) => {
+ if (streamState.rec) chunks.push(int16Arr);
+});
+
+backgroundMitt.on("write", (int16Arr) => {
+ clearTimeout(setWriteId);
+ setWriteId = setTimeout(() => {
+ setPlaybackStatus(false);
+ }, 500);
+});
+
+function setStreams()
+{
+ const defaultInput = nodeAudio.core.GetDefaultInputDevice();
+ const defaultOutput = nodeAudio.core.GetDefaultOutputDevice();
+
+ if (inputDevice !== defaultInput)
+ {
+ inputDevice = defaultInput;
+ nodeAudio.core.CloseInputStream(inputDevice);
+ nodeAudio.core.OpenInputStream(inputDevice);
+ }
+
+ if (outputDevice !== defaultOutput)
+ {
+ outputDevice = defaultOutput;
+ nodeAudio.core.CloseOutputStream(outputDevice);
+ nodeAudio.core.OpenOutputStream(outputDevice);
+ }
+}
+
+function startRecording()
+{
+ setRecordingStatus(true);
+
+ /* 15s recording time limit */
+ autoStopId = setTimeout(stopRecording, 15000);
+}
+
+function stopRecording()
+{
+ if (autoStopId)
+ {
+ clearTimeout(autoStopId);
+ }
+
+ setRecordingStatus(false);
+
+ sendMessage({
+ category: "audio",
+ text: null,
+ blob: encode(nodeAudio.utils.mergeChunks(chunks).buffer)
+ });
+
+ chunks.length = 0;
+}
+
+function initAudio()
+{
+ nodeAudio.core.Initialize(backgroundMitt.emit.bind(backgroundMitt));
+ setStreamsId = setInterval(setStreams, 2000);
+
+ const res = globalShortcut.register('CommandOrControl+Return', () => {
+ streamState.rec ? stopRecording() : startRecording();
+ });
+
+ if (!res) throw new Error("Failed to register recording shortcut");
+}
+
+function playback(base64String, id)
+{
+ /* terminate any current playback */
+ nodeAudio.core.CancelPlayback();
+ ipcEmit("playback", playbackId, false);
+
+ playbackId = id;
+ setPlaybackStatus(true);
+ nodeAudio.core.WriteToOutputStream(decode(base64String));
+}
+
+function terminateAudio()
+{
+ globalShortcut.unregisterAll();
+
+ /* Only terminate PA if initialized */
+ if (setStreamsId)
+ {
+ clearInterval(setStreamsId);
+ nodeAudio.core.Terminate();
+ }
+}
+
+function setRecordingStatus(status)
+{
+ streamState.rec = status;
+ ipcEmit("record", status);
+ updateTray("recording", streamState.rec);
+}
+
+function setPlaybackStatus(status)
+{
+ console.log("Setting playback status: ", status);
+
+ streamState.pb = status;
+ ipcEmit("playback", playbackId, status);
+ updateTray("playback", status);
+}
+
+exports.initAudio = initAudio;
+exports.terminateAudio = terminateAudio;
+exports.playback = playback;
+exports.streamState = streamState;
diff --git a/src/audio.ts b/src/audio.ts
deleted file mode 100644
index 7ae2562..0000000
--- a/src/audio.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-/* eslint @typescript-eslint/no-var-requires: "off" */
-
-"use strict";
-
-// where the audio goes
-let buffer: ArrayBuffer[] = [];
-
-// place audio data in buffer
-export const collect: IpcListenerCallback = (chunk) => {
- if (chunk) buffer.push(chunk);
-}
-
-// return audio and clear buffer
-export const flush = async () => {
- const bufferCopy = buffer;
- buffer = [];
- return bufferCopy;
-}
-
-// import { backgroundMitt } from '@/modules/emitter';
-// const portAudio = require('naudiodon');
-
-// // Audio in and out stream objects.
-// let ai: typeof portAudio.AudioIO | boolean = false;
-// let ao: typeof portAudio.AudioIO | boolean = false;
-
-// // Whether activly recording.
-// let record = false;
-
-// const audioContainer = {
-// input: '',
-// }
-
-// const audioOptions = {
-// channelCount: 1,
-// sampleFormat: 16,
-// sampleRate: 16000,
-// deviceId: -1,
-// closeOnError: false,
-// }
-
-// export const toggleRecord = (): void => { record = !record };
-
-
-// export const fetchAudioInput = (): Promise => (
-
-// new Promise((resolve, reject) => {
-
-// try {
-// resolve(audioContainer.input);
-// toggleRecord();
-// } catch (e) {
-// reject(new Error('Failed to fetch the audio.'))
-// }
-
-// })
-// )
-
-
-// // Main audio function run by run.ts module.
-// export function initAudioIO(): void {
-// console.log("AUDIO:Starting io streams.")
-
-// if (!ai) {
-
-// // Initialize and start input stream.
-// ai = new portAudio.AudioIO({ inOptions: audioOptions });
-// ai.setEncoding("hex");
-// ai.start();
-
-// // On each data chunk...
-// ai.on('data', (chunk: string) => {
-
-// // If recording, we capture the data.
-// if (record) {
-// console.log('AUDIO:Recording...')
-// audioContainer.input += chunk;
-// }
-
-// // Else, we don't capture and also clear audioContainer.
-// else {
-// if (audioContainer.input.length) {
-// audioContainer.input = "";
-// }
-// }
-
-// });
-// }
-
-// if (!ao) {
-
-// // Initialize and start input stream.
-// ao = new portAudio.AudioIO({ outOptions: audioOptions });
-// ao.start();
-
-// }
-// }
-
-
-// // ---Audio playback--------------------------------------------
-
-// // Split Buffer into an array of len-sized Buffers.
-// function bufSplit(buf: Buffer, len: number): Array {
-// const chunks = [];
-// let i = 0;
-// let L = len;
-
-// while(i < buf.byteLength) {
-// chunks.push(buf.slice(i, L));
-// i = L;
-// L += len;
-// }
-
-// return chunks;
-// }
-
-// // Audio playback.
-// export function play(input: string): void {
-
-// // Format the audio.
-// const audio = bufSplit(
-// Buffer.from(input as string, 'hex'),
-// 8192
-// );
-
-// // Called on end of write.
-// const callback = () => {
-
-// // We stop audio playback anim.
-// backgroundMitt.emit('ipc-renderer', {
-// endpoint: 'stop-playback-anim'
-// });
-
-// }
-
-// write();
-
-// // Iterate through audio array and write buffers to portAudio writable.
-// function write() {
-// let chunk: Buffer;
-// let ok = true;
-// let i = 0;
-
-// do {
-// chunk = audio[i];
-// if (i === audio.length - 1) {
-// // write last chunk.
-// ao.write(chunk, null, callback);
-// } else {
-// // check for backpreassure.
-// ok = ao.write(chunk, null);
-// }
-// i++;
-// } while (i < audio.length && ok);
-
-// if (i < audio.length) {
-// // Had to stop early!
-// // Write some more once it drains.
-// ao.once('drain', write);
-// }
-// }
-// }
-
-// // -------------------------------------------------------------
-
-// // Get's called on window close.
-// export async function stopStream() {
-// console.log("AUDIO:Stopping audio stream.")
-// if (ai) {
-// try {
-// await ai.quit()
-// } catch(e){
-// console.log('AUDIO: Failed to shutdown audio input.');
-// throw e;
-// }
-// }
-// if (ao) {
-// try {
-// await ao.quit()
-// } catch(e){
-// console.log('AUDIO: Failed to shutdown audio output.');
-// throw e;
-// }
-// }
-// }
-
-
diff --git a/src/auth.ts b/src/auth.ts
deleted file mode 100644
index 14ed36a..0000000
--- a/src/auth.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-
-export const parseAuthRes = (authRes: any) => {
- const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
- const profile = authRes.data as Profile;
- return {
- token,
- profile
- }
-};
-
-
-
-
diff --git a/src/codec.js b/src/codec.js
new file mode 100644
index 0000000..3a9a8bf
--- /dev/null
+++ b/src/codec.js
@@ -0,0 +1,13 @@
+const { ipcMain } = require("electron");
+const { encode, decode } = require("base64-arraybuffer");
+
+ipcMain.handle("encode", (_e, arrayBuffer) => {
+ return encode(arrayBuffer);
+});
+
+ipcMain.handle("decode", (_e, b64string) => {
+ return decode(b64string);
+});
+
+exports.encode = encode;
+exports.decode = decode;
\ No newline at end of file
diff --git a/src/composables/autoUpdate.ts b/src/composables/autoUpdate.ts
deleted file mode 100644
index 1a6b780..0000000
--- a/src/composables/autoUpdate.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-const { autoUpdater } = require('electron-updater');
-
-let win: boolean;
-
-// Listen for window creation.
-backgroundMitt.on('window-active', (state: boolean) => {
- win = state;
-});
-
-// Auto updating.
-autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' }
-
-autoUpdater.on('update-available', (info: any) => {
- console.log(`Update available: ${info.version}`)
-})
-
-autoUpdater.on('update-downloaded', (info: any) => {
-
- const updateDialog = {
- type: 'info',
- buttons: ['Restart', 'Later'],
- title: 'Application Update',
- message: info.version,
- detail: 'A new version has been downloaded. Restart the application to apply the updates.'
- }
-
- dialog.showMessageBox(updateDialog).then((returnValue) => {
- if (returnValue.response === 0) autoUpdater.quitAndInstall()
- })
-
-})
\ No newline at end of file
diff --git a/src/composables/useEmitter.ts b/src/composables/useEmitter.ts
deleted file mode 100644
index f95f328..0000000
--- a/src/composables/useEmitter.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-
-// Backend emitter
-//
-const EventEmitter = require('events');
-class BackgroundMitt extends EventEmitter { }
-
-export const backgroundMitt = new BackgroundMitt();
-
-export const ipcEmit = (channel: string, payload: T) => {
- backgroundMitt.emit('ipc-renderer', {
- channel,
- payload
- });
-};
-
-
diff --git a/src/composables/useHttp.ts b/src/composables/useHttp.ts
deleted file mode 100644
index 6b9ee56..0000000
--- a/src/composables/useHttp.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-
-import axios, { AxiosRequestConfig } from 'axios';
-import {config} from "@/config";
-
-const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX;
-
-interface Request {
- endpoint: string;
- query?: Record;
- config?: Record;
-}
-
-const makeQuery = (reqQuery: Record) => {
-
- let result = '';
-
- result = '?' + Object.entries(reqQuery)
- .map(([ key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
- .join('&')
-
- return result;
-};
-
-
-export default function useHttp() {
-
- const api = axios.create({
- baseURL,
- withCredentials: true,
- });
-
-
- const post = async (endpoint: string, payload?: Record): Promise => (
- await api.post(endpoint, payload)
- )
-
-
- const get = async (req: Request) => {
-
- if (req.query) {
- req.endpoint += makeQuery(req.query);
- }
-
- const res = await api.get(req.endpoint, req.config);
- return res;
- };
-
- return {
- get, post
- }
-}
diff --git a/src/composables/useIpcMain.ts b/src/composables/useIpcMain.ts
deleted file mode 100644
index ebfc4bc..0000000
--- a/src/composables/useIpcMain.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-
-import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
-
-export class IpcHandler implements IIpcHandler {
-
- readonly channel: string;
-
- readonly _handlerCallback: IpcHandlerCallback;
-
- constructor(options: {
- channel: string;
- handlerCallback: IpcHandlerCallback;
- }) {
- this.channel = options.channel;
- this._handlerCallback = options.handlerCallback;
- }
-
- handle() {
- this.remove();
- ipcMain.handle(this.channel, this._onInvoke);
- }
-
- remove() {
- ipcMain.removeHandler(this.channel);
- }
-
- private _onInvoke = (_e: IpcMainInvokeEvent, payload?: string | null): Promise => {
-
- return new Promise(async (resolve, reject) => {
-
- console.log(`[IPC] Handle:${this.channel}`);
-
- try {
-
- const params = payload ? JSON.parse(payload) : null;
-
- const res = await this._handlerCallback(params);
-
- resolve(res as unknown as ReturnType);
-
- } catch(e) {
- console.log(`[IPC] Error:${this.channel}`);
- reject(e);
- }
- });
- }
-
-}
-
-
-export class IpcListener implements IIpcListener {
-
- readonly channel: string;
-
- readonly _listenerCallback: IpcListenerCallback;
-
- constructor(options: {
- channel: string;
- listenerCallback: IpcListenerCallback;
- }) {
- this.channel = options.channel;
- this._listenerCallback = options.listenerCallback;
- }
-
- listen() {
- this.remove();
- ipcMain.on(this.channel, this._onPost);
- }
-
- remove() {
- ipcMain.removeAllListeners(this.channel);
- }
-
- private _onPost = (_e: IpcMainEvent, payload?: string | null): void => {
-
- console.log(`[IPC] Post: ${this.channel}`);
-
- const params = payload ? JSON.parse(payload) : null;
- this._listenerCallback(params);
- }
-
-}
diff --git a/src/composables/useMessageCanvas.ts b/src/composables/useMessageCanvas.ts
deleted file mode 100644
index c85f7ed..0000000
--- a/src/composables/useMessageCanvas.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-export default class Canvas {
-
- messages: Message[];
-
- /* seed canvas with messages on init */
- constructor(messages: Message[]) {
- this.messages = messages;
- ipcEmit("seed-view", this.messages);
- }
-
- /* add a new message to the canvas */
- add(message: Message) {
- this.messages.push(message);
- ipcEmit("update-view", message);
- }
-
- /* update an existing message */
- update(message: Message) {
-
- /* get the target message */
- let target_message = this.messages.filter((m: Message) => {
- return m.uid = message.uid;
- })[0];
-
- /* replace the target message */
- if (target_message) {
- target_message = message;
- ipcEmit("update-view", message);
- }
-
- }
-
-}
diff --git a/src/composables/useSaveToJSON.ts b/src/composables/useSaveToJSON.ts
deleted file mode 100644
index 06ab4b9..0000000
--- a/src/composables/useSaveToJSON.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-
-import {config} from "@/config";
-import fs from 'fs';
-
-export const saveToJson = (fileName: string, data: any) => {
-
- fs.writeFile(config.configPath + fileName, JSON.stringify(data), (err) => {
- if (err) {
- console.log("Error when saving to json.")
- }
- })
-
-}
diff --git a/src/composables/useWebsockets.ts b/src/composables/useWebsockets.ts
deleted file mode 100644
index d1cfc02..0000000
--- a/src/composables/useWebsockets.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-
-"use strict";
-
-import WebSocket from 'ws';
-
-
-const _connectionCheckTimeout = 4000;
-const _reconnectTimeout = 1000;
-let _connectionCheckInterval: ReturnType;
-
-
-export default function useWebSockets(
- messageCallback: (message: string) => void,
- connectionStatusCallback: (alive: boolean) => void,
-) {
-
- let socket: WebSocket;
-
- const send = async (data: Record): Promise => {
- return new Promise((resolve, reject) => {
- if (socket) {
- if (socket.readyState === WebSocket.OPEN) {
- socket.send(JSON.stringify(data));
- resolve(true);
- }
- }
- reject(false);
- });
- }
-
- const connect = (socketUrl: string, secret: string) => {
-
- // avoid setting multiple interval;
- if (_connectionCheckInterval) clearInterval(_connectionCheckInterval);
-
- /* create a new socket */
- socket = new WebSocket(socketUrl);
-
- /* add event listeners */
- socket.on("open", () => {
-
- socket.send(JSON.stringify({key: secret}));
-
- // ping server
- _connectionCheckInterval = setInterval(() => {
-
- socket.ping(null, true, (e: Error) => {
- if (e) {
- socket.close();
- connectionStatusCallback(false);
- setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
- }
- });
-
- }, _connectionCheckTimeout);
-
- });
-
- socket.on("message", (event: WebSocket.MessageEvent) => {
- console.log("message received", event);
- messageCallback(event.toString())
- });
-
- socket.on("close", (event: WebSocket.CloseEvent) => {
- connectionStatusCallback(false);
- clearInterval(_connectionCheckInterval);
- if (!event.wasClean) {
- setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
- }
-
- });
-
- socket.on("pong", () => connectionStatusCallback(true));
-
- }
-
- const close = () => {
- if (socket) {
- socket.close();
- }
- }
-
- return {
- connect,
- send,
- close
- };
-
-}
diff --git a/src/config.js b/src/config.js
new file mode 100644
index 0000000..bc47fb5
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,11 @@
+const { app } = require("electron");
+
+const DOMAIN = "crimata.com";
+
+const prod = !process.defaultApp;
+// const prod = true;
+
+module.exports = {
+ PLATFORM: prod ? `https://app.${DOMAIN}` : `http://localhost:8760`,
+ API: prod ? `https://${DOMAIN}/api` : `http://localhost:8761`
+}
diff --git a/src/config.ts b/src/config.ts
deleted file mode 100644
index 855b1b2..0000000
--- a/src/config.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-
-import { app } from "electron";
-
-const env = process.env;
-
-const PLATFORM_PORT = env.PLATFORM_PORT || 8760;
-const PLATFORM_IP = env.PLATFORM_IP || 'http://127.0.0.1';
-
-const BUSINESS_PORT = env.BUSINESS_PORT || 3000;
-const BUSINESS_IP = env.BUSINESS_IP || 'http://127.0.0.1';
-
-export const config = {
- PLATFORM_URL: `${PLATFORM_IP}:${PLATFORM_PORT}`,
- BUSINESS_URL: `${BUSINESS_IP}:${BUSINESS_PORT}`,
- BUSINESS_PREFIX: '/api',
- configPath: app.getPath('userData')
-}
diff --git a/src/init.ts b/src/init.ts
deleted file mode 100644
index 9a0fdf6..0000000
--- a/src/init.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Entry point for Crimata electron app.
- * "Look on my Works, ye Mighty, and despair!"
- */
-
-"use strict";
-
-import { app, protocol } from "electron";
-import createWindow from "./window";
-import main from "./main";
-import { backgroundMitt } from '@/composables/useEmitter';
-
-console.log('Starting Crimata electron app.');
-
-// Scheme must be registered before the app is ready
-protocol.registerSchemesAsPrivileged([
- { scheme: "app", privileges: { secure: true, standard: true } }
-]);
-
-const isDev = require('electron-is-dev');
-
-let win: boolean;
-
-// Listen for window creation.
-backgroundMitt.on('window-active', (state: boolean) => {
- win = state;
-});
-
-/* Start main process on ready */
-app.on("ready", async () => {
- await main();
-});
-
-// Must keep to ensure app doesn't quit on close.
-app.on("before-quit", async () => {
-});
-
-// Must keep to ensure app doesn't quit on close.
-app.on("window-all-closed", () => {
-});
-
-// When user clicks app icon (re-open)
-app.on("activate", () => {
- if (!win) createWindow();
-});
-
-// Exit cleanly on request from parent process in development mode.
-if (isDev) {
- process.on("SIGTERM", () => {
- app.quit();
- });
-}
diff --git a/src/io.js b/src/io.js
new file mode 100644
index 0000000..f09d3cf
--- /dev/null
+++ b/src/io.js
@@ -0,0 +1,69 @@
+const WebSocket = require("ws");
+const { ipcMain } = require("electron");
+
+const config = require("./config");
+const { updateTray } = require("./tray");
+const { backgroundMitt, ipcEmit } = require("./utils/emitter");
+
+let connection = null;
+
+let pingId;
+let reconnectId;
+
+function connectToPlatform(account)
+{
+ if (pingId) clearInterval(pingId);
+
+ connection = new WebSocket(`${config.PLATFORM}/${account}`)
+
+ .on("open", () => pingId = setInterval(() => connection.ping(null, true), 1000))
+
+ .on("pong", () => setConnectionStatus(0))
+
+ .on("error", () => {}) /** keep silent on error */
+
+ .on("message", (payload) => backgroundMitt.emit("message", JSON.parse(payload)))
+
+ .on("close", (_code, reason) => {
+
+ setConnectionStatus(1);
+
+ if (reason)
+ {
+ if (reason == "unauthorized")
+ {
+ backgroundMitt.emit("logout", reason);
+ }
+
+ return;
+ }
+
+ reconnectId = setTimeout(() => connectToPlatform(account), 500);
+
+ });
+}
+
+function sendMessage(message)
+{
+ if (connection.readyState === WebSocket.OPEN) {
+ connection.send(JSON.stringify(message));
+ } else console.log("Failed to send message");
+}
+
+function disconnectFromPlatform()
+{
+ clearTimeout(reconnectId);
+ if (connection.open) connection.close(1000, "logout");
+}
+
+function setConnectionStatus(status)
+{
+ updateTray("disconnect", status);
+ ipcEmit("ws", status);
+}
+
+ipcMain.on("message", (_e, message) => sendMessage(message));
+
+exports.connectToPlatform = connectToPlatform;
+exports.sendMessage = sendMessage;
+exports.disconnectFromPlatform = disconnectFromPlatform;
diff --git a/src/ipc/handlers.ts b/src/ipc/handlers.ts
deleted file mode 100644
index 5529662..0000000
--- a/src/ipc/handlers.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-
-"use strict";
-
-import { accountLogin, accountLogout, accountProfile } from "@/account";
-import { IpcHandler } from "@/composables/useIpcMain";
-import { flush } from "@/audio";
-
-
-const LOGIN_CHANNEL = "invoke-account-login";
-const LOGOUT_CHANNEL = "invoke-account-logout";
-const GET_AUDIO_CHANNEL = "invoke-audio-flush";
-
-export const loginHandler = new IpcHandler({
- channel: LOGIN_CHANNEL,
- handlerCallback: accountLogin
-});
-
-export const logoutHandler = new IpcHandler({
- channel: LOGOUT_CHANNEL,
- handlerCallback: accountLogout
-});
-
-export const getAudioHandler = new IpcHandler({
- channel: GET_AUDIO_CHANNEL,
- handlerCallback: flush
-});
-
-
diff --git a/src/ipc/index.ts b/src/ipc/index.ts
deleted file mode 100644
index e6eb90a..0000000
--- a/src/ipc/index.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-
-"use strict";
-
-import * as handlers from "./handlers";
-import * as listeners from "./listeners";
-
-const ipcHandlers: IPCHandlers = {};
-const ipcListeners: IPCListeners = {};
-
-const _initHandlers = (): void => {
- for (const [key, handler] of Object.entries(handlers)) {
- if (!(key in ipcHandlers)) {
- ipcHandlers[key] = handler;
- handler.handle();
- }
- }
-};
-
-const _initListeners = (): void => {
- for (const [key, listener] of Object.entries(listeners)) {
- if (!(key in ipcListeners)) {
- ipcListeners[key] = listener;
- listener.listen();
- }
- }
-};
-
-export default function initIpcMain(): void {
- _initHandlers();
- _initListeners();
-}
-
-
diff --git a/src/ipc/listeners.ts b/src/ipc/listeners.ts
deleted file mode 100644
index 8ef5d45..0000000
--- a/src/ipc/listeners.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-
-import { IpcListener } from "@/composables/useIpcMain"
-import { sendMessage } from '@/session';
-import { collect } from "@/audio";
-import { updateAppState } from "@/account";
-
-const CLIENT_MESSAGE_CHANNEL = "post-session-send"
-const GET_AUDIO_CHANNEL = "post-audio-collect";
-const APP_MOUNT_CHANNEL = "post-app-mount";
-
-export const messageListener = new IpcListener({
- channel: CLIENT_MESSAGE_CHANNEL,
- listenerCallback: sendMessage
-});
-
-export const audioChunkListener = new IpcListener({
- channel: GET_AUDIO_CHANNEL,
- listenerCallback: collect
-});
-
-export const appMountListener = new IpcListener({
- channel: APP_MOUNT_CHANNEL,
- listenerCallback: updateAppState
-});
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..63e4d13
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,28 @@
+const { app, protocol, ipcMain, nativeTheme } = require("electron");
+
+const store = require("./utils/store");
+
+const { initTray } = require("./tray");
+const { createWin } = require("./window");
+const { initAccount } = require("./account");
+const { terminateAudio } = require("./audio");
+
+// Assert a light theme
+nativeTheme.themeSource = "light";
+
+app.on("ready", () =>
+{
+ initTray();
+
+ createWin();
+
+ initAccount();
+});
+
+app.on("will-quit", terminateAudio);
+
+// When user clicks app icon (re-open)
+app.on("activate", createWin);
+
+// Prevents app from quitting on window close event
+app.on('window-all-closed', (e) => e.preventDefault());
diff --git a/src/main.ts b/src/main.ts
deleted file mode 100644
index a360835..0000000
--- a/src/main.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Where the background logic really begins, gets called by app.onReady().
- *
- * Handles authentication. If profile is set, we launch a session, which consis
- * of opening a connection with the platform, initializing the audio streams.
- *
- * The session is primarily an interface between the frontend and the platform,
- * relaying messages from one to the other.
- *
- */
-
-import initIpcMain from "@/ipc/index";
-import { accountAuth, updateAppState } from "./account";
-import { launchSession } from "./session";
-import createWindow from "./window";
-
-let authState: AuthState | null;
-
-export default async function main() {
-
- /* initiate controls for frontend to use when needed */
- initIpcMain();
-
- /* launch browser window */
- await createWindow();
-
- try {
- authState = await accountAuth() as AuthState;
- } catch(e) {
- console.log('AUTH:', e);
- authState = null;
- } finally {
- if (authState) {
- launchSession(authState.token as string);
- }
- updateAppState();
- }
-
-}
diff --git a/src/render/App.vue b/src/render/App.vue
deleted file mode 100644
index 686591b..0000000
--- a/src/render/App.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/render/assets/connectLogo.svg b/src/render/assets/connectLogo.svg
new file mode 100644
index 0000000..7adceea
--- /dev/null
+++ b/src/render/assets/connectLogo.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/render/assets/fonts/SF-Compact-Display-Bold.otf b/src/render/assets/fonts/SF-Compact-Display-Bold.otf
new file mode 100755
index 0000000..409e11a
Binary files /dev/null and b/src/render/assets/fonts/SF-Compact-Display-Bold.otf differ
diff --git a/src/render/assets/fonts/SF-Compact-Rounded-Bold.otf b/src/render/assets/fonts/SF-Compact-Rounded-Bold.otf
new file mode 100755
index 0000000..04cf9fc
Binary files /dev/null and b/src/render/assets/fonts/SF-Compact-Rounded-Bold.otf differ
diff --git a/src/render/assets/fonts/SF-Pro-Text-Regular.otf b/src/render/assets/fonts/SF-Pro-Text-Regular.otf
new file mode 100755
index 0000000..06dbe85
Binary files /dev/null and b/src/render/assets/fonts/SF-Pro-Text-Regular.otf differ
diff --git a/src/render/assets/settingsIcon.svg b/src/render/assets/settingsIcon.svg
new file mode 100644
index 0000000..25399f5
--- /dev/null
+++ b/src/render/assets/settingsIcon.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/render/components/app.js b/src/render/components/app.js
new file mode 100644
index 0000000..1793563
--- /dev/null
+++ b/src/render/components/app.js
@@ -0,0 +1,44 @@
+import Login from "./login.js";
+import Splash from "./splash.js";
+import Header from "./header.js";
+import Messenger from "./messenger.js";
+
+const account = Vue.ref(null);
+
+const App =
+{
+ components: {
+ Splash,
+ Header,
+ Messenger,
+ Login
+ },
+
+ setup()
+ {
+ return { account };
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+ `
+}
+
+window.mainApi.on("account", (value, reason) => {
+ if (reason) alert(reason);
+ account.value = value;
+});
+
+export default App;
+export { account };
+
+// Is called when window is about to close or reload
+window.onbeforeunload = () => console.log("beforeunload");
diff --git a/src/render/components/bubble.js b/src/render/components/bubble.js
new file mode 100644
index 0000000..a42364c
--- /dev/null
+++ b/src/render/components/bubble.js
@@ -0,0 +1,48 @@
+const Bubble =
+{
+ props: ["category", "text", "html", "blob", "modifier", "child"],
+
+ setup(props)
+ {
+ const getUrl = () => {
+ return `data:${props.category};base64,${props.blob}`;
+ }
+
+ Vue.onMounted(() => {
+
+ const el = document.getElementById("messenger");
+
+ setTimeout(() => {
+
+ el.dispatchEvent(new CustomEvent('adjust-scroll'));
+
+ }, 100);
+
+ });
+
+ if (!props.text) props.text = "...";
+
+ return { getUrl };
+ },
+
+ template: `
+
+
+
+
+
{{ text }}
+
+
{{ text }}
+
+
+
+
![]()
+
+
{{ text }}
+
+
`
+};
+
+export default Bubble;
+
+// TODO: should include other styling/actions for audio bubbles
diff --git a/src/render/components/bubble.vue b/src/render/components/bubble.vue
deleted file mode 100644
index 982cef3..0000000
--- a/src/render/components/bubble.vue
+++ /dev/null
@@ -1,331 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ context }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/render/components/context.js b/src/render/components/context.js
new file mode 100644
index 0000000..0c9e89c
--- /dev/null
+++ b/src/render/components/context.js
@@ -0,0 +1,33 @@
+
+const Context =
+{
+ props: ["modifier", "context", "avatar", "id"],
+
+ setup(props) {
+ const playback = Vue.ref(false);
+
+ Vue.onMounted(() => {
+ window.mainApi.on("playback", (id, status) => {
+ if (id === props.id) {
+ playback.value = status;
+ }
+ });
+ });
+
+ return { playback };
+ },
+
+ template: `
+
+
+
+
+ {{ context }}
+
+ `
+}
+
+export default Context;
diff --git a/src/render/components/control/draggify.js b/src/render/components/control/draggify.js
new file mode 100644
index 0000000..23d2683
--- /dev/null
+++ b/src/render/components/control/draggify.js
@@ -0,0 +1,195 @@
+const saveLocation = "input_item_position";
+const defaultPosition = { x: 15, y: 400 };
+
+//---Dragabble Helper Funcs--------------------------------------
+
+// Calculate distance to nearest side.
+function calcSideProximity (elementX, elementLength, winW) {
+
+ // Calc right short.
+ let short = winW - elementX - elementLength;
+
+ // See if it's left short.
+ if (elementX + 20 < winW / 2) {
+ short = elementX
+ }
+
+ return short
+}
+
+// Update elementX or elementY value on window resize
+function calcPosition (elementPosition, elementLength, percent, short, win) {
+
+ // Is it close to the right/bottom side?
+ if (percent > 0.75) {
+ elementPosition = win - short - elementLength;
+ }
+
+ // Is it not close to a side?
+ if (percent < 0.75 && percent > 0.25) {
+ elementPosition = win * percent
+ }
+
+ return elementPosition
+}
+
+function draggify(elementId, parentId, margin) {
+
+ let element;
+ let parent;
+
+ /* only compatible with elements having equal width and height */
+ let elementLength;
+
+ // Cords of inputItem.
+ const elementX = Vue.ref();
+ const elementY = Vue.ref();
+
+ // Position of inputItem on terms of percentage of window.
+ let percentX;
+ let percentY;
+
+ // How close inputItem is to closest X or Y side.
+ let xShort;
+ let yShort;
+
+ //---Reposition Anime-----------------------------------------------
+
+ // Move element to target smoothly.
+ const repositionAnime = (xChange, yChange) => {
+
+ const xStep = xChange / 1000;
+ const yStep = yChange / 1000;
+
+ for (let i = 1; i <= 1000; i++) {
+
+ setTimeout(() => {
+ elementX.value += xStep;
+ elementY.value += yStep;
+ }, 30) // 60fps
+
+ }
+ }
+
+ //---Event Handlers-----------------------------------------------
+
+ // Update the position of inputItem on mouse dragging.
+ const onMouseMove = (e) => {
+ e.preventDefault();
+
+ elementX.value = element.offsetLeft + e.movementX;
+ elementY.value = element.offsetTop + e.movementY;
+ }
+
+ // Add an event listener for dragging.
+ const onMouseDown = (e) => {
+ e.preventDefault();
+ window.addEventListener('mousemove', onMouseMove);
+ window.addEventListener('mouseup', onMouseUp);
+ }
+
+ // Update position references when user is done moving targetEl.
+ const onMouseUp = (e) => {
+ e.preventDefault();
+
+ window.removeEventListener('mousemove', onMouseMove);
+ window.removeEventListener('mouseup', onMouseUp);
+
+ // See if and calculate reposition.
+ let x = 0; // vector change
+ let y = 0;
+
+ const winW = parent.clientWidth;
+ const winH = parent.clientHeight;
+
+ if (elementX.value < 0) {
+ x = (elementX.value - margin)*-1
+ }
+
+ if (elementX.value > winW - elementLength) {
+ const b = winW - elementLength - margin;
+ x = (elementX.value - b)*-1
+ }
+
+ // Reposition y
+ if (elementY.value < 0) {
+ y = (elementY.value - margin)*-1
+ }
+
+ if (elementY.value > winH - elementLength) {
+ const d = winH - elementLength - margin;
+ y = (elementY.value - d)*-1
+ }
+
+ // Reposition if needed.
+ if (x !== 0 || y !== 0) repositionAnime(x, y);
+
+ xShort = calcSideProximity(elementX.value, elementLength, winW);
+ yShort = calcSideProximity(elementY.value, elementLength, winH);
+
+ // Update percentages.
+ percentX = elementX.value / winW;
+ percentY = elementY.value / winH;
+
+ // Save position.
+ savePosition();
+ }
+
+ // Update position of targetEl on windowResize.
+ const onWindowResize = (_e) => {
+
+ elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, parent.clientWidth);
+ elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, parent.clientHeight);
+
+ savePosition();
+ }
+
+ const savePosition = () => {
+ window.localStorage.setItem(saveLocation, JSON.stringify({
+ x: elementX.value,
+ y: elementY.value
+ }));
+ }
+
+ //---------------------------------------------------------------
+
+ Vue.onMounted(() => {
+
+ element = document.getElementById(elementId);
+ parent = document.getElementById(parentId);
+
+ elementLength = element.offsetWidth;
+
+ // Initialize the positional references.
+ percentX = elementX.value / parent.clientWidth;
+ percentY = elementY.value / parent.clientHeight;
+
+ xShort = calcSideProximity(elementX.value, elementLength, parent.clientWidth);
+ yShort = calcSideProximity(elementX.value, elementLength, parent.clientHeight);
+
+ // Then, we can listen for window resize (and mousedown).
+ element.addEventListener("mousedown", onMouseDown);
+ parent.addEventListener('resize', onWindowResize)
+ });
+
+ // remove event listeners on component dismount.
+ Vue.onUnmounted(() => {
+ element.removeEventListener('mousedown', onMouseDown);
+ parent.removeEventListener('resize', onWindowResize)
+ parent.removeEventListener('mouseup', onMouseUp)
+ parent.removeEventListener('mousemove', onMouseMove);
+ });
+
+ // Try loading initPosition, otherwise set default values
+ let initPosition;
+ const rawData = window.localStorage.getItem("saveLocation")
+ rawData ? initPosition = JSON.parse(rawData) : initPosition = defaultPosition;
+
+ elementX.value = initPosition.x;
+ elementY.value = initPosition.y;
+
+ return { elementX, elementY };
+
+}
+
+export default draggify;
diff --git a/src/render/components/control/drop.js b/src/render/components/control/drop.js
new file mode 100644
index 0000000..ac9c790
--- /dev/null
+++ b/src/render/components/control/drop.js
@@ -0,0 +1,24 @@
+const onDrop = async (e) => {
+ const file = e.dataTransfer.items[0].getAsFile();
+
+ if (file.size >= 1 * 1000 * 1000) {
+ alert("File must be under 1MB.");
+ return;
+ }
+
+ const buffer = await file.arrayBuffer();
+ const b64String = await window.mainApi.invoke("encode", buffer);
+
+ let category = "file";
+ if (file.type.startsWith("image/")) {
+ category = "image"
+ }
+
+ window.mainApi.send("message", {
+ category: category,
+ text: file.name,
+ blob: b64String
+ });
+}
+
+export default onDrop;
diff --git a/src/render/components/control/scroll.js b/src/render/components/control/scroll.js
new file mode 100644
index 0000000..3e9a0bf
--- /dev/null
+++ b/src/render/components/control/scroll.js
@@ -0,0 +1,20 @@
+let el;
+
+const scroll = () => {
+ if (!el) {
+ el = document.getElementById("messenger");
+ window.addEventListener('resize', scroll);
+ }
+ el.scrollTo({
+ top: el.scrollHeight - el.clientHeight,
+ behavior: 'smooth'
+ });
+}
+
+export default scroll;
+
+// const isBottom = () => {
+// if (el) {
+// return el.scrollHeight - el.clientHeight <= el.scrollTop + 1;
+// }
+// }
\ No newline at end of file
diff --git a/src/render/components/control/text.js b/src/render/components/control/text.js
new file mode 100644
index 0000000..cd18db9
--- /dev/null
+++ b/src/render/components/control/text.js
@@ -0,0 +1,84 @@
+const inputLength = 230;
+
+const metaKeys = [
+ "Tab",
+ "CapsLock",
+ "Shift",
+ "Control",
+ "Alt",
+ "Meta",
+ " ",
+ "ArrowLeft",
+ "ArrowRight",
+ "ArrowUp",
+ "ArrowDown",
+ "Enter",
+ "Backspace",
+ "Escape"
+];
+
+function useText(elementId, parentId, left)
+{
+ let el;
+ let parent;
+
+ const show = Vue.ref(false);
+ const leftSide = Vue.ref(false);
+
+ const calcSide = () => {
+ parent.clientWidth - left.value < inputLength ? leftSide.value = true : leftSide.value = false;
+ }
+
+ /* handle user typing */
+ Vue.onMounted(() => {
+
+ el = document.getElementById(elementId);
+ parent = document.getElementById(parentId);
+
+ window.addEventListener("keydown", (e) => {
+
+ if (!show.value && !metaKeys.includes(e.key) && !e.ctrlKey)
+ {
+ show.value = true;
+ }
+ else if (show.value && ((el.value.length === 1 && e.key === "Backspace") || e.key === "Escape"))
+ {
+ show.value = false;
+ }
+
+ if (e.key === "Enter" && el.value)
+ {
+ if (el.value.length >= 250) {
+ alert("250 character limit")
+ return;
+ }
+
+ window.mainApi.send("message", {
+ category: "text",
+ text: el.value,
+ blob: null
+ });
+
+ show.value = false;
+ }
+ });
+
+ calcSide();
+
+ });
+
+ Vue.watch(left, calcSide);
+
+ Vue.watch(show, (c, _p) => {
+ if (c) {
+ el.focus();
+ } else {
+ el.value = "";
+ el.blur();
+ }
+ });
+
+ return { leftSide, show };
+}
+
+export default useText;
diff --git a/src/render/components/controllers/bubble.control.ts b/src/render/components/controllers/bubble.control.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/render/components/controllers/helpers.ts b/src/render/components/controllers/helpers.ts
deleted file mode 100644
index 23976a8..0000000
--- a/src/render/components/controllers/helpers.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { ref } from "vue";
-import anime from "animejs";
-import { v4 as uuidv4 } from 'uuid';
-
-
-export function animateTextInput () {
-
- const side = ref("right");
-
- function show () {
- const t1 = (side.value === "right") ? 50 : -70;
- const t2 = (side.value === "right") ? 110 : -130;
-
- anime({
- targets: '#textInput',
- opacity: [0, 1],
- translateX: [t1, t2],
- scale: [0.3, 1],
- duration: 500,
- easing: 'easeOutExpo',
- })
-
- }
-
- function hide () {
- const t = (side.value === "right") ? 50 : -80;
-
- anime({
- targets: '#textInput',
- opacity: [1, 0],
- translateX: t,
- scale: 0.3,
- duration: 500,
- easing: 'easeOutExpo',
- })
-
- }
-
- function switchSide () {
- const t = (side.value === "right") ? -130 : 110;
-
- anime({
- targets: '#textInput',
- translateX: t,
- duration: 500,
- easing: 'easeOutExpo',
- })
-
- }
-
- return {
- side,
- show,
- hide,
- switchSide
- };
-
-}
-
-export function animateAudioInput () {
-
- function show () {
- anime({
- targets: '#recIcon',
- opacity: [0, 0.75],
- scale: [0.0, 1],
- duration: 250,
- easing: 'linear',
- })
- }
-
- function hide () {
- anime({
- targets: '#recIcon',
- opacity: [0.75, 0],
- scale: [1, 0],
- duration: 250,
- easing: 'linear',
- })
- }
-
- return {
- show,
- hide
- };
-
-}
-
-
-export function newMessage ({
- text=false,
- audio=false,
- context=false,
- uid=uuidv4()
-}) {
- return {
- text: text,
- audio: audio,
- context: context,
- uid: uid
- };
-}
diff --git a/src/render/components/controllers/inputItem.control.audio.ts b/src/render/components/controllers/inputItem.control.audio.ts
deleted file mode 100644
index facf30c..0000000
--- a/src/render/components/controllers/inputItem.control.audio.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { onMounted, onUnmounted, ref, Ref } from "vue";
-import { postMessage } from "@/render/ipc";
-import { newMessage, animateAudioInput } from "./helpers";
-import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
-
-export default function useAudioInputController (typing: Ref) {
-
- const recording = ref(false);
- let mediaRecorder: MediaRecorder;
-
- const { show, hide } = animateAudioInput();
-
- // initialize audio
- const conf = {audio: true, video: false}
- navigator.mediaDevices.getUserMedia(conf).then((stream: MediaStream) => {
-
- const options = {mimeType: 'audio/webm'};
- mediaRecorder = new MediaRecorder(stream, options);
-
- // post any mew audio to backend
- mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => {
- e.data.arrayBuffer().then((buff: ArrayBuffer) => {
- postAudioChunk(buff);
- });
- });
-
- // get audio and post new message to backend
- mediaRecorder.addEventListener('stop', (_e: Event) => {
- invokeReturnAudio().then((audio: ArrayBuffer[] | Error) => {
- console.log(audio);
- // postMessage(newMessage({audio: audio}));
- });
- });
-
- });
-
- // start recording on space bar
- const record = () => {
- console.log("INPT:Capturing audio...")
- mediaRecorder.start();
- recording.value = true;
- show()
- }
-
- // stop recording and send on release
- const stop = () => {
- console.log("INPT:Stopping record.")
- mediaRecorder.stop();
- recording.value = false;
- hide();
- }
-
- const onKeyDown = (e: KeyboardEvent) => {
- if (e.keyCode == 32 && !typing.value) record();
- }
-
- const onKeyUp = (e: KeyboardEvent) => {
- if (e.keyCode == 32 && recording.value) stop();
- }
-
- //-----------------------------------------------------------
-
- onMounted(() => {
- window.addEventListener("keydown", onKeyDown);
- window.addEventListener("keyup", onKeyUp);
- });
-
- onUnmounted(() => {
- window.removeEventListener("keydown", onKeyDown);
- window.removeEventListener("keyup", onKeyUp);
- })
-
- return {
- recording
- }
-
-}
diff --git a/src/render/components/controllers/inputItem.control.text.ts b/src/render/components/controllers/inputItem.control.text.ts
deleted file mode 100644
index 941ae89..0000000
--- a/src/render/components/controllers/inputItem.control.text.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
-import { postMessage } from "@/render/ipc";
-import { newMessage, animateTextInput } from "./helpers";
-
-
-export default function useTextInputController(elementX: Ref) {
-
- let textInput: HTMLInputElement | null;
-
- const { side, show, hide, switchSide } = animateTextInput();
-
- let firstKey = true;
- const typing = ref(false);
-
- // Prep inputItem for typing.
- const prepInput = () => {
- show()
- typing.value = true
- }
-
- // Clear and hide inputItem after done typing.
- const clearInput = () => {
-
- if (textInput) {
- textInput.value = "";
- textInput.blur();
- }
-
- hide()
- firstKey = true;
- typing.value = false;
- }
-
- // Send a message and clean up after.
- const sendMessage = () => {
- if (textInput) {
-
- // Send it to the backend for processing.
- const message = newMessage({
- text: false
- });
-
- // postMessage(message);
-
- clearInput()
- }
- }
-
- // Keys that are capable of opening the text input (numbers and letters).
- const isHotKey = (key: number) => {
- if (key >= 47 && key <= 91) { // a letter
- return true
- }
- }
-
- //---Callbacks-----------------------------------------------
-
- const onKeyDown = (e: KeyboardEvent) => {
- const key = e.keyCode;
-
- if (textInput) {
-
- // Only runs on firstKey.
- if (firstKey) {
-
- if (!isHotKey(key)) {
- return
- }
-
- prepInput()
- }
-
- textInput.focus();
-
- // Close input when no text or on ESC.
- if ((textInput.value == "") && (!firstKey) && (key === 8)) { // backspace
- clearInput()
- return
- }
-
- if (key === 27) { // escape
- clearInput()
- return
- }
-
- // Close and send on enter.
- if (key === 13) {
- if (textInput.value) {
- sendMessage()
- return
- }
- }
-
- if (firstKey) firstKey = false
- }
- }
-
- //-----------------------------------------------------------
-
- // Watch parent position and update side.
- watch(elementX, (elementX, _previous) => {
- const winW = window.innerWidth
-
- // Logic depends on the side we are on.
- if (side.value === "right") {
- if (winW - elementX < 230) {
- switchSide()
- side.value = "left"
- }
- }
-
- else {
- if (winW - elementX > 230) {
- switchSide()
- side.value = "right"
- }
- }
-
- });
-
- onMounted(() => {
- textInput = document.getElementById("textInput") as HTMLInputElement;
- window.addEventListener("keydown", onKeyDown);
- })
-
- onUnmounted(() => {
- window.removeEventListener("keydown", onKeyDown);
- });
-
- return {
- typing
- }
-
-}
diff --git a/src/render/components/controllers/messenger.control.ts b/src/render/components/controllers/messenger.control.ts
deleted file mode 100644
index b9c3a96..0000000
--- a/src/render/components/controllers/messenger.control.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { ref } from 'vue';
-import useScroll from "@/render/composables/useScroll";
-
-const messagesRef = ref();
-
-/* seed the canvas with messages */
-const seedCanvas = (messages: Message[]) => {
- messagesRef.value = messages;
-}
-
-const addMessage = (message: Message) => {
- messagesRef.value.push(message);
-}
-
-const updateMessage = (message: Message) => {
-
- let target_message = messagesRef.value.filter((m: Message) => {
- return m.uid = message.uid;
- })[0];
-
- if (target_message) {
- target_message = message;
- }
-
-}
-
-export default function useMessages() {
-
- const { updateScrollRef, adjustScroll } = useScroll("messenger");
-
- return {
- messagesRef,
- seedCanvas,
- addMessage,
- updateMessage
- };
-
-}
diff --git a/src/render/components/form.js b/src/render/components/form.js
new file mode 100644
index 0000000..fbc7b0e
--- /dev/null
+++ b/src/render/components/form.js
@@ -0,0 +1,83 @@
+const debounce = (fn, delay) => {
+
+ let timeoutId;
+
+ const wrapper = (...args) => {
+
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ timeoutId = setTimeout(() => {
+ fn(...args);
+ }, delay);
+
+ }
+
+ return wrapper;
+}
+
+
+
+const SmartForm =
+{
+ props: ["fields", "channel", "modifier"],
+
+ setup(props)
+ {
+ let logsEl;
+
+ const submit = debounce(async (data) => {
+
+ let object = {};
+ data.forEach(function(value, key){
+ object[key] = value;
+ });
+
+ const err = await window.mainApi.invoke(props.channel, object)
+
+ if (err) {
+ logsEl.innerText = err;
+ logsEl.style.color = "red";
+ return;
+ }
+
+ logsEl.style.color = "green";
+ logsEl.innerText = props.modifier;
+
+ }, 2000);
+
+ Vue.onMounted(() => {
+
+ const el = document.getElementById("smart-form");
+
+ logsEl = el.querySelector(".smart-form-logs");
+
+ logsEl.innerText = props.modifier;
+
+ el.addEventListener("input", () => {
+ submit(new FormData(el))
+ });
+
+ });
+ },
+
+ template: `
+ `
+}
+
+export default SmartForm;
diff --git a/src/render/components/header.js b/src/render/components/header.js
new file mode 100644
index 0000000..5aee0de
--- /dev/null
+++ b/src/render/components/header.js
@@ -0,0 +1,23 @@
+const Header =
+{
+ setup() {
+ const onNavBar = (command) => window.mainApi.send("nav", command);
+ return { onNavBar };
+ },
+
+ template: `
+
+
+ `
+}
+
+export default Header;
\ No newline at end of file
diff --git a/src/render/components/header.vue b/src/render/components/header.vue
deleted file mode 100644
index 63716c8..0000000
--- a/src/render/components/header.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/render/components/input.js b/src/render/components/input.js
new file mode 100644
index 0000000..b556dd0
--- /dev/null
+++ b/src/render/components/input.js
@@ -0,0 +1,55 @@
+import useText from "./control/text.js";
+import draggify from "./control/draggify.js";
+
+const Input =
+{
+ props: ["person"],
+
+ setup(props)
+ {
+ const initials = Vue.ref("");
+
+ const recording = Vue.ref(false);
+
+ const { elementX, elementY } = draggify("input", "app", 15);
+
+ const { leftSide, show } = useText("input-text", "app", elementX);
+
+ window.mainApi.on("record", (status) => recording.value = status);
+
+ Vue.watch(() => props.person, (c, _p) => initials.value = getInitials(c));
+
+ return { initials, elementX, elementY, recording, leftSide, show };
+ },
+
+ template: `
+ `
+}
+
+function getInitials(name)
+{
+ let rgx = new RegExp(/(\p{L}{1})\p{L}+/, 'gu');
+
+ let initials = [...name.matchAll(rgx)] || [];
+
+ return (
+ (initials.shift()?.[1] || '') + (initials.pop()?.[1] || '')
+ ).toUpperCase();
+}
+
+export default Input;
diff --git a/src/render/components/inputItem.vue b/src/render/components/inputItem.vue
deleted file mode 100644
index edd844f..0000000
--- a/src/render/components/inputItem.vue
+++ /dev/null
@@ -1,220 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/render/components/login.js b/src/render/components/login.js
new file mode 100644
index 0000000..f9a0f54
--- /dev/null
+++ b/src/render/components/login.js
@@ -0,0 +1,40 @@
+import SmartForm from "./form.js";
+
+const Login =
+{
+ components: {
+ SmartForm
+ },
+
+ setup()
+ {
+ return;
+ },
+
+ template: `
+
+
+
+
+
`
+}
+
+export default Login;
\ No newline at end of file
diff --git a/src/render/components/login.vue b/src/render/components/login.vue
deleted file mode 100644
index bece6ea..0000000
--- a/src/render/components/login.vue
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/render/components/message.js b/src/render/components/message.js
new file mode 100644
index 0000000..ce7231b
--- /dev/null
+++ b/src/render/components/message.js
@@ -0,0 +1,62 @@
+import Bubble from "./bubble.js";
+import Context from "./context.js";
+
+const Message =
+{
+ components: {
+ Bubble,
+ Context
+ },
+
+ props: ["modifier", "content", "context", "avatar", "id", "seen"],
+
+ setup(props)
+ {
+ return { calcChild };
+ },
+
+ template: `
+
+
+
+
+
+
+
`
+}
+
+function calcChild(index, len)
+{
+ if (len === 1)
+ {
+ return "none-child";
+ }
+ else if (index === 0)
+ {
+ return "first-child";
+ }
+ else if (index === len - 1)
+ {
+ return "last-child";
+ }
+ else
+ {
+ return "middle-child";
+ }
+}
+
+export default Message;
diff --git a/src/render/components/messenger.js b/src/render/components/messenger.js
new file mode 100644
index 0000000..572613d
--- /dev/null
+++ b/src/render/components/messenger.js
@@ -0,0 +1,86 @@
+import onDrop from "./control/drop.js";
+import scroll from "./control/scroll.js";
+
+import WS from "./ws.js";
+import Input from "./input.js";
+import Settings from "./settings.js";
+import Message from "./message.js";
+
+const Messenger = {
+
+ components: {
+ WS,
+ Settings,
+ Message,
+ Input
+ },
+
+ setup()
+ {
+ const person = Vue.ref("");
+ const messages = Vue.ref([]);
+
+ let current = Vue.ref();
+
+ Vue.onMounted(() => {
+
+ window.mainApi.on("message", (update) => {
+
+ if (update.person)
+ {
+ person.value = update.person;
+ messages.value = update.messages;
+ current.value = messages.value.at(-1);
+ }
+ else if (update.name)
+ {
+ person.value = update.name;
+ }
+ else if (update.modifier)
+ {
+ messages.value.push(update);
+ current.value = update;
+ }
+ else if (update.category)
+ {
+ current.value.content.push(update);
+ }
+ else if (update.context)
+ {
+ current.value.context = update.context;
+ }
+ else
+ {
+ current.value.content.at(-1).text = update.text;
+ }
+
+ setTimeout(scroll, 10);
+
+ });
+
+ window.mainApi.send("messenger");
+ });
+
+ return { person, messages, onDrop };
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
`
+}
+
+export default Messenger;
diff --git a/src/render/components/messenger.vue b/src/render/components/messenger.vue
deleted file mode 100644
index ee56549..0000000
--- a/src/render/components/messenger.vue
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/render/components/settings.js b/src/render/components/settings.js
new file mode 100644
index 0000000..8bcb994
--- /dev/null
+++ b/src/render/components/settings.js
@@ -0,0 +1,40 @@
+
+const Settings =
+{
+ setup() {
+ const active = Vue.ref(false);
+
+ function hideSettings(e) {
+ if (e.key == "Escape") {
+ active.value = false;
+ window.removeEventListener("keydown", hideSettings);
+ }
+ }
+
+ function showSettings() {
+ active.value = true;
+ window.addEventListener("keydown", hideSettings);
+ }
+
+ const logout = async () => {
+ await window.mainApi.send("logout");
+ }
+
+ return { showSettings, active, logout, window };
+ },
+
+ template: `
+
+
+
+
+
+
+ `
+}
+
+export default Settings;
\ No newline at end of file
diff --git a/src/render/components/settings.vue b/src/render/components/settings.vue
deleted file mode 100644
index cf881c3..0000000
--- a/src/render/components/settings.vue
+++ /dev/null
@@ -1,164 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/render/components/splash.js b/src/render/components/splash.js
new file mode 100644
index 0000000..220eaad
--- /dev/null
+++ b/src/render/components/splash.js
@@ -0,0 +1,50 @@
+const Splash =
+{
+ template: `
+
+
+
+ `
+}
+
+export default Splash;
\ No newline at end of file
diff --git a/src/render/components/splash.vue b/src/render/components/splash.vue
deleted file mode 100644
index 247847a..0000000
--- a/src/render/components/splash.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/render/components/ws.js b/src/render/components/ws.js
new file mode 100644
index 0000000..265915b
--- /dev/null
+++ b/src/render/components/ws.js
@@ -0,0 +1,35 @@
+
+const WS =
+{
+ setup()
+ {
+ const status = Vue.ref();
+
+ Vue.onMounted(() => {
+
+ window.mainApi.on("ws", (val) => {
+ status.value = val;
+ });
+
+ });
+
+ return { status, window }
+ },
+
+ template: `
+
+
+
![]()
+
+
+
+
`
+}
+
+export default WS;
\ No newline at end of file
diff --git a/src/render/composables/useDraggify.ts b/src/render/composables/useDraggify.ts
deleted file mode 100644
index f3d8978..0000000
--- a/src/render/composables/useDraggify.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { onMounted, ref, onUnmounted } from "vue";
-
-//---Dragabble Helper Funcs--------------------------------------
-
-// Calculate distance to nearest side.
-function calcSideProximity (elementX: number, winW: number) {
-
- // Calc right short.
- let short = winW - elementX - 40;
-
- // See if it's left short.
- if (elementX + 20 < winW / 2) {
- short = elementX
- }
-
- return short
-}
-
-// Update elementX or elementY value on window resize
-function calcPosition (elementPosition: number, percent: number,
- short: number, win: number) {
-
- // Is it close to the right/bottom side?
- if (percent > 0.75) {
- elementPosition = win - short - 40;
- }
-
- // Is it not close to a side?
- if (percent < 0.75 && percent > 0.25) {
- elementPosition = win * percent
- }
-
- return elementPosition
-}
-
-export default function draggify(elementId: string, xStart: number,
- yStart: number, margin: number) {
-
- let element: HTMLElement | null;
-
- // Cords of inputItem.
- const elementX = ref(0);
- const elementY = ref(0);
-
- // Position of inputItem on terms of percentage of window.
- let percentX: number;
- let percentY: number;
-
- // How close inputItem is to closest X or Y side.
- let xShort: number;
- let yShort: number;
-
- // Keep track of window size;
- let winW = window.innerWidth;
- let winH = window.innerHeight;
-
- //---Reposition Anime-----------------------------------------------
-
- // Move element to target smoothly.
- const repositionAnime = (xChange: number, yChange: number) => {
- const xStep = xChange / 6000;
- const yStep = yChange / 6000;
-
- for (let i = 1; i <= 6000; i++) {
-
- setTimeout(() => {
- elementX.value += xStep;
- elementY.value += yStep;
- }, 16) // 60fps
-
- }
- }
-
- //---Event Handlers-----------------------------------------------
-
- // Update the position of inputItem on mouse dragging.
- const onMouseMove = (e: any) => {
- e.preventDefault()
- element = document.getElementById(elementId);
-
- if (element) {
- const inputItemRect = element.getBoundingClientRect();
- elementX.value = inputItemRect.left + e.movementX;
- elementY.value = inputItemRect.top + e.movementY;
- }
-
- }
-
- // Add an event listener for dragging.
- const onMouseDown = (_e: any) => {
- _e.preventDefault()
- window.addEventListener('mousemove', onMouseMove, true);
- }
-
- // Update position references when user is done moving targetEl.
- const onMouseUp = (_e: any) => {
- window.removeEventListener('mousemove', onMouseMove, true);
-
- // See if and calculate reposition.
- let x = 0; // vector change
- let y = 0;
-
- if (elementX.value < 0) {
- x = (elementX.value - margin)*-1
- }
-
- if (elementX.value > winW - 40) {
- const b = winW - 40 - margin;
- x = (elementX.value - b)*-1
- }
-
- // Reposition y
- if (elementY.value < 0) {
- y = (elementY.value - margin)*-1
- }
-
- if (elementY.value > winH - 40) {
- const d = winH - 40 - margin;
- y = (elementY.value - d)*-1
- }
-
- // Reposition if needed.
- if (x !== 0 || y !== 0) repositionAnime(x, y);
-
- xShort = calcSideProximity(elementX.value, winW);
- yShort = calcSideProximity(elementY.value, winH);
-
- // Update percentages.
- percentX = elementX.value / winW;
- percentY = elementY.value / winH;
-
- // Save position.
- const position = {
- x: elementX.value,
- y: elementY.value
- }
- window.localStorage.setItem("inputItem_position", JSON.stringify(position));
- }
-
- // Update position of targetEl on windowResize.
- const onWindowResize = (_e: any) => {
-
- // Update window dimensions.
- winW = window.innerWidth;
- winH = window.innerHeight;
-
- elementX.value = calcPosition(elementX.value, percentX, xShort, winW);
- elementY.value = calcPosition(elementY.value, percentY, yShort, winH);
-
- }
-
- //---------------------------------------------------------------
-
- onMounted(() => {
- element = document.getElementById(elementId);
-
- if (element) {
- element.addEventListener('mousedown', onMouseDown, false);
- }
-
- window.addEventListener("mouseup", onMouseUp, false);
-
- // Initialize the positional references.
- percentX = elementX.value / window.innerWidth;
- percentY = elementY.value / window.innerHeight;
-
- xShort = calcSideProximity(elementX.value, window.innerWidth);
- yShort = calcSideProximity(elementX.value, window.innerHeight);
-
- // Then, we can listen for window resize.
- window.addEventListener('resize', onWindowResize, false);
- });
-
- // Try loading initPosition, otherwise set default values
- let initPosition: any;
- const rawData = window.localStorage.getItem("inputItem_position")
-
- if (rawData) {
- initPosition = JSON.parse(rawData)
- } else {
- initPosition = {x: xStart, y: yStart}
- }
-
- elementX.value = initPosition.x;
- elementY.value = initPosition.y;
-
- // remove event listeners on component dismount.
- onUnmounted(() => {
- window.removeEventListener('resize', onWindowResize);
- window.removeEventListener('mouseup', onMouseUp);
- if (element) element.removeEventListener('mousedown', onMouseDown);
- })
-
- return {
- elementX,
- elementY
- }
-
-}
diff --git a/src/render/composables/useIpcRend.ts b/src/render/composables/useIpcRend.ts
deleted file mode 100644
index 9ba7f73..0000000
--- a/src/render/composables/useIpcRend.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-
-import { IpcRendererEvent } from "electron";
-
-export class IpcRendererListener implements IIpcListener {
-
- readonly channel: string;
-
- readonly _listenerCallback: IpcListenerCallback;
-
- constructor(options: {
- channel: string;
- listenerCallback: IpcListenerCallback;
- }) {
- this.channel = options.channel;
- this._listenerCallback = options.listenerCallback;
- }
-
- listen() {
- this.remove();
- window.ipcRenderer.on(this.channel, this._onPost);
- }
-
- remove() {
- window.ipcRenderer.removeAllListeners(this.channel);
- }
-
- private _onPost = (_e: IpcRendererEvent, payload: InputParam): void => {
- console.log(`[IPC] Post: ${this.channel}`);
- this._listenerCallback(payload);
- }
-
-}
-
-
-export default function useIpcRenderer () {
-
- const invoke = async (endpoint: string, payload: any) => {
- try {
- const res = await window.ipcRenderer.invoke(endpoint, payload);
- return res;
- } catch (e) {
- throw e;
- }
- }
-
- const post = (endpoint: string, payload: any) => {
- window.ipcRenderer.send(endpoint, payload);
- };
-
- return {
- invoke,
- post,
- }
-}
diff --git a/src/render/composables/useMessages.ts b/src/render/composables/useMessages.ts
deleted file mode 100644
index f731584..0000000
--- a/src/render/composables/useMessages.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// shared
-import { ref, Ref } from "vue";
-import useScroll from "@/render/composables/useScroll";
-
-export const messages: Ref> = ref([]);
-
-export const setMessages: IpcListenerCallback> = (payload) => {
- messages.value = payload as Array;
-}
-
-export const addMessage: IpcListenerCallback = (payload) => {
- messages.value.push(payload as Message);
-}
-
-export const updateMessage: IpcListenerCallback = (payload) => {
- const message = payload as Message;
-
- let targetMessage = messages.value.filter((m: Message) => {
- return m.uid = message.uid;
- })[0];
-
- if (targetMessage) {
- targetMessage = message;
- }
-
-}
-
-export default {
- messages,
- setMessages,
- addMessage,
- updateMessage
-};
diff --git a/src/render/composables/useProfile.ts b/src/render/composables/useProfile.ts
deleted file mode 100644
index 3575de7..0000000
--- a/src/render/composables/useProfile.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-// shared
-import { ref } from "vue";
-
-export const profile = ref();
-
-export const authComplete = ref(false);
-
-export const setProfile: IpcListenerCallback = (payload) => {
- payload ? profile.value = payload : clearProfile();
- showRender();
-};
-
-export const clearProfile = () => {
- profile.value = null;
-};
-
-export const showRender = () => {
- authComplete.value = true;
-};
-
-export default {
- setProfile,
- clearProfile,
- profile,
- showRender,
- authComplete,
-};
diff --git a/src/render/composables/useScroll.ts b/src/render/composables/useScroll.ts
deleted file mode 100644
index 811e7fc..0000000
--- a/src/render/composables/useScroll.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-export default function useScroll(element: string) {
-
- let isScrolledToBottom: boolean;
- const view = document.getElementById(element)
-
- // Update isScrolledToBottom
- const updateScrollRef = () => {
- if (view) isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1;
- return isScrolledToBottom;
- }
-
- // Adjust scroll after we add content to the messenger.
- const adjustScroll = () => {
- if (view) {
- view.scrollTo({
- top: view.scrollHeight - view.clientHeight,
- behavior: 'smooth'
- });
- }
- }
-
- return {
- updateScrollRef,
- adjustScroll,
- };
-
-}
\ No newline at end of file
diff --git a/src/render/index.html b/src/render/index.html
new file mode 100644
index 0000000..e45bb6c
--- /dev/null
+++ b/src/render/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/render/index.js b/src/render/index.js
new file mode 100644
index 0000000..d7df8b0
--- /dev/null
+++ b/src/render/index.js
@@ -0,0 +1,5 @@
+import App from "./components/app.js";
+
+window.path = "./";
+
+Vue.createApp(App).mount("#app");
diff --git a/src/render/ipc.ts b/src/render/ipc.ts
deleted file mode 100644
index bc68049..0000000
--- a/src/render/ipc.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-
-import useIpc from "@/render/composables/useIpcRend";
-import * as rendererListeners from "./listeners";
-
-const { post, invoke } = useIpc();
-
-/**
- *
- * Account and auth related endpoints
- *
- */
-
-
-export const invokeLogin = async (
- payload: LoginPayload
-): Promise => (
- await invoke('invoke-account-login', JSON.stringify(payload))
-);
-
-export const invokeLogout = async (): Promise => (
- await invoke("invoke-account-logout", null)
-);
-
-/**
- *
- * Audio endpoints
- *
- */
-
-export const postAudioChunk = (chunk: ArrayBuffer): void => (
- post("post-audio-collect", chunk)
-);
-
-export const invokeReturnAudio = async (): Promise => (
- await invoke("invoke-audio-flush", null)
-);
-
-/**
- *
- * Crimata Platform (session) endpoints
- *
- */
-
-export const invokeSession = async (cid: string): Promise => (
- await invoke("messenger-init", cid)
-);
-
-export const postMessage = (payload: Message): void => (
- post('post-session-send', payload)
-);
-
-export const postAppMount = (): void => (
- post('post-app-mount', null)
-);
-
-
-/**
- *
- * Ipc Renderer Listeners
- *
- */
-
-let ipcListeners: IPCListeners = {};
-
-export const initIpcRendererListeners = () => {
- for (const [key, listener] of Object.entries(rendererListeners)) {
- if (!(key in ipcListeners)) {
- ipcListeners[key] = listener;
- listener.listen();
- }
- }
-};
-
-export const removeListeners = () => {
- for (const [key, listener] of Object.entries(rendererListeners)) {
- listener.remove();
- }
- ipcListeners = {};
-};
-
diff --git a/src/render/listeners.ts b/src/render/listeners.ts
deleted file mode 100644
index 876d206..0000000
--- a/src/render/listeners.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-
-import { IpcRendererListener } from "./composables/useIpcRend"
-import { setProfile } from "./composables/useProfile";
-import { setMessages, addMessage, updateMessage } from "./composables/useMessages";
-
-const SET_PROFILE_CHANNEL = "set-profile";
-
-const INIT_MESSAGES_CHANNEL = "init-messages";
-const ADD_MESSAGE_CHANNEL = "add-message";
-const UPDATE_MESSAGE_CHANNEL = "update-message";
-
-export const setProfileListener = new IpcRendererListener({
- channel: SET_PROFILE_CHANNEL,
- listenerCallback: setProfile
-});
-
-export const initMessagesListener = new IpcRendererListener({
- channel: INIT_MESSAGES_CHANNEL,
- listenerCallback: setMessages
-});
-
-
-export const addMessagesListener = new IpcRendererListener({
- channel: ADD_MESSAGE_CHANNEL,
- listenerCallback: addMessage
-});
-
-export const updateMessagesListener = new IpcRendererListener({
- channel: UPDATE_MESSAGE_CHANNEL,
- listenerCallback: updateMessage
-});
-
diff --git a/src/render/main.css b/src/render/main.css
new file mode 100644
index 0000000..cd26154
--- /dev/null
+++ b/src/render/main.css
@@ -0,0 +1,582 @@
+
+@font-face {
+ font-family: "Default";
+ src: url("assets/fonts/SF-Pro-Text-Regular.otf");
+}
+
+@font-face {
+ font-family: "Compact";
+ src: url("assets/fonts/SF-Compact-Display-Bold.otf");
+}
+
+@font-face {
+ font-family: "Rounded";
+ src: url("assets/fonts/SF-Compact-Rounded-Bold.otf");
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+ /*background-color: rgba(235, 235, 235, 0.75);*/
+}
+
+/* The first word of the class is the component that it modifys. */
+
+#splash {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+#app {
+ position: relative; /* must explicitly be declared */
+ font-family: "Default";
+ -webkit-font-smoothing: antialiased;
+ height: 100vh; /* 100% for website */
+ width: 100vw; /* 100% for website */
+ border-radius: 15px;
+}
+
+#header-titlebar {
+ position: absolute;
+ width: 100%;
+ height: 54px;
+ opacity: 0.75;
+ background-color: #EBEBEB;
+ -webkit-app-region: drag;
+ border: none;
+ outline: none;
+ z-index: 1;
+ border-top-left-radius: 15px;
+ border-top-right-radius: 15px;
+}
+
+.contacts {
+ margin: 0;
+ padding: 0;
+ border-radius: inherit;
+ background-color: #DBDBDB;
+}
+
+.contacts > li {
+ padding: 12px;
+ display: flex;
+}
+
+.contacts img {
+ height: 30px;
+ border-radius: 50%;
+}
+
+.contacts .info {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.contacts .name {
+
+}
+
+.contacts .email {
+ font-family: "Compact";
+ font-size: 12px;
+ color: #898989;
+ overflow: w;
+ word-wrap: break-word;
+}
+
+#header-menu {
+ position: absolute;
+ margin-left: 20px;
+ margin-top: 20px;
+ z-index: 2;
+}
+
+.header-menu-button {
+ min-width: 14px;
+ min-height: 14px;
+ border-radius: 50%;
+}
+
+.header-exit-button {
+ background-color: #FF6157;
+}
+
+.header-exit-button:active {
+ background: #c14645;
+}
+
+.header-min-button {
+ background-color: #FFC12F;
+ margin-left: 8px;
+}
+
+.header-min-button:active {
+ background-color: #c08e38;
+}
+
+#login {
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+#messenger {
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+}
+
+/* hide native scrollbar */
+#messenger::-webkit-scrollbar {
+ display: none;
+}
+
+#ws {
+ position: absolute;
+ width: 100%;
+ height: 54px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1;
+}
+
+#ws-logo {
+ transition: all 0.5s;
+}
+
+#ws-logo.move {
+ transform: translateX(15px);
+}
+
+#ws-dots {
+ opacity: 0;
+ transition: all 0.5s;
+}
+
+#ws-dots.move {
+ opacity: 1;
+ transform: translateX(-15px);
+}
+
+.ws-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background-color: #48E065;
+ position: relative;
+ transform: translateX(-15px);
+ animation: ws-dot-flashing 1s infinite linear alternate;
+ animation-delay: .25s;
+}
+
+.ws-dot::before, .ws-dot::after {
+ content: '';
+ display: inline-block;
+ position: absolute;
+ box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
+}
+
+.ws-dot::before {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background-color: #48E065;
+ left: -9px;
+ animation: ws-dot-flashing 1s infinite alternate;
+ animation-delay: 0s;
+}
+
+.ws-dot::after {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background-color: #48E065;
+ left: 9px;
+ animation: ws-dot-flashing 1s infinite alternate;
+ animation-delay: 0.5s;
+}
+
+@keyframes ws-dot-flashing {
+ 0% {
+ background-color: #48E065;
+ }
+ 50%,
+ 100% {
+ background-color: #9B9B9B;
+ }
+}
+
+#input {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ width: 40px;
+ height: 40px;
+ z-index: 3;
+ /* To prevent window drag when overlapping with titlebar. */
+ -webkit-app-region: no-drag;
+ border-radius: 50%;
+ /* Set opacity here to not affect child. */
+ background-color: rgba(235, 235, 235, 0.75);
+ cursor: pointer;
+ box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
+}
+
+#input-text {
+ position: absolute;
+ min-width: 150px;
+ height: 16px;
+ border-radius: 18px;
+ padding: 10px;
+ outline: none;
+ border: none;
+ font-size: 14px;
+ pointer-events: none;
+ background-color: white;
+ z-index: -1;
+ box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
+ transform-origin: center;
+ opacity: 0;
+ transform: translateX(70%);
+ transition: all 0.5s;
+}
+
+#input-text.show {
+ opacity: 1;
+}
+
+#input-text.leftSide {
+ transform: translateX(-70%);
+}
+
+#settings {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(235, 235, 235, 0.75);
+ z-index: 4;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ animation-name: settings-appear;
+ animation-duration: 0.5s;
+ border-radius: inherit;
+}
+
+@keyframes settings-appear {
+ from {
+ background-color: rgba(235, 235, 235, 0);
+ }
+ to {
+ background-color: rgba(235, 235, 235, 0.75);
+ }
+}
+
+.settings-icon {
+ position: absolute;
+ right: 0;
+ border: none;
+ outline: none;
+ display: flex;
+ flex-direction: row;
+ padding: 5px;
+ margin-right: 20px;
+ margin-top: 19px;
+ z-index: 2;
+ background-color: Transparent;
+}
+
+.settings-icon:hover {
+ cursor: pointer;
+}
+
+.settings-account {
+ font-size: 12px;
+ font-weight: bold;
+ margin-bottom: 25px;
+}
+
+.settings-logout-button {
+ font-family: "Compact";
+ background-color: #B7B7B7;
+ padding: 10px 20px;
+ border-radius: 20px;
+ font-size: 14px;
+}
+
+.settings-logout-button:hover {
+ cursor: pointer;
+}
+
+.settings-version {
+ position: absolute;
+ left: 50%;
+ top: 75%;
+ transform: translate(-50%, -50%);
+ font-size: 12px;
+ font-weight: bold;
+ color: #575757;
+}
+
+.message {
+ width: inherit;
+ display: flex;
+ flex-direction: column;
+ padding-top: 9px;
+ padding-bottom: 9px;
+ animation-name: message-init-anim;
+ animation-duration: 0.25s;
+}
+
+@keyframes message-init-anim {
+ from {
+ opacity: 0;
+ } to {
+ opacity: 1;
+ }
+}
+
+.message:first-child {
+ margin-top: 55px;
+}
+
+.message:last-child {
+ margin-bottom: 6px;
+}
+
+.message-session {
+ width: 100vw;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-family: "Compact";
+ font-size: 12px;
+ color: #9B9B9B;
+ margin-bottom: 18px;
+}
+
+.client-message {
+ align-items: flex-end;
+}
+
+.ai-message {
+ align-items: flex-start;
+}
+
+.admin-message {
+ align-items: center;
+}
+
+.bubble {
+ position: relative;
+ max-width: 66%;
+ font-size: 14px;
+ border-radius: 18px;
+ margin-bottom: 4px;
+ overflow-wrap: break-word;
+}
+
+.bubble-notify {
+ position: absolute;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background-color: #58D9FF;
+ top: -5px;
+ left: -5px;
+ border: 2px solid #EBEBEB;
+ transform: scale(0);
+ animation-name: bubble-notify-anim;
+ animation-duration: 5s;
+}
+
+@keyframes bubble-notify-anim {
+ 0%, 90% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
+
+.ai-bubble {
+ background-color: #FFFFFF;
+ margin-left: 15px;
+}
+
+.client-bubble {
+ color: white;
+ background-color: #58C4FD;
+ margin-right: 15px;
+}
+
+.ai-first-child {
+ border-bottom-left-radius: 9px;
+}
+
+.ai-middle-child {
+ border-top-left-radius: 9px;
+ border-bottom-left-radius: 9px;
+}
+
+.ai-last-child {
+ border-top-left-radius: 9px;
+}
+
+.client-first-child {
+ border-bottom-right-radius: 9px;
+}
+
+.client-middle-child {
+ border-top-right-radius: 9px;
+ border-bottom-right-radius: 9px;
+}
+
+.client-last-child {
+ border-top-right-radius: 9px;
+}
+
+.bubble > p {
+ margin: 0px;
+ padding: 10px;
+}
+
+.bubble > div {
+ border-radius: inherit;
+}
+
+.bubble > img {
+ border-radius: inherit;
+ display: block;
+ max-width: 100%;
+}
+
+.bubble a {
+ display: inline-block;
+ font-family: "Rounded";
+ text-decoration: none;
+ border-radius: inherit;
+ background-color: #D9D9D9;
+ padding: 10px;
+ color: #727272;
+}
+
+.context {
+ position: relative;
+ min-height: 14px;
+ display: flex;
+ align-items: center;
+ font-family: "Compact";
+ font-size: 12px;
+ margin-top: 5px;
+}
+
+.ai-context {
+ margin-left: 15px;
+}
+
+.client-context {
+ margin-right: 15px;
+}
+
+.context-avatar {
+ margin-right: 5px;
+ border-radius: 50%;
+ height: 30px;
+}
+
+/* ---------- shared between components ---------- */
+
+.button {
+ border: none;
+ outline: none;
+ text-decoration: none;
+}
+
+/*.button:hover {
+ cursor: pointer;
+}*/
+
+/* shake a div to signal error*/
+.shake {
+ animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
+ transform: translate3d(0, 0, 0);
+ backface-visibility: hidden;
+ perspective: 1000px;
+}
+
+@keyframes shake {
+ 10%, 90% {
+ transform: translate3d(-1px, 0, 0);
+ }
+
+ 20%, 80% {
+ transform: translate3d(2px, 0, 0);
+ }
+
+ 30%, 50%, 70% {
+ transform: translate3d(-4px, 0, 0);
+ }
+
+ 40%, 60% {
+ transform: translate3d(4px, 0, 0);
+ }
+}
+
+.audio {
+ animation-name: audio-anim;
+ animation-duration: 2s;
+ animation-iteration-count: infinite;
+}
+
+@keyframes audio-anim {
+ 0%,
+ 100% {
+ box-shadow: 0 0 0 0.4em rgba(195, 195, 195, 0.4), 0 0 0 0.25em rgba(195, 195, 195, 0.15);
+ }
+ 25% {
+ box-shadow: 0 0 0 0.15em rgba(195, 195, 195, 0.15), 0 0 0 0.4em rgba(195, 195, 195, 0.3);
+ }
+ 50% {
+ box-shadow: 0 0 0 0.55em rgba(195, 195, 195, 0.55), 0 0 0 0.15em rgba(195, 195, 195, 0.05);
+ }
+ 75% {
+ box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45);
+ }
+}
+
+.smart-form {
+ width: 225px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+}
+
+.text-field {
+ width: 100%;
+ padding: 16px;
+ background: none;
+ border: none;
+ margin: 4px;
+ font-size: 14px;
+ border-radius: 10px;
+ background-color: #d1d1d1;
+ outline: none;
+}
+
+.smart-form-logs {
+ font-family: "Compact";
+ font-size: 12px;
+ text-align: left;
+ margin-left: 10px;
+}
diff --git a/src/render/main.ts b/src/render/main.ts
deleted file mode 100644
index 5375889..0000000
--- a/src/render/main.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-
-// src/main.ts
-
-import App from "./App.vue";
-
-import mitt from "mitt";
-import { createApp } from "vue";
-
-import { initIpcRendererListeners } from "./ipc"
-
-
-// Handle ipcMain events.
-initIpcRendererListeners();
-
-// Handle events.
-const emitter = mitt();
-
-const app = createApp(App);
-
-app.provide("mitt", emitter);
-app.mount("#app");
diff --git a/src/render/preload.js b/src/render/preload.js
new file mode 100644
index 0000000..adef673
--- /dev/null
+++ b/src/render/preload.js
@@ -0,0 +1,53 @@
+const { contextBridge, ipcRenderer } = require("electron");
+
+/** The main <-> render interface */
+
+const validFromMainChannels = [
+ "playback",
+ "account",
+ "message",
+ "record",
+ "ws"
+];
+
+const validToMain = [
+ "messenger",
+ "message",
+ "logout",
+ "encode",
+ "auth",
+ "nav"
+]
+
+ipcRenderer.setMaxListeners(250);
+
+// Expose protected methods that allow the renderer process to use
+// the ipcRenderer without exposing the entire object
+// TODO: Implement argument filtering for added security
+contextBridge.exposeInMainWorld(
+ "mainApi", {
+ invoke: async (channel, ...args) => {
+ if (validToMain.includes(channel)) {
+ try {
+ const res = await ipcRenderer.invoke(channel, ...args);
+ return res;
+ } catch (e) {
+ console.log("IPCRenderer.invoke error");
+ }
+ }
+ },
+ send: (channel, ...args) => {
+ if (validToMain.includes(channel)) {
+ ipcRenderer.send(channel, ...args);
+ }
+ },
+ on: (channel, func) => {
+ if (validFromMainChannels.includes(channel)) {
+ ipcRenderer.on(channel, (event, ...args) => func(...args));
+ }
+ },
+ removeAllListeners: (channel) => {
+ ipcRenderer.removeAllListeners(channel);
+ }
+ }
+);
diff --git a/src/render/preload.ts b/src/render/preload.ts
deleted file mode 100644
index 054f672..0000000
--- a/src/render/preload.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-// All of the Node.js APIs are available in the preload process.
-// It has the same sandbox as a Chrome extension.
-import { ipcRenderer } from "electron";
-
-declare global {
- interface Window {
- ipcRenderer: typeof ipcRenderer;
- }
-}
-
-window.ipcRenderer = ipcRenderer;
-
-process.once("loaded", () => {
-
- window.addEventListener("message", event => {
-
- // do something with custom event
- const message = event.data;
-
- if (message.endpoint === "update-menu-bar") {
- ipcRenderer.send("update-menu-bar", message.content);
- }
-
- if (message.endpoint === "update-recorder") {
- ipcRenderer.send("update-recorder", message.content);
- }
-
- if (message.endpoint === "client-message") {
- ipcRenderer.send("client-message", message.content);
- }
-
- if (message.endpoint === "logout") {
- ipcRenderer.send("logout", message.content);
- }
-
- });
-
-});
diff --git a/src/render/shims-vue.d.ts b/src/render/shims-vue.d.ts
deleted file mode 100644
index 35a61a9..0000000
--- a/src/render/shims-vue.d.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-declare module "*.vue" {
- import { defineComponent } from "vue";
- const component: ReturnType;
- export default component;
-}
-
-declare module "anime-js" {
- namespace anime {
- import * as anime from "animejs/lib/anime.es.js";
- function anime(): void;
- export = anime;
- }
-}
diff --git a/src/render/vendor/vue.js b/src/render/vendor/vue.js
new file mode 100644
index 0000000..6e5ee00
--- /dev/null
+++ b/src/render/vendor/vue.js
@@ -0,0 +1,15872 @@
+var Vue = (function (exports) {
+ 'use strict';
+
+ /**
+ * Make a map and return a function for checking if a key
+ * is in that map.
+ * IMPORTANT: all calls of this function must be prefixed with
+ * \/\*#\_\_PURE\_\_\*\/
+ * So that rollup can tree-shake them if necessary.
+ */
+ function makeMap(str, expectsLowerCase) {
+ const map = Object.create(null);
+ const list = str.split(',');
+ for (let i = 0; i < list.length; i++) {
+ map[list[i]] = true;
+ }
+ return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val];
+ }
+
+ /**
+ * dev only flag -> name mapping
+ */
+ const PatchFlagNames = {
+ [1 /* TEXT */]: `TEXT`,
+ [2 /* CLASS */]: `CLASS`,
+ [4 /* STYLE */]: `STYLE`,
+ [8 /* PROPS */]: `PROPS`,
+ [16 /* FULL_PROPS */]: `FULL_PROPS`,
+ [32 /* HYDRATE_EVENTS */]: `HYDRATE_EVENTS`,
+ [64 /* STABLE_FRAGMENT */]: `STABLE_FRAGMENT`,
+ [128 /* KEYED_FRAGMENT */]: `KEYED_FRAGMENT`,
+ [256 /* UNKEYED_FRAGMENT */]: `UNKEYED_FRAGMENT`,
+ [512 /* NEED_PATCH */]: `NEED_PATCH`,
+ [1024 /* DYNAMIC_SLOTS */]: `DYNAMIC_SLOTS`,
+ [2048 /* DEV_ROOT_FRAGMENT */]: `DEV_ROOT_FRAGMENT`,
+ [-1 /* HOISTED */]: `HOISTED`,
+ [-2 /* BAIL */]: `BAIL`
+ };
+
+ /**
+ * Dev only
+ */
+ const slotFlagsText = {
+ [1 /* STABLE */]: 'STABLE',
+ [2 /* DYNAMIC */]: 'DYNAMIC',
+ [3 /* FORWARDED */]: 'FORWARDED'
+ };
+
+ const GLOBALS_WHITE_LISTED = 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
+ 'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
+ 'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt';
+ const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED);
+
+ const range = 2;
+ function generateCodeFrame(source, start = 0, end = source.length) {
+ // Split the content into individual lines but capture the newline sequence
+ // that separated each line. This is important because the actual sequence is
+ // needed to properly take into account the full line length for offset
+ // comparison
+ let lines = source.split(/(\r?\n)/);
+ // Separate the lines and newline sequences into separate arrays for easier referencing
+ const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);
+ lines = lines.filter((_, idx) => idx % 2 === 0);
+ let count = 0;
+ const res = [];
+ for (let i = 0; i < lines.length; i++) {
+ count +=
+ lines[i].length +
+ ((newlineSequences[i] && newlineSequences[i].length) || 0);
+ if (count >= start) {
+ for (let j = i - range; j <= i + range || end > count; j++) {
+ if (j < 0 || j >= lines.length)
+ continue;
+ const line = j + 1;
+ res.push(`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`);
+ const lineLength = lines[j].length;
+ const newLineSeqLength = (newlineSequences[j] && newlineSequences[j].length) || 0;
+ if (j === i) {
+ // push underline
+ const pad = start - (count - (lineLength + newLineSeqLength));
+ const length = Math.max(1, end > count ? lineLength - pad : end - start);
+ res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length));
+ }
+ else if (j > i) {
+ if (end > count) {
+ const length = Math.max(Math.min(end - count, lineLength), 1);
+ res.push(` | ` + '^'.repeat(length));
+ }
+ count += lineLength + newLineSeqLength;
+ }
+ }
+ break;
+ }
+ }
+ return res.join('\n');
+ }
+
+ /**
+ * On the client we only need to offer special cases for boolean attributes that
+ * have different names from their corresponding dom properties:
+ * - itemscope -> N/A
+ * - allowfullscreen -> allowFullscreen
+ * - formnovalidate -> formNoValidate
+ * - ismap -> isMap
+ * - nomodule -> noModule
+ * - novalidate -> noValidate
+ * - readonly -> readOnly
+ */
+ const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;
+ const isSpecialBooleanAttr = /*#__PURE__*/ makeMap(specialBooleanAttrs);
+ /**
+ * Boolean attributes should be included if the value is truthy or ''.
+ * e.g.