Compare commits

...
43 changed files with 13087 additions and 2926 deletions

29
.gitignore vendored
View file

@ -1,30 +1,5 @@
.DS_Store .DS_Store
/node_modules /node_modules
/dist /out
/crate/target/*
/.log
crash.log *.sublime-workspace
# 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

View file

@ -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}\"}"

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/:_authToken=${NPM_TOKEN} //gitlab.com/api/v4/projects/28849281/packages/npm

View file

@ -1,29 +0,0 @@
# 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/).

8
electron.sublime-project Normal file
View file

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

14
entitlements.plist Normal file
View file

@ -0,0 +1,14 @@
<?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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

11766
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,54 @@
{ {
"name": "Crimata", "name": "crimata",
"productName": "crimata-messenger",
"version": "1.0.0", "version": "1.0.0",
"private": true, "description": "Cross-platform messenger app (Electron, Vue3)",
"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 ." "start": "electron-forge start",
"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"
}, },
"devDependencies": { "config": {
"electron": "^13.2.3" "forge": {
"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,44 +1,43 @@
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", "[email protected]"); store.set("account", process.env.ACCOUNT);
store.set("account", "[email protected]");
async function login(_e, email, password) async function auth(_e, creds)
{ {
const res = await postLogin(email, password); const { error, data } = await post("/auth", creds);
if (res)
if (error)
{ {
const account = res.crimataId; return data;
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() function logout(_e, reason)
{ {
store.delete("account"); store.delete("account");
ipcEmit("set-account", false); ipcEmit("account", false, reason);
endSession(); endSession();
}; };
function initAccount() function initAccount()
{ {
ipcMain.handle("login", login); ipcMain.handle("auth", auth);
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,18 +2,37 @@ const axios = require('axios').default;
const config = require("./config"); const config = require("./config");
const baseUrl = config.BUSINESS_URL + config.BUSINESS_PREFIX;
async function postLogin(email, password) async function post(route, body)
{ {
try { let error;
const res = await axios.post(baseUrl + "/account/login", { let data;
email: email, password: password
}); try
return res.data; {
} catch (e) { const res = await axios.post(config.API + route, body);
return false; 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.postLogin = postLogin; exports.post = post;

View file

@ -26,7 +26,9 @@ backgroundMitt.on("data", (int16Arr) => {
backgroundMitt.on("write", (int16Arr) => { backgroundMitt.on("write", (int16Arr) => {
clearTimeout(setWriteId); clearTimeout(setWriteId);
setWriteId = setTimeout(setPlaybackStatus.prototype.bind(null, false), 500); setWriteId = setTimeout(() => {
setPlaybackStatus(false);
}, 500);
}); });
function setStreams() function setStreams()
@ -67,7 +69,7 @@ function stopRecording()
setRecordingStatus(false); setRecordingStatus(false);
sendMessage({ sendMessage({
category: "audio/L16", category: "audio",
text: null, text: null,
blob: encode(nodeAudio.utils.mergeChunks(chunks).buffer) blob: encode(nodeAudio.utils.mergeChunks(chunks).buffer)
}); });
@ -130,8 +132,3 @@ 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,16 +1,11 @@
const { app } = require("electron"); const { app } = require("electron");
const env = process.env; const DOMAIN = "crimata.com";
const PLATFORM_PORT = env.PLATFORM_PORT || 8760; /* If PROD env variable exists */
const PLATFORM_IP = env.PLATFORM_IP || 'http://127.0.0.1'; const prod = process.env.PROD;
const BUSINESS_PORT = env.BUSINESS_PORT || 3000;
const BUSINESS_IP = env.BUSINESS_IP || 'http://127.0.0.1';
module.exports = { module.exports = {
PLATFORM_URL: `${PLATFORM_IP}:${PLATFORM_PORT}`, PLATFORM: prod ? `https://app.${DOMAIN}` : `http://localhost:5000`,
BUSINESS_URL: `${BUSINESS_IP}:${BUSINESS_PORT}`, API: prod ? `https://${DOMAIN}/api` : `http://localhost:5001`
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_URL}/${account}`) connection = new WebSocket(`${config.PLATFORM}/${account}`)
.on("open", () => pingId = setInterval(() => connection.ping(null, true), 1000)) .on("open", () => pingId = setInterval(() => connection.ping(null, true), 1000))
@ -24,11 +24,22 @@ 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") {
reconnectId = setTimeout(() => connectToPlatform(account), 500); if (reason)
{
if (reason == "unauthorized")
{
backgroundMitt.emit("logout", reason);
}
return;
} }
reconnectId = setTimeout(() => connectToPlatform(account), 500);
}); });
} }
@ -42,13 +53,13 @@ function sendMessage(message)
function disconnectFromPlatform() function disconnectFromPlatform()
{ {
clearTimeout(reconnectId); clearTimeout(reconnectId);
connection.close(1000, "logout"); if (connection.open) connection.close(1000, "logout");
} }
function setConnectionStatus(status) function setConnectionStatus(status)
{ {
updateTray("disconnect", status); updateTray("disconnect", status);
ipcEmit("ws-status", status); ipcEmit("ws", status);
} }
ipcMain.on("message", (_e, message) => sendMessage(message)); ipcMain.on("message", (_e, message) => sendMessage(message));
@ -56,25 +67,3 @@ ipcMain.on("message", (_e, message) => sendMessage(message));
exports.connectToPlatform = connectToPlatform; exports.connectToPlatform = connectToPlatform;
exports.sendMessage = sendMessage; exports.sendMessage = sendMessage;
exports.disconnectFromPlatform = disconnectFromPlatform; exports.disconnectFromPlatform = disconnectFromPlatform;
/* ------------- Test Messages------------- */
setTimeout(() => {
// sendMessage({
// category: "text/plain",
// text: "show contacts",
// blob: null
// })
sendMessage({
category: "text/plain",
text: "add contact John Smith, [email protected]",
blob: null
})
}, 2000);

View file

@ -3,10 +3,9 @@ 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");
// Assert a light theme // Assert a light theme
nativeTheme.themeSource = "light"; nativeTheme.themeSource = "light";
@ -26,4 +25,4 @@ app.on("ready", () =>
app.on("activate", createWin); app.on("activate", createWin);
// Prevents app from quitting on window close event // Prevents app from quitting on window close event
app.on('window-all-closed', (e) => e.preventDefault()); app.on('window-all-closed', (e) => e.preventDefault());

View file

@ -1,52 +0,0 @@
<!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

@ -1,14 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24.87" height="23.001" viewBox="0 0 24.87 23.001"> <svg id="logo" xmlns="http://www.w3.org/2000/svg" width="24.87" height="23.001" viewBox="0 0 24.87 23.001">
<g id="Group_541" data-name="Group 541" transform="translate(-4127 -507)"> <g id="Group_33" data-name="Group 33">
<g id="Group_33" data-name="Group 33" transform="translate(4127 507)"> <g id="Group_32" data-name="Group 32" transform="translate(0 0)">
<g id="Group_32" data-name="Group 32" transform="translate(0 0)"> <g id="Group_31" data-name="Group 31" transform="translate(0 11.948)">
<g id="Group_31" data-name="Group 31" transform="translate(0 11.948)"> <circle id="Ellipse_50" data-name="Ellipse 50" cx="5.527" cy="5.527" r="5.527" transform="translate(0 0)" fill="#383838"/>
<circle id="Ellipse_50" data-name="Ellipse 50" cx="5.527" cy="5.527" r="5.527" transform="translate(0 0)" fill="#383838"/> <circle id="Ellipse_51" data-name="Ellipse 51" cx="5.527" cy="5.527" r="5.527" transform="translate(13.817 0)" fill="#383838"/>
<circle id="Ellipse_51" data-name="Ellipse 51" cx="5.527" cy="5.527" r="5.527" transform="translate(13.817 0)" fill="#383838"/>
</g>
<circle id="Ellipse_52" data-name="Ellipse 52" cx="5.527" cy="5.527" r="5.527" transform="translate(6.898 0)" fill="#383838"/>
<path id="Path_150" data-name="Path 150" d="M36.27,83.67" transform="translate(-30.733 -66.196)" fill="#ff0"/>
</g> </g>
<circle id="Ellipse_52" data-name="Ellipse 52" cx="5.527" cy="5.527" r="5.527" transform="translate(6.898)" fill="#383838"/>
<path id="Path_150" data-name="Path 150" d="M36.27,83.67" transform="translate(-30.733 -66.196)" fill="#ff0"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 973 B

After

Width:  |  Height:  |  Size: 845 B

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,6 +1,7 @@
import Splash from "./splash.js";
import Messenger from "./messenger.js";
import Login from "./login.js"; 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 account = Vue.ref(null);
@ -8,6 +9,7 @@ const App =
{ {
components: { components: {
Splash, Splash,
Header,
Messenger, Messenger,
Login Login
}, },
@ -18,25 +20,25 @@ const App =
}, },
template: ` template: `
<main id="app" v-if="account !== null"> <div id="app" v-if="account !== null">
<button id="header-titlebar" /> <Header />
<Messenger v-if="account"/> <Messenger v-if="account"/>
<Login v-else /> <Login v-else />
</main> </div>
<Splash v-else />` <Splash v-else />`
} }
window.mainApi.on("set-account", (account_) => { window.mainApi.on("account", (value, reason) => {
console.log("setting-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 // Is called when window is about to close or reload
window.onbeforeunload = () => console.log("beforeUnload"); window.onbeforeunload = () => console.log("beforeunload");

View file

@ -1,6 +1,6 @@
const Bubble = const Bubble =
{ {
props: ["category", "text", "blob", "modifier", "child"], props: ["category", "text", "html", "blob", "modifier", "child"],
setup(props) setup(props)
{ {
@ -9,28 +9,40 @@ const Bubble =
} }
Vue.onMounted(() => { Vue.onMounted(() => {
document.getElementById("messenger").dispatchEvent(new CustomEvent('adjust-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">
<p v-if="category=='text/plain'">{{ text }}</p> <small class="bubble-notify" v-if="modifier=='ai'"></small>
<p v-else-if="category=='audio/L16'">{{ text }}</p> <p v-if="category=='text'">{{ text }}</p>
<div v-else-if="category=='text/html'" v-html="text"></div> <p v-else-if="category=='audio'">{{ text }}</p>
<img v-else-if="category.startsWith('image/')" :src="getUrl()" /> <div v-else-if="category=='html'" v-html="html"></div>
<a v-else :href="getUrl()">{{ text }}</a> <img v-else-if="category == 'image'" :src="getUrl()" />
<a v-else :download="text" :href="getUrl()">{{ text }}</a>
</div>` </div>`
}; };
export default Bubble; export default Bubble;
// TODO: should include other styling/actions for audio bubbles // TODO: should include other styling/actions for audio bubbles

View file

@ -20,15 +20,10 @@ const Context =
template: ` template: `
<span :class="'context ' + modifier + '-context'"> <span :class="'context ' + modifier + '-context'">
<div <img v-show="avatar" class="context-avatar"
v-show="avatar"
class="context-avatar"
:class="{ audio: playback }" :class="{ audio: playback }"
> :src="'data:image/png;base64,' + avatar"
/>
<img :src="'data:image/png;base64,' + avatar" />
</div>
{{ context }} {{ context }}

View file

@ -1,5 +1,5 @@
const saveLocation = "input_item_position"; const saveLocation = "input_item_position";
const defaultPosition = { x: 15, y: window.innerHeight - 300 }; const defaultPosition = { x: 15, y: 400 };
//---Dragabble Helper Funcs-------------------------------------- //---Dragabble Helper Funcs--------------------------------------
@ -33,9 +33,10 @@ function calcPosition (elementPosition, elementLength, percent, short, win) {
return elementPosition return elementPosition
} }
function draggify(elementId, margin) { function draggify(elementId, parentId, 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;
@ -52,23 +53,20 @@ function draggify(elementId, 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;
for (let i = 1; i <= 6000; i++) { const xStep = xChange / 1000;
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;
}, 16) // 60fps }, 30) // 60fps
} }
} }
@ -79,9 +77,8 @@ function draggify(elementId, margin) {
const onMouseMove = (e) => { const onMouseMove = (e) => {
e.preventDefault(); e.preventDefault();
const inputItemRect = element.getBoundingClientRect(); elementX.value = element.offsetLeft + e.movementX;
elementX.value = inputItemRect.left + e.movementX; elementY.value = element.offsetTop + e.movementY;
elementY.value = inputItemRect.top + e.movementY;
} }
// Add an event listener for dragging. // Add an event listener for dragging.
@ -102,6 +99,9 @@ function draggify(elementId, 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,12 +138,8 @@ function draggify(elementId, margin) {
// Update position of targetEl on windowResize. // Update position of targetEl on windowResize.
const onWindowResize = (_e) => { const onWindowResize = (_e) => {
// Update window dimensions. elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, parent.clientWidth);
winW = window.innerWidth; elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, parent.clientHeight);
winH = window.innerHeight;
elementX.value = calcPosition(elementX.value, elementLength, percentX, xShort, winW);
elementY.value = calcPosition(elementY.value, elementLength, percentY, yShort, winH);
savePosition(); savePosition();
} }
@ -160,27 +156,28 @@ function draggify(elementId, 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 / window.innerWidth; percentX = elementX.value / parent.clientWidth;
percentY = elementY.value / window.innerHeight; percentY = elementY.value / parent.clientHeight;
xShort = calcSideProximity(elementX.value, elementLength, window.innerWidth); xShort = calcSideProximity(elementX.value, elementLength, parent.clientWidth);
yShort = calcSideProximity(elementX.value, elementLength, window.innerHeight); yShort = calcSideProximity(elementX.value, elementLength, parent.clientHeight);
// 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);
window.addEventListener('resize', onWindowResize) parent.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);
window.removeEventListener('resize', onWindowResize) parent.removeEventListener('resize', onWindowResize)
window.removeEventListener('mouseup', onMouseUp) parent.removeEventListener('mouseup', onMouseUp)
window.removeEventListener('mousemove', onMouseMove); parent.removeEventListener('mousemove', onMouseMove);
}); });
// Try loading initPosition, otherwise set default values // Try loading initPosition, otherwise set default values

View file

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

View file

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

View file

@ -17,25 +17,27 @@ const metaKeys = [
"Escape" "Escape"
]; ];
function useText(elementId, left, typing) function useText(elementId, parentId, left)
{ {
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 = () => {
window.innerWidth - left.value < inputLength ? leftSide.value = true : leftSide.value = false; parent.clientWidth - 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) => {
if (!show.value && !metaKeys.includes(e.key)) if (!show.value && !metaKeys.includes(e.key) && !e.ctrlKey)
{ {
show.value = true; show.value = true;
} }
@ -46,15 +48,23 @@ function useText(elementId, left, typing)
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/plain", 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);
@ -68,8 +78,6 @@ function useText(elementId, left, typing)
} }
}); });
calcSide();
return { leftSide, show }; return { leftSide, show };
} }

View file

@ -0,0 +1,23 @@
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

@ -0,0 +1,86 @@
// 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-bar', command); const onNavBar = (command) => window.mainApi.send("nav", 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", 15); const { elementX, elementY } = draggify("input", "app", 15);
const { leftSide, show } = useText("input-text", elementX); const { leftSide, show } = useText("input-text", "app", elementX);
window.mainApi.on("record", (status) => recording.value = status); window.mainApi.on("record", (status) => recording.value = status);

View file

@ -1,58 +1,44 @@
import SmartForm from "./form.js";
import { isEmail, isPassword, isName } from "./control/valid.js";
const Login = const Login =
{ {
components: {
SmartForm
},
setup() setup()
{ {
const usr = Vue.ref(""); return { isEmail, isPassword, isName };
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: `
<form id="login" @submit.prevent="submitForm"> <div id="login">
<!-- username and password forms --> <SmartForm
<div id="login-form"> :fields="[{
<!-- username --> name: 'email',
<div class="login-input-box"> value: null,
<input class="login-input" type="text" v-model="usr" /> placeholder: 'Email',
<div class="login-input-modifier">Email</div> valid: isEmail
</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.'"
/>
<!-- password --> </div>`
<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 */ export default Login;
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;

View file

@ -8,7 +8,7 @@ const Message =
Context Context
}, },
props: ["modifier", "content", "context", "avatar", "id"], props: ["modifier", "content", "context", "avatar", "id", "seen"],
setup(props) setup(props)
{ {
@ -18,10 +18,12 @@ const Message =
template: ` template: `
<div :id="id" :class="'message ' + modifier + '-message'"> <div :id="id" :class="'message ' + modifier + '-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"
:blob="c.blob" :blob="c.blob"
:modifier="modifier" :modifier="modifier"
:child="calcChild(index, content.length)" :child="calcChild(index, content.length)"

View file

@ -1,4 +1,5 @@
import initScroll from "./control/scroll.js"; import onDrop from "./control/drop.js";
import scroll from "./control/scroll.js";
import WS from "./ws.js"; import WS from "./ws.js";
import Input from "./input.js"; import Input from "./input.js";
@ -17,64 +18,49 @@ const Messenger = {
setup() setup()
{ {
const person = Vue.ref(""); const person = Vue.ref("");
const messages = Vue.ref([{ const messages = Vue.ref([]);
modifier: "ai",
content: [{
category: "text/html",
text: `<div class="mail">
<h4>About HTML Parser</h4>
<p>Return the text of the most recently opened start tag. This should not normally be needed for structured processing, but may be useful in dealing with HTML “as deployed” or for re-generating input with minimal changes (whitespace between attributes can be preserved, etc.).</p>
</div>`,
blob: null
}],
context: "Email from David Spade",
avatar: null,
id: 0
}]);
const onDrop = async (e) => { let current = Vue.ref();
const file = e.dataTransfer.items[0].getAsFile();
const buffer = await file.arrayBuffer();
const b64String = await window.mainApi.invoke("encode", buffer);
window.mainApi.send("message", {
category: file.type,
text: file.name,
blob: b64String
});
}
Vue.onMounted(() => { Vue.onMounted(() => {
window.mainApi.on("message", (message) => { window.mainApi.on("message", (update) => {
if (message.person)
if (update.person)
{ {
person.value = message.person; person.value = update.person;
messages.value = message.messages; messages.value = update.messages;
current.value = messages.value.at(-1);
} }
else if (message.name) else if (update.name)
{ {
person.value = message; person.value = update.name;
} }
else if (message.content) else if (update.modifier)
{ {
messages.value.push(message); 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
{ {
messages.value[messages.value.length - 1].content.push(message); current.value.content.at(-1).text = update.text;
} }
setTimeout(scroll, 10);
}); });
initScroll("messenger");
window.mainApi.send("messenger"); window.mainApi.send("messenger");
}); });
Vue.onUnmounted(() => {
messages.value = [];
});
return { person, messages, onDrop }; return { person, messages, onDrop };
}, },
@ -88,15 +74,13 @@ const Messenger = {
@dragover.prevent @dragover.prevent
@dragenter.prevent @dragenter.prevent
> >
<Message
v-for="message in messages" <Message
:modifier="message.modifier" v-for="message in messages"
:content="message.content" v-bind="message"
:context="message.context"
:avatar="message.avatar"
:id="message.id"
/> />
</div> `
</div>`
} }
export default Messenger; export default Messenger;

View file

@ -4,25 +4,37 @@ const Settings =
setup() { setup() {
const active = Vue.ref(false); const active = Vue.ref(false);
window.addEventListener("keydown", (e) => { function hideSettings(e) {
console.log("toggle settings"); if (e.key == "Escape") {
}) active.value = false;
window.removeEventListener("keydown", hideSettings);
}
}
function showSettings() {
active.value = true;
window.addEventListener("keydown", hideSettings);
}
const logout = async () => { const logout = async () => {
await window.mainApi.send("logout"); await window.mainApi.send("logout");
} }
return { active, logout }; return { showSettings, active, logout, window };
}, },
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
</button> </button>
</div>` </div>
<button v-else class="settings-icon" @click="showSettings">
<img :src="window.path + 'assets/settingsIcon.svg'">
</button>`
} }
export default Settings; export default Settings;

View file

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

View file

@ -1,2 +1,5 @@
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,29 +1,48 @@
@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);*/
} }
/* 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: 100vw; width: 100%;
height: 100vh; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
#app { #app {
font-family: "SF Pro Text"; position: relative; /* must explicitly be declared */
font-family: "Default";
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
height: 100vh; height: 100vh; /* 100% for website */
width: 100vw; width: 100vw; /* 100% for website */
/* Does not work on Linux */
border-radius: 15px; border-radius: 15px;
} }
#header-titlebar { #header-titlebar {
position: fixed; position: absolute;
width: 100vw; width: 100%;
height: 54px; height: 54px;
opacity: 0.75; opacity: 0.75;
background-color: #EBEBEB; background-color: #EBEBEB;
@ -31,6 +50,8 @@ 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 {
@ -46,6 +67,7 @@ html, body {
} }
.contacts img { .contacts img {
height: 30px;
border-radius: 50%; border-radius: 50%;
} }
@ -59,38 +81,26 @@ html, body {
} }
.contacts .email { .contacts .email {
font-family: SF Compact Display; font-family: "Compact";
font-weight: bold;
font-size: 12px; font-size: 12px;
color: #898989; color: #898989;
} overflow: w;
word-wrap: break-word;
.mail {
padding: 12px;
margin: 0px;
background-color: #D6F2FF;
border-radius: inherit;
}
.mail h4 {
margin-top: 0px;
}
.mail p {
margin-bottom: 0px;
} }
#header-menu { #header-menu {
position: fixed; position: absolute;
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: 7px; border-radius: 50%;
} }
.header-exit-button { .header-exit-button {
@ -119,87 +129,9 @@ 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: 100vw; width: 100%;
height: 100vh; height: 100%;
overflow: auto; overflow: auto;
} }
@ -209,8 +141,8 @@ html, body {
} }
#ws { #ws {
position: fixed; position: absolute;
width: 100vw; width: 100%;
height: 54px; height: 54px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -251,6 +183,7 @@ html, body {
content: ''; content: '';
display: inline-block; display: inline-block;
position: absolute; position: absolute;
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
} }
.ws-dot::before { .ws-dot::before {
@ -297,7 +230,6 @@ 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);
} }
@ -309,6 +241,7 @@ 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;
@ -328,9 +261,9 @@ html, body {
} }
#settings { #settings {
position: fixed; position: absolute;
width: 100vw; width: 100%;
height: 100vh; height: 100%;
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;
@ -339,6 +272,7 @@ 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 {
@ -351,7 +285,7 @@ html, body {
} }
.settings-icon { .settings-icon {
position: fixed; position: absolute;
right: 0; right: 0;
border: none; border: none;
outline: none; outline: none;
@ -362,10 +296,8 @@ 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 {
@ -375,16 +307,11 @@ html, body {
} }
.settings-logout-button { .settings-logout-button {
font-family: "SF Compact Display"; font-family: "Compact";
background-color: #B7B7B7; background-color: #B7B7B7;
padding: 10px 30px; padding: 10px 20px;
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 {
@ -398,7 +325,7 @@ html, body {
} }
.message { .message {
width: 100vw; width: inherit;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: 9px; padding-top: 9px;
@ -423,6 +350,17 @@ 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;
} }
@ -431,13 +369,40 @@ html, body {
align-items: flex-start; align-items: flex-start;
} }
.admin-message {
align-items: center;
}
.bubble { .bubble {
position: relative; position: relative;
max-width: 66vw; max-width: 66%;
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 {
@ -489,17 +454,17 @@ html, body {
.bubble > img { .bubble > img {
border-radius: inherit; border-radius: inherit;
display: block; display: block;
max-width: inherit; max-width: 100%;
} }
.bubble a { .bubble a {
display: inline-block;
font-family: "Rounded";
text-decoration: none;
border-radius: inherit; border-radius: inherit;
font-family: "SF Compact Rounded";
background-color: #D9D9D9; background-color: #D9D9D9;
font-weight: bold; padding: 10px;
padding: 12px;
color: #727272; color: #727272;
margin: 0px;
} }
.context { .context {
@ -507,9 +472,8 @@ html, body {
min-height: 14px; min-height: 14px;
display: flex; display: flex;
align-items: center; align-items: center;
font-family: "SF Compact Display"; font-family: "Compact";
font-size: 12px; font-size: 12px;
font-weight: bold;
margin-top: 5px; margin-top: 5px;
} }
@ -522,18 +486,9 @@ html, body {
} }
.context-avatar { .context-avatar {
display: flex;
justify-content: center;
align-items: center;
width: 30px;
height: 30px;
background-color: white;
border-radius: 15px;
margin-right: 5px; margin-right: 5px;
}
.context-avatar img {
border-radius: 50%; border-radius: 50%;
height: 30px;
} }
/* ---------- shared between components ---------- */ /* ---------- shared between components ---------- */
@ -548,6 +503,32 @@ 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;
@ -569,3 +550,29 @@ 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,20 @@ const { contextBridge, ipcRenderer } = require("electron");
/** The main <-> render interface */ /** The main <-> render interface */
const validFromMainChannels = [ const validFromMainChannels = [
"set-account", "playback",
"message", "account",
"ws-status", "message",
"record", "record",
"playback" "ws"
]; ];
const validToMain = [ const validToMain = [
"messenger", "messenger",
"message", "message",
"login",
"logout", "logout",
"encode", "encode",
"decode" "auth",
"nav"
] ]
ipcRenderer.setMaxListeners(250); ipcRenderer.setMaxListeners(250);

View file

@ -3,9 +3,12 @@ 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");
let ui; /* wait to receive ui from the platform */ const util = require('util');
let ui;
let current;
const messageQueue = []; const messageQueue = [];
@ -16,58 +19,67 @@ let processContentId;
* - New message (add message to ui.messages) * - New message (add message to ui.messages)
* - New content (add content to existing message) * - New content (add content to existing message)
* - Update person * - Update person
* - Notification
*/ */
backgroundMitt.on("message", (message) => messageQueue.push(message)); backgroundMitt.on("message", (message) => messageQueue.push(message));
function handleMessageQueue() function handleMessageQueue()
{ {
if (!streamState.pb && !streamState.rec) // if (!streamState.pb && !streamState.rec)
{ // {
const update = messageQueue.shift(); const update = messageQueue.shift();
if (update) console.log(update);
{
console.log("Handling UI Update:", update);
if (update)
{
if (update.person) if (update.person)
{ {
ui = update; ui = update;
current = update.messages.at(-1);
} }
else if (update.name) else if (update.name)
{ {
ui.person = update; ui.person = update;
} }
else if (update.content) else if (update.modifier)
{ {
ui.messages.push(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 else
{ {
ui.messages[ui.messages.length - 1].content.push(update); 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); ipcEmit("message", update);
// notify();
} }
} // }
}
function notify()
{
const message = ui.messages[ui.messages.length - 1];
const content = message.content[message.content.length -1];
new Notification(
{
title: message.context,
body: content.text
}
).show();
if (content.category == "audio/L16") playback(content.blob, message.id);
} }
function launchSession(account) function launchSession(account)
@ -93,5 +105,3 @@ ipcMain.on("messenger", () => {
exports.launchSession = launchSession; exports.launchSession = launchSession;
exports.endSession = endSession; exports.endSession = endSession;
// ui.messages[ui.messages.findIndex(m => m.id === message.id)].content.push(message);

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 = "./src/assets/tray/"; const TRAY_PATH = path.join(__dirname, "assets/tray");
/* Current state of recording, playback, and ws connection */ /* Current state of recording, playback, and ws connection */
const state = { const state = {
@ -19,25 +19,24 @@ 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( tray.setImage(path.join(
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(TRAY_PATH + "000" + ".png"); tray = new Tray(path.join(TRAY_PATH, "000.png"));
} }
exports.initTray = initTray; exports.initTray = initTray;

View file

@ -21,28 +21,11 @@ function updateWindowPosition()
} }
function createPreviewWin(_e, content) function onNav(_e, cmd)
{ {
console.log("Creating preview window"); cmd === "close" ? win.close() : win.minimize();
const preview = new BrowserWindow({
width: 500,
height: 300,
x: null,
y: null,
resizable: true,
frame: false,
webPreferences: { preload: path.join(__dirname, "/render/preload.js") }
});
preview.loadFile(path.join(__dirname, "preview.html"));
preview.webContents.on("did-finish-load", () => {
preview.webContents.send("preview-init", content);
});
} }
function createWin() function createWin()
{ {
if (win) return; if (win) return;
@ -56,11 +39,11 @@ function createWin()
x: pos.x, x: pos.x,
y: pos.y, y: pos.y,
resizable: true, resizable: true,
backgroundColor: "#EBEBEB",
frame: false, frame: false,
minWidth: 350, minWidth: 350,
minHeight: 500, minHeight: 500,
webPreferences: { preload: path.join(__dirname, "/render/preload.js") } backgroundColor: "#EBEBEB",
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
@ -88,10 +71,10 @@ function createWin()
} }
}); });
win.webContents.send("set-account", store.get("account")); win.webContents.send("account", store.get("account"));
}); });
} }
ipcMain.on("preview", createPreviewWin); ipcMain.on("nav", onNav);
exports.createWin = createWin; exports.createWin = createWin;

785
yarn.lock
View file

@ -1,785 +0,0 @@
# 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"