Compare commits

..
41 changed files with 2760 additions and 13036 deletions

29
.gitignore vendored
View file

@ -1,5 +1,30 @@
.DS_Store .DS_Store
/node_modules /node_modules
/out /dist
/crate/target/*
/.log
*.sublime-workspace crash.log
# local env files
.env.local
.env.*.local
.env
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#Electron-builder output
/dist_electron

63
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,63 @@
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}\"}"

2
.npmrc
View file

@ -1,2 +1,2 @@
@crimata:registry=https://gitlab.com/api/v4/projects/28849281/packages/npm/ @crimata:registry=https://gitlab.com/api/v4/projects/28849281/packages/npm/
//gitlab.com/api/v4/projects/28849281/packages/npm //gitlab.com/api/v4/projects/28849281/packages/npm/:_authToken=${NPM_TOKEN}

29
README.md Normal file
View file

@ -0,0 +1,29 @@
# crimata-electron
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn electron:serve
```
### Compiles and minifies for production
```
yarn electron:build
```
<!-- ### Run your unit tests
```
yarn test:unit
```
### Lints and fixes files
```
yarn lint
``` -->
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View file

@ -1,8 +0,0 @@
{
"folders":
[
{
"path": "."
}
]
}

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>
<!--<key>com.apple.security.cs.disable-library-validation</key>
<true/>-->

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

11756
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,54 +1,24 @@
{ {
"name": "crimata", "name": "Crimata",
"productName": "crimata-messenger",
"version": "1.0.0", "version": "1.0.0",
"description": "Cross-platform messenger app (Electron, Vue3)", "private": true,
"description": "Cross-platform messenger app (Electron, Vue3, and TS)",
"author": {
"name": "Andrew Gundersen"
},
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {
"start": "electron-forge start", "start": "electron ."
"test": "echo \"Error: no test specified\" && exit 1",
"package": "electron-forge package",
"make": "electron-forge make"
},
"author": "Andrew Gundersen",
"license": "ISC",
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.63",
"@electron-forge/maker-deb": "^6.0.0-beta.63",
"@electron-forge/maker-rpm": "^6.0.0-beta.63",
"@electron-forge/maker-squirrel": "^6.0.0-beta.63",
"@electron-forge/maker-zip": "^6.0.0-beta.63",
"electron": "^18.2.3"
}, },
"dependencies": { "dependencies": {
"@crimata/nodeaudio": "^0.0.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"base64-arraybuffer": "^1.0.1", "base64-arraybuffer": "^1.0.1",
"electron-store": "^8.0.0", "electron-store": "^8.0.0",
"ws": "^7.3.1", "ws": "^7.3.1"
"electron-squirrel-startup": "^1.0.0"
}, },
"config": { "devDependencies": {
"forge": { "electron": "^13.2.3"
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
} }
} }

View file

@ -1,43 +1,44 @@
const { ipcMain } = require("electron"); const { ipcMain } = require("electron");
const { post } = require("./api");
const store = require("./utils/store"); const store = require("./utils/store");
const { postLogin } = require("./api");
const { ipcEmit } = require("./utils/emitter");
const { launchSession, endSession } = require("./session"); const { launchSession, endSession } = require("./session");
const { backgroundMitt, ipcEmit } = require("./utils/emitter");
store.set("account", process.env.ACCOUNT); // store.set("account", "[email protected]");
store.set("account", "[email protected]");
async function auth(_e, creds) async function login(_e, email, password)
{ {
const { error, data } = await post("/auth", creds); const res = await postLogin(email, password);
if (res)
if (error)
{ {
return data; const account = res.crimataId;
store.set("account", account);
ipcEmit("set-account", account);
launchSession(account);
return true;
}
else
{
return false;
} }
store.set("account", data);
ipcEmit("account", data);
launchSession(data);
}; };
function logout(_e, reason) function logout()
{ {
store.delete("account"); store.delete("account");
ipcEmit("account", false, reason); ipcEmit("set-account", false);
endSession(); endSession();
}; };
function initAccount() function initAccount()
{ {
ipcMain.handle("auth", auth); ipcMain.handle("login", login);
ipcMain.on("logout", logout); ipcMain.on("logout", logout);
const account = store.get("account"); const account = store.get("account");
if (account) launchSession(account); if (account) launchSession(account);
} }
backgroundMitt.on("logout", (reason) => logout(null, reason));
exports.initAccount = initAccount; exports.initAccount = initAccount;

View file

@ -2,37 +2,18 @@ const axios = require('axios').default;
const config = require("./config"); const config = require("./config");
const baseUrl = config.BUSINESS_URL + config.BUSINESS_PREFIX;
async function post(route, body) async function postLogin(email, password)
{ {
let error; try {
let data; const res = await axios.post(baseUrl + "/account/login", {
email: email, password: password
try });
{ return res.data;
const res = await axios.post(config.API + route, body); } catch (e) {
data = res.data; return false;
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; exports.postLogin = postLogin;

View file

@ -26,9 +26,7 @@ backgroundMitt.on("data", (int16Arr) => {
backgroundMitt.on("write", (int16Arr) => { backgroundMitt.on("write", (int16Arr) => {
clearTimeout(setWriteId); clearTimeout(setWriteId);
setWriteId = setTimeout(() => { setWriteId = setTimeout(setPlaybackStatus.prototype.bind(null, false), 500);
setPlaybackStatus(false);
}, 500);
}); });
function setStreams() function setStreams()
@ -132,3 +130,8 @@ exports.initAudio = initAudio;
exports.terminateAudio = terminateAudio; exports.terminateAudio = terminateAudio;
exports.playback = playback; exports.playback = playback;
exports.streamState = streamState; exports.streamState = streamState;
// setWriteId = setTimeout(() => {
// setPlaybackStatus(false);
// }, 500);

View file

@ -1,11 +1,16 @@
const { app } = require("electron"); const { app } = require("electron");
const DOMAIN = "crimata.com"; const env = process.env;
/* If PROD env variable exists */ const PLATFORM_PORT = env.PLATFORM_PORT || 8760;
const prod = process.env.PROD; 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';
module.exports = { module.exports = {
PLATFORM: prod ? `https://app.${DOMAIN}` : `http://localhost:5000`, PLATFORM_URL: `${PLATFORM_IP}:${PLATFORM_PORT}`,
API: prod ? `https://${DOMAIN}/api` : `http://localhost:5001` BUSINESS_URL: `${BUSINESS_IP}:${BUSINESS_PORT}`,
BUSINESS_PREFIX: '/api',
configPath: app.getPath('userData'),
} }

View file

@ -14,7 +14,7 @@ function connectToPlatform(account)
{ {
if (pingId) clearInterval(pingId); if (pingId) clearInterval(pingId);
connection = new WebSocket(`${config.PLATFORM}/${account}`) connection = new WebSocket(`${config.PLATFORM_URL}/${account}`)
.on("open", () => pingId = setInterval(() => connection.ping(null, true), 1000)) .on("open", () => pingId = setInterval(() => connection.ping(null, true), 1000))
@ -24,22 +24,11 @@ function connectToPlatform(account)
.on("message", (payload) => backgroundMitt.emit("message", JSON.parse(payload))) .on("message", (payload) => backgroundMitt.emit("message", JSON.parse(payload)))
.on("close", (_code, reason) => { .on("close", (code, reason) => {
setConnectionStatus(1); setConnectionStatus(1);
if (reason !== "logout") {
if (reason) reconnectId = setTimeout(() => connectToPlatform(account), 500);
{
if (reason == "unauthorized")
{
backgroundMitt.emit("logout", reason);
}
return;
} }
reconnectId = setTimeout(() => connectToPlatform(account), 500);
}); });
} }
@ -53,13 +42,13 @@ function sendMessage(message)
function disconnectFromPlatform() function disconnectFromPlatform()
{ {
clearTimeout(reconnectId); clearTimeout(reconnectId);
if (connection.open) connection.close(1000, "logout"); connection.close(1000, "logout");
} }
function setConnectionStatus(status) function setConnectionStatus(status)
{ {
updateTray("disconnect", status); updateTray("disconnect", status);
ipcEmit("ws", status); ipcEmit("ws-status", status);
} }
ipcMain.on("message", (_e, message) => sendMessage(message)); ipcMain.on("message", (_e, message) => sendMessage(message));

View file

@ -3,9 +3,12 @@ const { app, protocol, ipcMain, nativeTheme } = require("electron");
const store = require("./utils/store"); const store = require("./utils/store");
const { initTray } = require("./tray"); const { initTray } = require("./tray");
const { postLogin } = require("./api");
const { createWin } = require("./window"); const { createWin } = require("./window");
const { initAccount } = require("./account"); const { initAccount } = require("./account");
// const { terminateAudio } = require("./audio"); const { terminateAudio } = require("./audio");
app.commandLine.appendSwitch("enable-transparent-visuals");
// Assert a light theme // Assert a light theme
nativeTheme.themeSource = "light"; nativeTheme.themeSource = "light";

52
src/preview.html Normal file
View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body {
margin: 0;
padding: 0;
font-family: "SF Pro Text";
font-size: 14px;
}
#titlebar {
position: fixed;
width: 100vw;
height: 54px;
opacity: 0;
-webkit-app-region: drag;
border: none;
outline: none;
z-index: 1;
}
.building {
width: 250px;
height: 150px;
}
.email {
padding: 15px;
}
.email-subject {
font-size: 16px;
margin-top: 10px;
}
</style>
</head>
<body>
<button id="titlebar"></button>
<div id="content"></div>
<script>
window.mainApi.on("preview-init", (content) => {
document.getElementById("content").innerHTML = content;
});
</script>
</body>
</html>

View file

@ -20,25 +20,21 @@ const App =
}, },
template: ` template: `
<div id="app" v-if="account !== null"> <main id="app" v-if="account !== null">
<Header /> <Header />
<Messenger v-if="account"/> <Messenger v-if="account"/>
<Login v-else /> <Login v-else />
</div> </main>
<Splash v-else />` <Splash v-else />`
} }
window.mainApi.on("account", (value, reason) => { window.mainApi.on("set-account", (account_) => {
if (reason) alert(reason); account.value = account_;
account.value = value;
}); });
export default App; export default App;
export { account }; export { account };
// Is called when window is about to close or reload
window.onbeforeunload = () => console.log("beforeunload");

View file

@ -9,27 +9,15 @@ const Bubble =
} }
Vue.onMounted(() => { Vue.onMounted(() => {
document.dispatchEvent(new CustomEvent("scroll"));
const el = document.getElementById("messenger");
setTimeout(() => {
el.dispatchEvent(new CustomEvent('adjust-scroll'));
}, 100);
}); });
if (!props.text) props.text = "...";
return { getUrl }; return { getUrl };
}, },
template: ` template: `
<div :class="'bubble ' + modifier + '-bubble ' + modifier +'-'+ child"> <div :class="'bubble ' + modifier + '-bubble ' + modifier +'-'+ child">
<small class="bubble-notify" v-if="modifier=='ai'"></small>
<p v-if="category=='text'">{{ text }}</p> <p v-if="category=='text'">{{ text }}</p>
<p v-else-if="category=='audio'">{{ text }}</p> <p v-else-if="category=='audio'">{{ text }}</p>
@ -38,7 +26,7 @@ const Bubble =
<img v-else-if="category == 'image'" :src="getUrl()" /> <img v-else-if="category == 'image'" :src="getUrl()" />
<a v-else :download="text" :href="getUrl()">{{ text }}</a> <a v-else :href="getUrl()">{{ text }}</a>
</div>` </div>`
}; };

View file

@ -1,5 +1,5 @@
const saveLocation = "input_item_position"; const saveLocation = "input_item_position";
const defaultPosition = { x: 15, y: 400 }; const defaultPosition = { x: 15, y: window.innerHeight - 300 };
//---Dragabble Helper Funcs-------------------------------------- //---Dragabble Helper Funcs--------------------------------------
@ -33,10 +33,9 @@ function calcPosition (elementPosition, elementLength, percent, short, win) {
return elementPosition return elementPosition
} }
function draggify(elementId, parentId, margin) { function draggify(elementId, margin) {
let element; let element;
let parent;
/* only compatible with elements having equal width and height */ /* only compatible with elements having equal width and height */
let elementLength; let elementLength;
@ -53,20 +52,23 @@ function draggify(elementId, parentId, margin) {
let xShort; let xShort;
let yShort; let yShort;
// Keep track of window size;
let winW = window.innerWidth;
let winH = window.innerHeight;
//---Reposition Anime----------------------------------------------- //---Reposition Anime-----------------------------------------------
// Move element to target smoothly. // Move element to target smoothly.
const repositionAnime = (xChange, yChange) => { const repositionAnime = (xChange, yChange) => {
const xStep = xChange / 6000;
const yStep = yChange / 6000;
const xStep = xChange / 1000; for (let i = 1; i <= 6000; i++) {
const yStep = yChange / 1000;
for (let i = 1; i <= 1000; i++) {
setTimeout(() => { setTimeout(() => {
elementX.value += xStep; elementX.value += xStep;
elementY.value += yStep; elementY.value += yStep;
}, 30) // 60fps }, 16) // 60fps
} }
} }
@ -77,8 +79,9 @@ function draggify(elementId, parentId, margin) {
const onMouseMove = (e) => { const onMouseMove = (e) => {
e.preventDefault(); e.preventDefault();
elementX.value = element.offsetLeft + e.movementX; const inputItemRect = element.getBoundingClientRect();
elementY.value = element.offsetTop + e.movementY; elementX.value = inputItemRect.left + e.movementX;
elementY.value = inputItemRect.top + e.movementY;
} }
// Add an event listener for dragging. // Add an event listener for dragging.
@ -99,9 +102,6 @@ function draggify(elementId, parentId, margin) {
let x = 0; // vector change let x = 0; // vector change
let y = 0; let y = 0;
const winW = parent.clientWidth;
const winH = parent.clientHeight;
if (elementX.value < 0) { if (elementX.value < 0) {
x = (elementX.value - margin)*-1 x = (elementX.value - margin)*-1
} }
@ -138,8 +138,12 @@ function draggify(elementId, parentId, margin) {
// Update position of targetEl on windowResize. // Update position of targetEl on windowResize.
const onWindowResize = (_e) => { const onWindowResize = (_e) => {
elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, parent.clientWidth); // Update window dimensions.
elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, parent.clientHeight); winW = window.innerWidth;
winH = window.innerHeight;
elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, winW);
elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, winH);
savePosition(); savePosition();
} }
@ -156,28 +160,27 @@ function draggify(elementId, parentId, margin) {
Vue.onMounted(() => { Vue.onMounted(() => {
element = document.getElementById(elementId); element = document.getElementById(elementId);
parent = document.getElementById(parentId);
elementLength = element.offsetWidth; elementLength = element.offsetWidth;
// Initialize the positional references. // Initialize the positional references.
percentX = elementX.value / parent.clientWidth; percentX = elementX.value / window.innerWidth;
percentY = elementY.value / parent.clientHeight; percentY = elementY.value / window.innerHeight;
xShort = calcSideProximity(elementX.value, elementLength, parent.clientWidth); xShort = calcSideProximity(elementX.value, elementLength, window.innerWidth);
yShort = calcSideProximity(elementX.value, elementLength, parent.clientHeight); yShort = calcSideProximity(elementX.value, elementLength, window.innerHeight);
// Then, we can listen for window resize (and mousedown). // Then, we can listen for window resize (and mousedown).
element.addEventListener("mousedown", onMouseDown); element.addEventListener("mousedown", onMouseDown);
parent.addEventListener('resize', onWindowResize) window.addEventListener('resize', onWindowResize)
}); });
// remove event listeners on component dismount. // remove event listeners on component dismount.
Vue.onUnmounted(() => { Vue.onUnmounted(() => {
element.removeEventListener('mousedown', onMouseDown); element.removeEventListener('mousedown', onMouseDown);
parent.removeEventListener('resize', onWindowResize) window.removeEventListener('resize', onWindowResize)
parent.removeEventListener('mouseup', onMouseUp) window.removeEventListener('mouseup', onMouseUp)
parent.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mousemove', onMouseMove);
}); });
// Try loading initPosition, otherwise set default values // Try loading initPosition, otherwise set default values

View file

@ -1,11 +1,6 @@
const onDrop = async (e) => { const onDrop = async (e) => {
const file = e.dataTransfer.items[0].getAsFile(); 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 buffer = await file.arrayBuffer();
const b64String = await window.mainApi.invoke("encode", buffer); const b64String = await window.mainApi.invoke("encode", buffer);

View file

@ -1,20 +1,22 @@
let el; let el;
const scroll = () => { const initScroll = (elementId) => {
if (!el) { el = document.getElementById(elementId);
el = document.getElementById("messenger"); el.addEventListener('adjust-scroll', adjustScroll);
window.addEventListener('resize', scroll); window.addEventListener('resize', adjustScroll);
} }
const isBottom = () => {
return el.scrollHeight - el.clientHeight <= el.scrollTop + 1;
}
const adjustScroll = () => {
if (isBottom()) {
el.scrollTo({ el.scrollTo({
top: el.scrollHeight - el.clientHeight, top: el.scrollHeight - el.clientHeight,
behavior: 'smooth' behavior: 'smooth'
}); });
}
} }
export default scroll; export default initScroll;
// const isBottom = () => {
// if (el) {
// return el.scrollHeight - el.clientHeight <= el.scrollTop + 1;
// }
// }

View file

@ -17,23 +17,21 @@ const metaKeys = [
"Escape" "Escape"
]; ];
function useText(elementId, parentId, left) function useText(elementId, left, typing)
{ {
let el; let el;
let parent;
const show = Vue.ref(false); const show = Vue.ref(false);
const leftSide = Vue.ref(false); const leftSide = Vue.ref(false);
const calcSide = () => { const calcSide = () => {
parent.clientWidth - left.value < inputLength ? leftSide.value = true : leftSide.value = false; window.innerWidth - left.value < inputLength ? leftSide.value = true : leftSide.value = false;
} }
/* handle user typing */ /* handle user typing */
Vue.onMounted(() => { Vue.onMounted(() => {
el = document.getElementById(elementId); el = document.getElementById(elementId);
parent = document.getElementById(parentId);
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
@ -48,23 +46,15 @@ function useText(elementId, parentId, left)
if (e.key === "Enter" && el.value) if (e.key === "Enter" && el.value)
{ {
if (el.value.length >= 250) {
alert("250 character limit")
return;
}
window.mainApi.send("message", { window.mainApi.send("message", {
category: "text", category: "text",
text: el.value, text: el.value,
blob: null blob: null
}); });
show.value = false; show.value = false;
} }
}); });
calcSide();
}); });
Vue.watch(left, calcSide); Vue.watch(left, calcSide);
@ -78,6 +68,8 @@ function useText(elementId, parentId, left)
} }
}); });
calcSide();
return { leftSide, show }; return { leftSide, show };
} }

View file

@ -1,23 +0,0 @@
export const isEmail = (value) => {
console.log(value);
if (!/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(value)) {
return "Not a valid email."
}
}
export const isPassword = (value) => {
if (!/^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,16}$/.test(value)) {
return "Password must have symbol, number, uppercase and be 6-16 length.";
}
}
/**
* Name is optional and therefore doesn't return error if no value.
*/
export const isName = (value) => {
if (value) {
if (!/^([a-zA-Z ]){5,30}$/.test(value)) {
return "Name must be at least 5 long.";
}
}
}

View file

@ -1,86 +0,0 @@
// Basic debounce implentation
const debounce = (fn, delay) => {
let timeoutId;
return (...args) => {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn(...args);
}, delay);
}
}
/**
* Dynamic form component with auto submission and validation.
*
* props
* .fields => List of field objects
* .channel => e.g. "/login"
* .modifier => Form description
*/
const SmartForm =
{
props: ["fields", "channel", "modifier"],
setup(props)
{
// Error log below form
const log = Vue.ref(props.modifier);
/**
* Valid and submit form data to channel.
*/
const submit = async () => {
// Validate each field
for (let field of props.fields) {
const error = field.valid(field.value);
if (error) return error;
}
// Reduce fields into the form
const form = props.fields.reduce((v, n) => {
v[n.name] = n.value;
return v;
}, {});
console.log("Submitting =>", form);
return await window.mainApi.invoke(props.channel, form);
}
/**
* Wrapper for submit that is debounced and updates UI elements
*/
const preSub = debounce(async () => {
const error = await submit();
log.value = error ? error : props.modifier
}, 2000);
return { preSub, log };
},
template: `
<form id="smart-form" class="smart-form" v-on:input="preSub">
<div v-for="field in fields" style="display:flex">
<input type="text" class="text-field"
v-model="field.value"
:name="field.name"
:placeholder="field.placeholder"
/>
</div>
<div class="smart-form-logs">{{ log }}</div>
</form>`
}
export default SmartForm;

View file

@ -1,7 +1,7 @@
const Header = const Header =
{ {
setup() { setup() {
const onNavBar = (command) => window.mainApi.send("nav", command); const onNavBar = (command) => window.mainApi.send('nav-bar', command);
return { onNavBar }; return { onNavBar };
}, },

View file

@ -11,9 +11,9 @@ const Input =
const recording = Vue.ref(false); const recording = Vue.ref(false);
const { elementX, elementY } = draggify("input", "app", 15); const { elementX, elementY } = draggify("input", 15);
const { leftSide, show } = useText("input-text", "app", elementX); const { leftSide, show } = useText("input-text", elementX);
window.mainApi.on("record", (status) => recording.value = status); window.mainApi.on("record", (status) => recording.value = status);

View file

@ -1,44 +1,58 @@
import SmartForm from "./form.js";
import { isEmail, isPassword, isName } from "./control/valid.js";
const Login = const Login =
{ {
components: {
SmartForm
},
setup() setup()
{ {
return { isEmail, isPassword, isName }; const usr = Vue.ref("");
const pwd = Vue.ref("");
const submitForm = async () => {
const success = await window.mainApi.invoke("login", usr.value, pwd.value);
if (!success) shake("login-form");
}
return { usr, pwd, submitForm };
}, },
template: ` template: `
<div id="login"> <form id="login" @submit.prevent="submitForm">
<SmartForm <!-- username and password forms -->
:fields="[{ <div id="login-form">
name: 'email', <!-- username -->
value: null, <div class="login-input-box">
placeholder: 'Email', <input class="login-input" type="text" v-model="usr" />
valid: isEmail <div class="login-input-modifier">Email</div>
}, </div>
{
name: 'password',
value: null,
placeholder: 'Password',
valid: isPassword
},
{
name: 'name',
value: null,
placeholder: 'Name',
valid: isName
}]"
:channel="'auth'"
:modifier="'Login or enter name to register.'"
/>
</div>` <!-- password -->
<div class="login-input-box">
<input class="login-input" type="password" v-model="pwd" />
<div class="login-input-modifier">Password</div>
</div>
</div>
<!-- submit button; position: fixed -->
<button
class="login-submit-button button"
type="submit"
>
Login
</button>
</form> `
}
/** Shake a div to indicate incorrect credentials */
const shake = (elementId) =>
{
const el = document.getElementById(elementId);
if (el)
el.classList.remove("shake");
setTimeout(() => {
if (el) el.classList.add("shake");
}, 250);
} }
export default Login; export default Login;

View file

@ -8,7 +8,7 @@ const Message =
Context Context
}, },
props: ["modifier", "content", "context", "avatar", "id", "seen"], props: ["modifier", "content", "context", "avatar", "id"],
setup(props) setup(props)
{ {
@ -20,7 +20,6 @@ const Message =
<Bubble <Bubble
v-for="(c, index) in content" v-for="(c, index) in content"
:id="'bubble-' + id + '-' + index"
:category="c.category" :category="c.category"
:text="c.text" :text="c.text"
:html="c.html" :html="c.html"

View file

@ -1,5 +1,5 @@
import onDrop from "./control/drop.js"; import onDrop from "./control/drop.js";
import scroll from "./control/scroll.js"; import adjustScroll from "./control/scroll.js";
import WS from "./ws.js"; import WS from "./ws.js";
import Input from "./input.js"; import Input from "./input.js";
@ -20,45 +20,26 @@ const Messenger = {
const person = Vue.ref(""); const person = Vue.ref("");
const messages = Vue.ref([]); const messages = Vue.ref([]);
let current = Vue.ref();
Vue.onMounted(() => { Vue.onMounted(() => {
window.mainApi.on("message", (update) => { window.mainApi.on("update", (update) => {
if (update.person) if (update.name)
{ {
person.value = update.person; person.value = update;
messages.value = update.messages;
current.value = messages.value.at(-1);
} }
else if (update.name) else if (message.content)
{
person.value = update.name;
}
else if (update.modifier)
{ {
messages.value.push(update); 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 else
{ {
current.value.content.at(-1).text = update.text; messages.value.at(-1).content.push(update);
} }
setTimeout(scroll, 10);
}); });
window.mainApi.send("messenger"); window.mainApi.send("message", {route: "window", open: true});
}); });
return { person, messages, onDrop }; return { person, messages, onDrop };
@ -74,13 +55,18 @@ const Messenger = {
@dragover.prevent @dragover.prevent
@dragenter.prevent @dragenter.prevent
> >
<Message <Message
v-for="message in messages" v-for="message in messages"
v-bind="message" :modifier="message.modifier"
:content="message.content"
:context="message.context"
:avatar="message.avatar"
:id="message.id"
/> />
</div> `
</div>`
} }
export default Messenger; export default Messenger;
// Is called when window is about to close or reload
window.onbeforeunload = () => window.mainApi.send("message", {route: "window", open: true});

View file

@ -20,11 +20,11 @@ const Settings =
await window.mainApi.send("logout"); await window.mainApi.send("logout");
} }
return { showSettings, active, logout, window }; return { showSettings, active, logout };
}, },
template: ` template: `
<div v-if="active" id="settings"> <div v-if="active" id="settings" >
<button class="settings-logout-button button" @click="logout"> <button class="settings-logout-button button" @click="logout">
Logout Logout
@ -33,7 +33,7 @@ const Settings =
</div> </div>
<button v-else class="settings-icon" @click="showSettings"> <button v-else class="settings-icon" @click="showSettings">
<img :src="window.path + 'assets/settingsIcon.svg'"> <img src="./assets/settingsIcon.svg">
</button>` </button>`
} }

View file

@ -7,19 +7,19 @@ const WS =
Vue.onMounted(() => { Vue.onMounted(() => {
window.mainApi.on("ws", (val) => { window.mainApi.on("ws-status", (val) => {
status.value = val; status.value = val;
}); });
}); });
return { status, window } return { status }
}, },
template: ` template: `
<div id="ws"> <div id="ws">
<img id="ws-logo" :src="window.path + 'assets/connectLogo.svg'" <img id="ws-logo" src="./assets/connectLogo.svg"
:class="{ move: status }" :class="{ move: status }"
> >

View file

@ -1,5 +1,2 @@
import App from "./components/app.js"; import App from "./components/app.js";
window.path = "./";
Vue.createApp(App).mount("#app"); Vue.createApp(App).mount("#app");

View file

@ -1,48 +1,30 @@
@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 { html, body {
margin: 0; margin: 0;
padding: 0; padding: 0;
/*background-color: rgba(235, 235, 235, 0.75);*/ background-color: rgba(235, 235, 235, 0.75);
} }
/* The first word of the class is the component that it modifys. */ /* The first word of the class is the component that it modifys. */
#splash { #splash {
width: 100%; width: 100vw;
height: 100%; height: 100vh;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
#app { #app {
position: relative; /* must explicitly be declared */ font-family: "SF Pro Text";
font-family: "Default";
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
height: 100vh; /* 100% for website */ height: 100vh;
width: 100vw; /* 100% for website */ width: 100vw;
/* Does not work on Linux */
border-radius: 15px; border-radius: 15px;
} }
#header-titlebar { #header-titlebar {
position: absolute; position: fixed;
width: 100%; width: 100vw;
height: 54px; height: 54px;
opacity: 0.75; opacity: 0.75;
background-color: #EBEBEB; background-color: #EBEBEB;
@ -50,8 +32,6 @@ html, body {
border: none; border: none;
outline: none; outline: none;
z-index: 1; z-index: 1;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
} }
.contacts { .contacts {
@ -81,26 +61,22 @@ html, body {
} }
.contacts .email { .contacts .email {
font-family: "Compact"; font-family: SF Compact Display;
font-size: 12px; font-size: 12px;
color: #898989; color: #898989;
overflow: w;
word-wrap: break-word;
} }
#header-menu { #header-menu {
position: absolute; position: fixed;
margin-left: 20px; margin-left: 20px;
margin-top: 20px; margin-top: 20px;
z-index: 2; z-index: 2;
/* Needed for Linux even with higher Z index */
-webkit-app-region: no-drag;
} }
.header-menu-button { .header-menu-button {
min-width: 14px; min-width: 14px;
min-height: 14px; min-height: 14px;
border-radius: 50%; border-radius: 7px;
} }
.header-exit-button { .header-exit-button {
@ -129,9 +105,87 @@ html, body {
justify-content: center; justify-content: center;
} }
#login-form {
font-family: "SF Compact Display";
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-bottom: 20px;
}
.login-input-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 10px;
}
.login-input {
background-color: #B9B9B9;
border: none;
outline: none;
padding: 16px;
text-decoration: none;
margin: 4px 2px;
cursor: pointer;
border-radius: 10px;
}
.login-input-modifier {
min-width: 181px;
font-size: 12px;
font-weight: bold;
text-align: left;
margin-left: 15px;
}
.login-submit-button {
font-family: "SF Compact Display";
position: fixed;
margin-top: 260px;
background-color: #58C4FD;
color: white;
padding: 10px 30px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
}
.login-submit-button:active {
background-color: #4296C3;
}
/* generic shaking functionality */
.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);
}
}
#messenger { #messenger {
width: 100%; width: 100vw;
height: 100%; height: 100vh;
overflow: auto; overflow: auto;
} }
@ -141,8 +195,8 @@ html, body {
} }
#ws { #ws {
position: absolute; position: fixed;
width: 100%; width: 100vw;
height: 54px; height: 54px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -230,6 +284,7 @@ html, body {
border-radius: 50%; border-radius: 50%;
/* Set opacity here to not affect child. */ /* Set opacity here to not affect child. */
background-color: rgba(235, 235, 235, 0.75); background-color: rgba(235, 235, 235, 0.75);
cursor: pointer;
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15); box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
} }
@ -241,7 +296,6 @@ html, body {
padding: 10px; padding: 10px;
outline: none; outline: none;
border: none; border: none;
font-size: 14px;
pointer-events: none; pointer-events: none;
background-color: white; background-color: white;
z-index: -1; z-index: -1;
@ -261,9 +315,9 @@ html, body {
} }
#settings { #settings {
position: absolute; position: fixed;
width: 100%; width: 100vw;
height: 100%; height: 100vh;
background-color: rgba(235, 235, 235, 0.75); background-color: rgba(235, 235, 235, 0.75);
z-index: 4; z-index: 4;
display: flex; display: flex;
@ -272,7 +326,6 @@ html, body {
align-items: center; align-items: center;
animation-name: settings-appear; animation-name: settings-appear;
animation-duration: 0.5s; animation-duration: 0.5s;
border-radius: inherit;
} }
@keyframes settings-appear { @keyframes settings-appear {
@ -285,7 +338,7 @@ html, body {
} }
.settings-icon { .settings-icon {
position: absolute; position: fixed;
right: 0; right: 0;
border: none; border: none;
outline: none; outline: none;
@ -296,8 +349,10 @@ html, body {
margin-top: 19px; margin-top: 19px;
z-index: 2; z-index: 2;
background-color: Transparent; background-color: Transparent;
/* Needed for Linux */ }
-webkit-app-region: no-drag;
.settings-icon:hover {
cursor: pointer;
} }
.settings-account { .settings-account {
@ -307,11 +362,16 @@ html, body {
} }
.settings-logout-button { .settings-logout-button {
font-family: "Compact"; font-family: "SF Compact Display";
background-color: #B7B7B7; background-color: #B7B7B7;
padding: 10px 20px; padding: 10px 30px;
border-radius: 20px; border-radius: 20px;
font-size: 14px; font-size: 14px;
font-weight: bold;
}
.settings-logout-button:hover {
cursor: pointer;
} }
.settings-version { .settings-version {
@ -325,7 +385,7 @@ html, body {
} }
.message { .message {
width: inherit; width: 100vw;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: 9px; padding-top: 9px;
@ -350,17 +410,6 @@ html, body {
margin-bottom: 6px; 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 { .client-message {
align-items: flex-end; align-items: flex-end;
} }
@ -369,40 +418,13 @@ html, body {
align-items: flex-start; align-items: flex-start;
} }
.admin-message {
align-items: center;
}
.bubble { .bubble {
position: relative; position: relative;
max-width: 66%; max-width: 66vw;
font-family: "SF Pro Text";
font-size: 14px; font-size: 14px;
border-radius: 18px; border-radius: 18px;
margin-bottom: 4px; 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 { .ai-bubble {
@ -454,15 +476,15 @@ html, body {
.bubble > img { .bubble > img {
border-radius: inherit; border-radius: inherit;
display: block; display: block;
max-width: 100%; max-width: inherit;
} }
.bubble a { .bubble a {
display: inline-block; display:inline-block;
font-family: "Rounded";
text-decoration: none; text-decoration: none;
border-radius: inherit; border-radius: inherit;
background-color: #D9D9D9; background-color: #D9D9D9;
font-weight: bold;
padding: 10px; padding: 10px;
color: #727272; color: #727272;
} }
@ -472,8 +494,9 @@ html, body {
min-height: 14px; min-height: 14px;
display: flex; display: flex;
align-items: center; align-items: center;
font-family: "Compact"; font-family: "SF Compact Display";
font-size: 12px; font-size: 12px;
font-weight: bold;
margin-top: 5px; margin-top: 5px;
} }
@ -503,32 +526,6 @@ html, body {
cursor: pointer; 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 { .audio {
animation-name: audio-anim; animation-name: audio-anim;
animation-duration: 2s; animation-duration: 2s;
@ -550,29 +547,3 @@ html, body {
box-shadow: 0 0 0 0.25em rgba(195, 195, 195, 0.25), 0 0 0 0.55em rgba(195, 195, 195, 0.45); 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;
}

View file

@ -3,20 +3,21 @@ const { contextBridge, ipcRenderer } = require("electron");
/** The main <-> render interface */ /** The main <-> render interface */
const validFromMainChannels = [ const validFromMainChannels = [
"playback", "set-account",
"account", "update",
"message", "ws-status",
"record", "record",
"ws" "playback"
]; ];
const validToMain = [ const validToMain = [
"messenger", "messenger",
"message", "message",
"login",
"logout", "logout",
"encode", "encode",
"auth", "decode",
"nav" "nav-bar"
] ]
ipcRenderer.setMaxListeners(250); ipcRenderer.setMaxListeners(250);

View file

@ -3,90 +3,40 @@ const { Notification, ipcMain } = require("electron");
const store = require("./utils/store"); const store = require("./utils/store");
const { backgroundMitt, ipcEmit } = require("./utils/emitter"); const { backgroundMitt, ipcEmit } = require("./utils/emitter");
const { connectToPlatform, disconnectFromPlatform } = require("./io"); const { connectToPlatform, disconnectFromPlatform } = require("./io");
// const { initAudio, terminateAudio, playback, streamState } = require("./audio"); const { initAudio, terminateAudio, playback, streamState } = require("./audio");
const util = require('util');
let ui;
let current;
const messageQueue = []; const messageQueue = [];
let processContentId; let processContentId;
/* Possible messages:
* - New UI (set ui)
* - New message (add message to ui.messages)
* - New content (add content to existing message)
* - Update person
* - Notification
*/
backgroundMitt.on("message", (message) => messageQueue.push(message)); backgroundMitt.on("message", (message) => messageQueue.push(message));
function handleMessageQueue() function handleUpdateQueue()
{ {
// if (!streamState.pb && !streamState.rec) if (!streamState.pb && !streamState.rec)
// { {
const update = messageQueue.shift(); const update = messageQueue.shift();
console.log(update);
if (update) if (update)
{ {
if (update.person) if (update.title)
{ {
ui = update; new Notification(update).show();
current = update.messages.at(-1);
} }
else if (update.name) else if (update.audio)
{ {
ui.person = update; playback(update);
} }
else if (update.modifier) else { ipcEmit("update", update); }
{
ui.messages.push(update);
current = update;
}
else if (update.category)
{
current.content.push(update);
}
else if (update.context)
{
current.context = update.context;
}
else if (update.text)
{
current.content.at(-1).text = update.text;
}
else
{
const content = current.content.at(-1);
new Notification({
title: current.context,
body: content.text
}).show();
/* Ideally, we want a more native solution than this */
// if (current.modifier == "ai" && content.category == "audio")
// {
// playback(content.blob, current.id);
// }
return;
}
ipcEmit("message", update);
} }
// } }
} }
function launchSession(account) function launchSession(account)
{ {
connectToPlatform(account); connectToPlatform(account);
// initAudio(); // initAudio();
processContentId = setInterval(handleMessageQueue, 100); processContentId = setInterval(handleUpdateQueue, 100);
} }
function endSession() function endSession()
@ -94,14 +44,7 @@ function endSession()
clearInterval(processContentId); clearInterval(processContentId);
// terminateAudio(); // terminateAudio();
disconnectFromPlatform(); disconnectFromPlatform();
ui = null;
} }
ipcMain.on("messenger", () => {
if (ui) {
ipcEmit("message", ui);
}
});
exports.launchSession = launchSession; exports.launchSession = launchSession;
exports.endSession = endSession; exports.endSession = endSession;

View file

@ -5,7 +5,7 @@ const { Tray } = require("electron");
const { backgroundMitt } = require("./utils/emitter"); const { backgroundMitt } = require("./utils/emitter");
let tray; let tray;
const TRAY_PATH = path.join(__dirname, "assets/tray"); const TRAY_PATH = "./src/assets/tray/";
/* Current state of recording, playback, and ws connection */ /* Current state of recording, playback, and ws connection */
const state = { const state = {
@ -19,24 +19,25 @@ const state = {
* @param {"recording" | "playback" | "disconnect"} attribute * @param {"recording" | "playback" | "disconnect"} attribute
* @param {boolean} newState * @param {boolean} newState
*/ */
function updateTray(attribute, newState) function updateTray(attribute, newState) {
{
state[attribute] = newState; state[attribute] = newState;
// convert boolean to number // convert boolean to number
const bi = (b) => b ? 1 : 0; const bi = (b) => b ? 1 : 0;
// Make sure we have the icon we're looking for. // Make sure we have the icon we're looking for.
tray.setImage(path.join( tray.setImage(
TRAY_PATH, TRAY_PATH +
`${bi(state.recording)}${bi(state.playback)}${bi(state.disconnect)}` + `${bi(state.recording)}${bi(state.playback)}${bi(state.disconnect)}` +
".png" ".png"
)); );
} }
function initTray() function initTray()
{ {
tray = new Tray(path.join(TRAY_PATH, "000.png")); tray = new Tray(TRAY_PATH + "000" + ".png");
} }
exports.initTray = initTray; exports.initTray = initTray;

View file

@ -21,7 +21,7 @@ function updateWindowPosition()
} }
function onNav(_e, cmd) function onNavBar(_e, cmd)
{ {
cmd === "close" ? win.close() : win.minimize(); cmd === "close" ? win.close() : win.minimize();
} }
@ -42,8 +42,7 @@ function createWin()
frame: false, frame: false,
minWidth: 350, minWidth: 350,
minHeight: 500, minHeight: 500,
backgroundColor: "#EBEBEB", webPreferences: { preload: path.join(__dirname, "/render/preload.js") }
webPreferences: { preload: path.join(__dirname, "render/preload.js") }
}) })
// TODO: find a simpler way to notify platform of user events // TODO: find a simpler way to notify platform of user events
@ -71,10 +70,10 @@ function createWin()
} }
}); });
win.webContents.send("account", store.get("account")); win.webContents.send("set-account", store.get("account"));
}); });
} }
ipcMain.on("nav", onNav); ipcMain.on("nav-bar", onNavBar);
exports.createWin = createWin; exports.createWin = createWin;

785
yarn.lock Normal file
View file

@ -0,0 +1,785 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@crimata/nodeaudio@^0.0.0":
"integrity" "sha1-KTpDGuL8j3x72BLNb4Iyg8zgARo="
"resolved" "https://gitlab.com/api/v4/projects/28849281/packages/npm/@crimata/nodeaudio/-/@crimata/nodeaudio-0.0.0.tgz"
"version" "0.0.0"
dependencies:
"bindings" "~1.2.1"
"sleep" "^6.3.0"
"@electron/get@^1.0.1":
"integrity" "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ=="
"resolved" "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz"
"version" "1.13.0"
dependencies:
"debug" "^4.1.1"
"env-paths" "^2.2.0"
"fs-extra" "^8.1.0"
"got" "^9.6.0"
"progress" "^2.0.3"
"semver" "^6.2.0"
"sumchecker" "^3.0.1"
optionalDependencies:
"global-agent" "^2.0.2"
"global-tunnel-ng" "^2.7.1"
"@sindresorhus/is@^0.14.0":
"integrity" "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ=="
"resolved" "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz"
"version" "0.14.0"
"@szmarczak/http-timer@^1.1.2":
"integrity" "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA=="
"resolved" "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz"
"version" "1.1.2"
dependencies:
"defer-to-connect" "^1.0.1"
"@types/node@^14.6.2":
"integrity" "sha512-vhUqgjJR1qxwTWV5Ps5txuy2XMdf7Fw+OrdChRboy8BmWUPkckOhphaohzFG6b8DW7CrxaBMdrdJ47SYFq1okw=="
"resolved" "https://registry.npmjs.org/@types/node/-/node-14.17.12.tgz"
"version" "14.17.12"
"ajv-formats@^2.0.2":
"integrity" "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="
"resolved" "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
"version" "2.1.1"
dependencies:
"ajv" "^8.0.0"
"ajv@^8.0.0", "ajv@^8.1.0":
"integrity" "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w=="
"resolved" "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz"
"version" "8.6.2"
dependencies:
"fast-deep-equal" "^3.1.1"
"json-schema-traverse" "^1.0.0"
"require-from-string" "^2.0.2"
"uri-js" "^4.2.2"
"atomically@^1.7.0":
"integrity" "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w=="
"resolved" "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz"
"version" "1.7.0"
"axios@^0.21.1":
"integrity" "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA=="
"resolved" "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz"
"version" "0.21.1"
dependencies:
"follow-redirects" "^1.10.0"
"base64-arraybuffer@^1.0.1":
"integrity" "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA=="
"resolved" "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz"
"version" "1.0.1"
"bindings@~1.2.1":
"integrity" "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
"resolved" "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
"version" "1.2.1"
"boolean@^3.0.1":
"integrity" "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w=="
"resolved" "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz"
"version" "3.1.4"
"buffer-crc32@~0.2.3":
"integrity" "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
"resolved" "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz"
"version" "0.2.13"
"buffer-from@^1.0.0":
"integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
"resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
"version" "1.1.2"
"cacheable-request@^6.0.0":
"integrity" "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg=="
"resolved" "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz"
"version" "6.1.0"
dependencies:
"clone-response" "^1.0.2"
"get-stream" "^5.1.0"
"http-cache-semantics" "^4.0.0"
"keyv" "^3.0.0"
"lowercase-keys" "^2.0.0"
"normalize-url" "^4.1.0"
"responselike" "^1.0.2"
"clone-response@^1.0.2":
"integrity" "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws="
"resolved" "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz"
"version" "1.0.2"
dependencies:
"mimic-response" "^1.0.0"
"concat-stream@^1.6.2":
"integrity" "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw=="
"resolved" "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz"
"version" "1.6.2"
dependencies:
"buffer-from" "^1.0.0"
"inherits" "^2.0.3"
"readable-stream" "^2.2.2"
"typedarray" "^0.0.6"
"conf@^10.0.0":
"integrity" "sha512-iyy4ArqyQ/yrzNASNBN+jaylu53JRuq0ztvL6KAWYHj4iN56BVuhy2SrzEEHBodNbacZr2Pd/4nWhoAwc66T1g=="
"resolved" "https://registry.npmjs.org/conf/-/conf-10.0.2.tgz"
"version" "10.0.2"
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"
"config-chain@^1.1.11":
"integrity" "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="
"resolved" "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz"
"version" "1.1.13"
dependencies:
"ini" "^1.3.4"
"proto-list" "~1.2.1"
"core-js@^3.6.5":
"integrity" "sha512-lM3GftxzHNtPNUJg0v4pC2RC6puwMd6VZA7vXUczi+SKmCWSf4JwO89VJGMqbzmB7jlK7B5hr3S64PqwFL49cA=="
"resolved" "https://registry.npmjs.org/core-js/-/core-js-3.16.3.tgz"
"version" "3.16.3"
"core-util-is@~1.0.0":
"integrity" "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
"resolved" "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
"version" "1.0.2"
"debounce-fn@^4.0.0":
"integrity" "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ=="
"resolved" "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz"
"version" "4.0.0"
dependencies:
"mimic-fn" "^3.0.0"
"debug@^2.6.9":
"integrity" "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="
"resolved" "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
"version" "2.6.9"
dependencies:
"ms" "2.0.0"
"debug@^4.1.0", "debug@^4.1.1":
"integrity" "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw=="
"resolved" "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz"
"version" "4.3.2"
dependencies:
"ms" "2.1.2"
"decompress-response@^3.3.0":
"integrity" "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M="
"resolved" "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz"
"version" "3.3.0"
dependencies:
"mimic-response" "^1.0.0"
"defer-to-connect@^1.0.1":
"integrity" "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ=="
"resolved" "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz"
"version" "1.1.3"
"define-properties@^1.1.3":
"integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ=="
"resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz"
"version" "1.1.3"
dependencies:
"object-keys" "^1.0.12"
"detect-node@^2.0.4":
"integrity" "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
"resolved" "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz"
"version" "2.1.0"
"dot-prop@^6.0.1":
"integrity" "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="
"resolved" "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz"
"version" "6.0.1"
dependencies:
"is-obj" "^2.0.0"
"duplexer3@^0.1.4":
"integrity" "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
"resolved" "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz"
"version" "0.1.4"
"electron-store@^8.0.0":
"integrity" "sha512-ZgRPUZkfrrjWSqxZeaxu7lEvmYf6tgl49dLMqxXGnEmliSiwv3u4rJPG+mH3fBQP9PBqgSh4TCuxHZImMMUgWg=="
"resolved" "https://registry.npmjs.org/electron-store/-/electron-store-8.0.0.tgz"
"version" "8.0.0"
dependencies:
"conf" "^10.0.0"
"type-fest" "^1.0.2"
"electron@^13.2.3":
"integrity" "sha512-FzWhbKHjq7ZTpPQFaYiLPL64erC8/BOsu5NlNN9nQ6f7rIP8ygKlNAlQit3vbOcksQAwItDUCIw4sW0mcaRpCA=="
"resolved" "https://registry.npmjs.org/electron/-/electron-13.2.3.tgz"
"version" "13.2.3"
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^14.6.2"
"extract-zip" "^1.0.3"
"encodeurl@^1.0.2":
"integrity" "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
"resolved" "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
"version" "1.0.2"
"end-of-stream@^1.1.0":
"integrity" "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="
"resolved" "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
"version" "1.4.4"
dependencies:
"once" "^1.4.0"
"env-paths@^2.2.0", "env-paths@^2.2.1":
"integrity" "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
"resolved" "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz"
"version" "2.2.1"
"es6-error@^4.1.1":
"integrity" "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
"resolved" "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz"
"version" "4.1.1"
"escape-string-regexp@^4.0.0":
"integrity" "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
"resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
"version" "4.0.0"
"extract-zip@^1.0.3":
"integrity" "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA=="
"resolved" "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz"
"version" "1.7.0"
dependencies:
"concat-stream" "^1.6.2"
"debug" "^2.6.9"
"mkdirp" "^0.5.4"
"yauzl" "^2.10.0"
"fast-deep-equal@^3.1.1":
"integrity" "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
"resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
"version" "3.1.3"
"fd-slicer@~1.1.0":
"integrity" "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4="
"resolved" "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz"
"version" "1.1.0"
dependencies:
"pend" "~1.2.0"
"find-up@^3.0.0":
"integrity" "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="
"resolved" "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz"
"version" "3.0.0"
dependencies:
"locate-path" "^3.0.0"
"follow-redirects@^1.10.0":
"integrity" "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA=="
"resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz"
"version" "1.14.2"
"fs-extra@^8.1.0":
"integrity" "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="
"resolved" "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz"
"version" "8.1.0"
dependencies:
"graceful-fs" "^4.2.0"
"jsonfile" "^4.0.0"
"universalify" "^0.1.0"
"get-stream@^4.1.0":
"integrity" "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w=="
"resolved" "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz"
"version" "4.1.0"
dependencies:
"pump" "^3.0.0"
"get-stream@^5.1.0":
"integrity" "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="
"resolved" "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"
"version" "5.2.0"
dependencies:
"pump" "^3.0.0"
"global-agent@^2.0.2":
"integrity" "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg=="
"resolved" "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz"
"version" "2.2.0"
dependencies:
"boolean" "^3.0.1"
"core-js" "^3.6.5"
"es6-error" "^4.1.1"
"matcher" "^3.0.0"
"roarr" "^2.15.3"
"semver" "^7.3.2"
"serialize-error" "^7.0.1"
"global-tunnel-ng@^2.7.1":
"integrity" "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg=="
"resolved" "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz"
"version" "2.7.1"
dependencies:
"encodeurl" "^1.0.2"
"lodash" "^4.17.10"
"npm-conf" "^1.1.3"
"tunnel" "^0.0.6"
"globalthis@^1.0.1":
"integrity" "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ=="
"resolved" "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz"
"version" "1.0.2"
dependencies:
"define-properties" "^1.1.3"
"got@^9.6.0":
"integrity" "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q=="
"resolved" "https://registry.npmjs.org/got/-/got-9.6.0.tgz"
"version" "9.6.0"
dependencies:
"@sindresorhus/is" "^0.14.0"
"@szmarczak/http-timer" "^1.1.2"
"cacheable-request" "^6.0.0"
"decompress-response" "^3.3.0"
"duplexer3" "^0.1.4"
"get-stream" "^4.1.0"
"lowercase-keys" "^1.0.1"
"mimic-response" "^1.0.1"
"p-cancelable" "^1.0.0"
"to-readable-stream" "^1.0.0"
"url-parse-lax" "^3.0.0"
"graceful-fs@^4.1.6", "graceful-fs@^4.2.0":
"integrity" "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg=="
"resolved" "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz"
"version" "4.2.8"
"http-cache-semantics@^4.0.0":
"integrity" "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
"resolved" "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz"
"version" "4.1.0"
"inherits@^2.0.3", "inherits@~2.0.3":
"integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
"version" "2.0.4"
"ini@^1.3.4":
"integrity" "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
"resolved" "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
"version" "1.3.8"
"is-obj@^2.0.0":
"integrity" "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="
"resolved" "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz"
"version" "2.0.0"
"isarray@~1.0.0":
"integrity" "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
"resolved" "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
"version" "1.0.0"
"[email protected]":
"integrity" "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg="
"resolved" "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz"
"version" "3.0.0"
"json-schema-traverse@^1.0.0":
"integrity" "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
"resolved" "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
"version" "1.0.0"
"json-schema-typed@^7.0.3":
"integrity" "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="
"resolved" "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz"
"version" "7.0.3"
"json-stringify-safe@^5.0.1":
"integrity" "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
"resolved" "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
"version" "5.0.1"
"jsonfile@^4.0.0":
"integrity" "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss="
"resolved" "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz"
"version" "4.0.0"
optionalDependencies:
"graceful-fs" "^4.1.6"
"keyv@^3.0.0":
"integrity" "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA=="
"resolved" "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz"
"version" "3.1.0"
dependencies:
"json-buffer" "3.0.0"
"locate-path@^3.0.0":
"integrity" "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="
"resolved" "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz"
"version" "3.0.0"
dependencies:
"p-locate" "^3.0.0"
"path-exists" "^3.0.0"
"lodash@^4.17.10":
"integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
"version" "4.17.21"
"lowercase-keys@^1.0.0", "lowercase-keys@^1.0.1":
"integrity" "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
"resolved" "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz"
"version" "1.0.1"
"lowercase-keys@^2.0.0":
"integrity" "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
"resolved" "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz"
"version" "2.0.0"
"lru-cache@^6.0.0":
"integrity" "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="
"resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
"version" "6.0.0"
dependencies:
"yallist" "^4.0.0"
"matcher@^3.0.0":
"integrity" "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="
"resolved" "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz"
"version" "3.0.0"
dependencies:
"escape-string-regexp" "^4.0.0"
"mimic-fn@^2.1.0":
"integrity" "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
"resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
"version" "2.1.0"
"mimic-fn@^3.0.0":
"integrity" "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ=="
"resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz"
"version" "3.1.0"
"mimic-response@^1.0.0", "mimic-response@^1.0.1":
"integrity" "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
"resolved" "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz"
"version" "1.0.1"
"minimist@^1.2.5":
"integrity" "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz"
"version" "1.2.5"
"mkdirp@^0.5.4":
"integrity" "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ=="
"resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz"
"version" "0.5.5"
dependencies:
"minimist" "^1.2.5"
"[email protected]":
"integrity" "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"resolved" "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
"version" "2.0.0"
"[email protected]":
"integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
"version" "2.1.2"
"nan@^2.14.1":
"integrity" "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
"resolved" "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz"
"version" "2.15.0"
"normalize-url@^4.1.0":
"integrity" "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA=="
"resolved" "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz"
"version" "4.5.1"
"npm-conf@^1.1.3":
"integrity" "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw=="
"resolved" "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz"
"version" "1.1.3"
dependencies:
"config-chain" "^1.1.11"
"pify" "^3.0.0"
"object-keys@^1.0.12":
"integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
"resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz"
"version" "1.1.1"
"once@^1.3.1", "once@^1.4.0":
"integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
"resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
"version" "1.4.0"
dependencies:
"wrappy" "1"
"onetime@^5.1.2":
"integrity" "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="
"resolved" "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz"
"version" "5.1.2"
dependencies:
"mimic-fn" "^2.1.0"
"p-cancelable@^1.0.0":
"integrity" "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw=="
"resolved" "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz"
"version" "1.1.0"
"p-limit@^2.0.0":
"integrity" "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="
"resolved" "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
"version" "2.3.0"
dependencies:
"p-try" "^2.0.0"
"p-locate@^3.0.0":
"integrity" "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="
"resolved" "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz"
"version" "3.0.0"
dependencies:
"p-limit" "^2.0.0"
"p-try@^2.0.0":
"integrity" "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
"resolved" "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
"version" "2.2.0"
"path-exists@^3.0.0":
"integrity" "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
"resolved" "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz"
"version" "3.0.0"
"pend@~1.2.0":
"integrity" "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
"resolved" "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
"version" "1.2.0"
"pify@^3.0.0":
"integrity" "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
"resolved" "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz"
"version" "3.0.0"
"pkg-up@^3.1.0":
"integrity" "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="
"resolved" "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz"
"version" "3.1.0"
dependencies:
"find-up" "^3.0.0"
"prepend-http@^2.0.0":
"integrity" "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc="
"resolved" "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz"
"version" "2.0.0"
"process-nextick-args@~2.0.0":
"integrity" "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
"resolved" "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
"version" "2.0.1"
"progress@^2.0.3":
"integrity" "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
"resolved" "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
"version" "2.0.3"
"proto-list@~1.2.1":
"integrity" "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
"resolved" "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz"
"version" "1.2.4"
"pump@^3.0.0":
"integrity" "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww=="
"resolved" "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz"
"version" "3.0.0"
dependencies:
"end-of-stream" "^1.1.0"
"once" "^1.3.1"
"punycode@^2.1.0":
"integrity" "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
"resolved" "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
"version" "2.1.1"
"readable-stream@^2.2.2":
"integrity" "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw=="
"resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
"version" "2.3.7"
dependencies:
"core-util-is" "~1.0.0"
"inherits" "~2.0.3"
"isarray" "~1.0.0"
"process-nextick-args" "~2.0.0"
"safe-buffer" "~5.1.1"
"string_decoder" "~1.1.1"
"util-deprecate" "~1.0.1"
"require-from-string@^2.0.2":
"integrity" "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
"resolved" "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
"version" "2.0.2"
"responselike@^1.0.2":
"integrity" "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec="
"resolved" "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz"
"version" "1.0.2"
dependencies:
"lowercase-keys" "^1.0.0"
"roarr@^2.15.3":
"integrity" "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="
"resolved" "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz"
"version" "2.15.4"
dependencies:
"boolean" "^3.0.1"
"detect-node" "^2.0.4"
"globalthis" "^1.0.1"
"json-stringify-safe" "^5.0.1"
"semver-compare" "^1.0.0"
"sprintf-js" "^1.1.2"
"safe-buffer@~5.1.0", "safe-buffer@~5.1.1":
"integrity" "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
"resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
"version" "5.1.2"
"semver-compare@^1.0.0":
"integrity" "sha1-De4hahyUGrN+nvsXiPavxf9VN/w="
"resolved" "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
"version" "1.0.0"
"semver@^6.2.0":
"integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
"version" "6.3.0"
"semver@^7.3.2", "semver@^7.3.5":
"integrity" "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ=="
"resolved" "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz"
"version" "7.3.5"
dependencies:
"lru-cache" "^6.0.0"
"serialize-error@^7.0.1":
"integrity" "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="
"resolved" "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz"
"version" "7.0.1"
dependencies:
"type-fest" "^0.13.1"
"sleep@^6.3.0":
"integrity" "sha512-+WgYl951qdUlb1iS97UvQ01pkauoBK9ML9I/CMPg41v0Ze4EyMlTgFTDDo32iYj98IYqxIjDMRd+L71lawFfpQ=="
"resolved" "https://registry.npmjs.org/sleep/-/sleep-6.3.0.tgz"
"version" "6.3.0"
dependencies:
"nan" "^2.14.1"
"sprintf-js@^1.1.2":
"integrity" "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
"resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz"
"version" "1.1.2"
"string_decoder@~1.1.1":
"integrity" "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="
"resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
"version" "1.1.1"
dependencies:
"safe-buffer" "~5.1.0"
"sumchecker@^3.0.1":
"integrity" "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="
"resolved" "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz"
"version" "3.0.1"
dependencies:
"debug" "^4.1.0"
"to-readable-stream@^1.0.0":
"integrity" "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q=="
"resolved" "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz"
"version" "1.0.0"
"tunnel@^0.0.6":
"integrity" "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
"resolved" "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz"
"version" "0.0.6"
"type-fest@^0.13.1":
"integrity" "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="
"resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz"
"version" "0.13.1"
"type-fest@^1.0.2":
"integrity" "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="
"resolved" "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz"
"version" "1.4.0"
"typedarray@^0.0.6":
"integrity" "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
"resolved" "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
"version" "0.0.6"
"universalify@^0.1.0":
"integrity" "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
"resolved" "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz"
"version" "0.1.2"
"uri-js@^4.2.2":
"integrity" "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="
"resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
"version" "4.4.1"
dependencies:
"punycode" "^2.1.0"
"url-parse-lax@^3.0.0":
"integrity" "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww="
"resolved" "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz"
"version" "3.0.0"
dependencies:
"prepend-http" "^2.0.0"
"util-deprecate@~1.0.1":
"integrity" "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
"resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
"version" "1.0.2"
"wrappy@1":
"integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
"version" "1.0.2"
"ws@^7.3.1":
"integrity" "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg=="
"resolved" "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz"
"version" "7.5.4"
"yallist@^4.0.0":
"integrity" "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
"resolved" "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
"version" "4.0.0"
"yauzl@^2.10.0":
"integrity" "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk="
"resolved" "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"
"version" "2.10.0"
dependencies:
"buffer-crc32" "~0.2.3"
"fd-slicer" "~1.1.0"