Merge branch 'ipc_refactor' into 'main'

refactor main and rendered ipc

See merge request crimata/electron-app!1
This commit is contained in:
Enrique Hernandez 2021-06-01 12:13:04 +00:00
commit 87dce82c53
28 changed files with 758 additions and 211 deletions

4
.env Normal file
View file

@ -0,0 +1,4 @@
BUSINESS_URL="http://localhost:3000"
PLATFORM_URL="ws://127.0.0.1:8760"

1
.gitignore vendored
View file

@ -9,6 +9,7 @@ crash.log
# local env files # local env files
.env.local .env.local
.env.*.local .env.*.local
.env
# Log files # Log files
npm-debug.log* npm-debug.log*

View file

@ -22,8 +22,11 @@
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/ws": "^7.2.7", "@types/ws": "^7.2.7",
"animejs": "^3.2.0", "animejs": "^3.2.0",
"axios": "^0.21.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"dotenv": "^10.0.0",
"electron-is-dev": "^2.0.0", "electron-is-dev": "^2.0.0",
"electron-store": "^8.0.0",
"electron-updater": "^4.3.8", "electron-updater": "^4.3.8",
"mitt": "^2.1.0", "mitt": "^2.1.0",
"naudiodon": "^2.3.2", "naudiodon": "^2.3.2",
@ -36,6 +39,7 @@
"ws": "^7.3.1" "ws": "^7.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/axios": "^0.14.0",
"@types/electron-devtools-installer": "^2.2.0", "@types/electron-devtools-installer": "^2.2.0",
"@types/jest": "^24.0.19", "@types/jest": "^24.0.19",
"@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/eslint-plugin": "^2.33.0",

View file

@ -35,12 +35,19 @@
import { defineComponent, onMounted, onUnmounted, ref } from "vue"; import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import { IpcRendererEvent } from "electron"; import { IpcRendererEvent } from "electron";
import { useIpc } from "@/modules/ipc"; import { useIpc } from "@/modules/ipc";
import { useProfile } from "@/modules/auth"
import { invokeProfile } from "@/ipcRend/account";
import { postInitSession, postMount } from "@/ipcRend/session";
import Splash from "@/components/splash.vue"; import Splash from "@/components/splash.vue";
import Messenger from "@/components/messenger.vue"; import Messenger from "@/components/messenger.vue";
import Login from "@/components/login.vue"; import Login from "@/components/login.vue";
import { Profile } from "@/types";
export default defineComponent({ export default defineComponent({
components: { components: {
Splash, Splash,
Messenger, Messenger,
@ -48,39 +55,38 @@ export default defineComponent({
}, },
setup() { setup() {
const { post } = useIpc();
const { post, invoke } = useIpc();
const { profile, setProfile, clearProfile } = useProfile();
// Whether browser has received user info yet. // Whether browser has received user info yet.
const ready = ref(false); const ready = ref(false);
// Information about current user.
const profile = ref(false);
// New messages that browser missed while closed. // New messages that browser missed while closed.
const newMessages = ref([]) const newMessages = ref([]);
// Receive updated information about the session. // Receive updated information about the session.
const updateState = (_event: IpcRendererEvent, payload: any) => { const updateState = (_event: IpcRendererEvent, payload: any) => {
console.log('YAYAYAYAYA');
console.log("APP:Received updated profile and new messages: \n" + console.log("APP:Received updated profile and new messages: \n" +
` profile: ${payload.message.profile}\n` + ` profile: ${payload.message.profile}\n` +
` new: ${payload.message.newMessages}`) ` new: ${payload.message.newMessages}`);
if (payload.message.profile) { if (payload.message.profile) {
console.log(`APP:Logged-in, showing Messenger View.`) console.log(`APP:Logged-in, showing Messenger View.`);
} else { } else {
console.log(`APP:Logged-out, showing Login View.`) console.log(`APP:Logged-out, showing Login View.`);
} }
// Set profile and newMessages.
profile.value = payload.message.profile;
newMessages.value = payload.message.newMessages; newMessages.value = payload.message.newMessages;
ready.value = true; ready.value = true;
} }
onMounted(() => { onMounted(async () => {
console.log("APP:mounted.") console.log("APP:mounted.");
window.ipcRenderer.on("update-state", updateState) window.ipcRenderer.on("update-state", updateState)
window.ipcRenderer.on("update_available", () => { window.ipcRenderer.on("update_available", () => {
console.log('testing auto update'); console.log('testing auto update');
@ -89,18 +95,35 @@ export default defineComponent({
window.ipcRenderer.on("update_downloaded", () => { window.ipcRenderer.on("update_downloaded", () => {
console.log('testing update download'); console.log('testing update download');
}) })
post("app-mounted", "")
postMount();
try {
const profile = await invokeProfile() as Profile;
setProfile(profile);
} catch(e) {
console.log('[AUTH]', e);
clearProfile();
} finally {
ready.value = true;
if (profile.value.crimataId) {
// start session
postInitSession(profile.value.crimataId);
}
}
}); });
onUnmounted(() => { onUnmounted(() => {
window.ipcRenderer.removeAllListeners("update-state") window.ipcRenderer.removeAllListeners("update-state");
}) })
return { return {
ready, ready,
profile,
post, post,
newMessages newMessages,
profile
} }
} }
}) })

36
src/api/account.ts Normal file
View file

@ -0,0 +1,36 @@
import { useHttp } from "@/modules/http";
import axios from "axios";
const { post } = useHttp();
export const submit =
async (email: string, password: string) => (
await post('/account/login', {
email,
password
})
)
export const logout =
async (): Promise<null | Error> => (await post('/account/logout'));
export const fetchProfile = async(email: string, token: string) => (
await axios({
url: "http://127.0.0.1:3000/api/account/profile",
headers: {
Cookie: `jwt=${token}`
},
method: 'GET',
data: {
email,
}
})
)

View file

@ -7,6 +7,7 @@
import { initApp } from './background/init'; import { initApp } from './background/init';
import { protocol } from "electron"; import { protocol } from "electron";
require('dotenv').config()
// Scheme must be registered before the app is ready // Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([ protocol.registerSchemesAsPrivileged([

View file

@ -2,9 +2,7 @@
"use strict"; "use strict";
import { ipcMain } from "electron";
import { backgroundMitt } from '@/modules/emitter'; import { backgroundMitt } from '@/modules/emitter';
const portAudio = require('naudiodon'); const portAudio = require('naudiodon');
// Audio in and out stream objects. // Audio in and out stream objects.
@ -26,27 +24,22 @@ const audioOptions = {
closeOnError: false, closeOnError: false,
} }
// Toggles record to true to begin capturing chunks. export const toggleRecord = (): void => { record = !record };
const onRecordingStart = (_event: any, _payload: any) => {
console.log("AUDIO: Beginning audio capture.")
record = true;
}
// Returns recorded audio to frontend and sets record to false.
const onRecordingEnd = async (_event: any, payload: any) => {
console.log("AUDIO:Sending audio to browser.")
return new Promise((resolve, reject) => { export const fetchAudioInput = (): Promise<Error | string> => (
new Promise((resolve, reject) => {
try { try {
resolve(audioContainer.input); resolve(audioContainer.input);
record = false; toggleRecord();
} catch (e) { } catch (e) {
reject() reject(new Error('Failed to fetch the audio.'))
} }
}); })
}; )
// Main audio function run by run.ts module. // Main audio function run by run.ts module.
@ -86,15 +79,6 @@ export function initAudioIO(): void {
ao.start(); ao.start();
} }
// Listen to record.
console.log("AUDIO:Adding recording listeners.")
ipcMain.removeAllListeners("start-recording");
ipcMain.on("start-recording", onRecordingStart);
ipcMain.removeHandler("stop-recording");
ipcMain.handle("stop-recording", onRecordingEnd);
} }

View file

@ -3,9 +3,10 @@
import { app, dialog } from "electron"; import { app, dialog } from "electron";
import { createWindow } from './window'; import { createWindow } from './window';
import { initSession } from './session'; import { stopStream } from './audio';
import { initAudioIO, stopStream } from './audio';
import { backgroundMitt } from '@/modules/emitter'; import { backgroundMitt } from '@/modules/emitter';
import useIpc from "@/background/ipc/index";
const { autoUpdater } = require('electron-updater');
let win: boolean; let win: boolean;
@ -15,7 +16,6 @@ backgroundMitt.on('window-active', (state: boolean) => {
}); });
// Auto updating. // Auto updating.
const { autoUpdater } = require('electron-updater')
autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' } autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' }
autoUpdater.on('update-available', (info: any) => { autoUpdater.on('update-available', (info: any) => {
@ -38,19 +38,18 @@ autoUpdater.on('update-downloaded', (info: any) => {
}) })
// Run when electron app is initialized. // Run when electron app is initialized.
async function main(dev: boolean) { async function main(): Promise<void> {
console.log("MAIN:Initializing Electron App.")
console.log("MAIN:Initializing Electron App.");
useIpc();
// Must wait til window is created. // Must wait til window is created.
await createWindow(); await createWindow();
// Instantiate socket session with crimata-platorm.
initSession(dev);
// Begin audio stream.
initAudioIO();
} }
// Root function of app. // Root function of app.
@ -58,8 +57,8 @@ export function initApp(dev: boolean): void {
// On initial startup. // On initial startup.
app.on("ready", () => { app.on("ready", () => {
if (!dev) autoUpdater.checkForUpdates() // autoUpdater.checkForUpdates()
main(dev) main();
}); });
// Must keep to ensure app doesn't quit on close. // Must keep to ensure app doesn't quit on close.

View file

@ -0,0 +1,117 @@
"use strict";
import { Profile } from "@/types";
import { submit, fetchProfile, logout } from "@/api/account";
import { ipcMain, IpcMainInvokeEvent } from "electron";
import { store } from "@/background/store";
const parseAuthRes = (authRes: any) => {
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
const profile = authRes.data as Profile;
return {
token,
profile
}
};
const onProfile = async (
_event: IpcMainInvokeEvent,
_payload: null
): Promise<Profile | Error> => (
new Promise(async (resolve, reject) => {
console.log('[IPC]: user-profile');
// get jwt token and crimataId from store
const token = store.get('key');
const crimataId = store.get('crimataId');
// authenticate and fetch profile
try {
const res = await fetchProfile(
crimataId,
token
);
const parsed = parseAuthRes(res);
resolve(parsed.profile);
} catch(e) {
reject(new Error('Failed to fetch profile.'));
}
})
)
const onLogin = async (
_event: IpcMainInvokeEvent,
payload: string
): Promise<Profile | Error> => (
new Promise(async (resolve, reject) => {
console.log('[IPC]: user-login');
const account = JSON.parse(payload);
if ( account.password && account.email ) {
try {
const res = await submit(account.email, account.password);
const parsed = parseAuthRes(res);
// save jwt token and profile
store.set('key', parsed.token);
store.set('crimataId', parsed.profile.crimataId);
// return profile to renderer
resolve(parsed.profile);
} catch(e) {
console.log('[API]', e.response);
reject(new Error('Failed to authenticate'));
}
}
})
)
const onLogout = async (
_event: IpcMainInvokeEvent,
_payload: null
): Promise<void> => (
new Promise(async (resolve, reject) => {
console.log('[IPC]: user-logout');
try {
// post logout to backend
await logout();
// remove key and crimataId
store.delete('key');
store.delete('crimataId');
// TODO: kill crimata platform session
resolve();
} catch(e) {
reject(new Error('Failed to logout. Please try again.'));
}
})
)
export default function useAccountListeners(): void {
ipcMain.removeHandler("user-profile");
ipcMain.handle("user-profile", onProfile);
ipcMain.removeHandler("user-login");
ipcMain.handle("user-login", onLogin);
ipcMain.removeHandler("user-logout");
ipcMain.handle("user-logout", onLogout);
}

View file

@ -0,0 +1,40 @@
"use strict";
import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
import { fetchAudioInput, toggleRecord } from "@/background/audio";
// Toggles record to true to begin capturing chunks.
const onRecordingStart = (
_event: IpcMainEvent,
_payload: null
): void => {
console.log('[IPC]: start-recording');
toggleRecord();
};
// Returns recorded audio to frontend and sets record to false.
const onRecordingStop = async (
_event: IpcMainInvokeEvent,
_payload: null
): Promise<Error | string> => {
console.log('[IPC]: stop-recording');
return await fetchAudioInput()
};
export default function useAudioListeners(): void {
ipcMain.removeAllListeners("start-recording");
ipcMain.on("start-recording", onRecordingStart);
ipcMain.removeHandler("stop-recording");
ipcMain.handle("stop-recording", onRecordingStop);
}

View file

@ -0,0 +1,17 @@
"use strict";
import useAccountListeners from "./account";
import useSessionListeners from "./session";
import useAudioListeners from "./audio";
export default function useIpc(): void {
useAccountListeners();
useSessionListeners();
useAudioListeners();
}

View file

@ -0,0 +1,62 @@
"use strict";
import { initSession, emitNewMessages, sendMessage } from '@/background/session';
import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
import { initAudioIO } from "@/background/audio";
import { ClientMessage } from "@/types";
// Instantiate socket session with crimata-platorm.
const onSessionInit = (
_event: IpcMainInvokeEvent,
cid: string
): void => {
console.log('[IPC]: init-session');
initSession(cid);
initAudioIO();
}
const onAppMounted = (
_event: IpcMainInvokeEvent,
_payload: null
): void => {
console.log('[IPC]: app-mounted');
emitNewMessages()
};
// Handle messages from window/client.
const onClientMessage = (
_event: IpcMainEvent,
payload: ClientMessage
): void => {
console.log('[IPC]: client-message');
sendMessage(payload);
}
export default function useSessionListeners(): void {
console.log('[IPC]: Init session listeners.');
// Attach listeners for frontend.
ipcMain.removeAllListeners("client-message");
ipcMain.on("client-message", onClientMessage);
// Attack browser window init listener.
ipcMain.removeAllListeners("app-mounted");
ipcMain.on("app-mounted", onAppMounted);
ipcMain.removeAllListeners("init-session");
ipcMain.on("init-session", onSessionInit);
}

View file

@ -1,82 +1,35 @@
/* /*
* Creates a websocket session with Crimata Servers. * Creates a websocket session with Crimata Servers.
* *
* Connects to Servers and attempts key authentication. Server will respond * Connects to Servers and attempts key authentication. Server will respond
* with key and user profile. We send the profile to the browser. We also * with key and user profile. We send the profile to the browser. We also
* resend this information on new broser window. We then serve as a * resend this information on new broser window. We then serve as a
* communication interface between the window and the servers. It will * communication interface between the window and the servers. It will
* automatically try to reconnect on websocket disconnect. * automatically try to reconnect on websocket disconnect.
*/ */
import { backgroundMitt } from "@/modules/emitter"; import { backgroundMitt } from "@/modules/emitter";
import { ipcEmit, loadState, saveToJson } from './helpers'; import { ipcEmit, loadState } from './helpers';
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
import useWebSockets from "@/modules/websockets"; import useWebSockets from "./websockets";
import { play } from "./audio"; import { play } from "./audio";
import { renderMessage } from "@/modules/message"; import { renderMessage } from "@/modules/message";
import { AuthProtocol, SessionState, Profile } from "@/types"; import { SessionState } from "@/types";
let win = true; let win = true;
// Info saved to json on quit (key, newMessages). // Info saved to json on quit (key, newMessages).
let state: SessionState; let state: SessionState;
// Profile of current user.
let profile: Profile | boolean;
// Called when server sends auth message.
const updateState = (res: AuthProtocol) => {
console.log("SESS:Auth message received: \n" +
` key: ${res.key}\n` +
` alias: ${res.profile}`)
if (state) {
// Update key.
state.key = res.key;
// Update the user profile.
profile = res.profile;
// Send upated profile to frontend.
console.log("SESS:Sending updated user profile to browser.")
ipcEmit("update-state", {
profile: profile,
newMessages: state.newMessages
})
// Save the updated state to json.
console.log("SESS:Saving session state.")
saveToJson("session.json", state)
}
}
// Send state on new window.
const onNewBrowserWindow = (_event: IpcMainInvokeEvent, _payload: any) => {
if (typeof profile !== 'undefined') {
console.log("SESS:Sending user profile to browser.")
ipcEmit("update-state", {
profile: profile,
newMessages: state.newMessages
})
}
}
// Calls appropriate endpoint for a server message. // Calls appropriate endpoint for a server message.
const onMessage = (data: string) => { const onMessage = (data: string): void => {
let message = JSON.parse(data) let message = JSON.parse(data);
console.log('received new message', message);
// AuthProtocol message.
if (message.hasOwnProperty("key")) {
updateState(message)
}
// Standard message. // Standard message.
else if (message.content) { if (message.content) {
// Convert to render message // Convert to render message
message = renderMessage( message = renderMessage(
@ -93,7 +46,7 @@ const onMessage = (data: string) => {
} }
ipcEmit("render-message", message) ipcEmit("render-message", message)
} }
else { else {
console.log("SESS:No window: saving message.") console.log("SESS:No window: saving message.")
state.newMessages.push(message); state.newMessages.push(message);
@ -108,57 +61,40 @@ const onMessage = (data: string) => {
}; };
// When socket connects, we update state.
const onOpen = () => {
console.log(`SESS:Sending key: ${state.key}`)
if (state) {
sendMessage({
"key": state.key,
"usr": false,
"pwd": false
})
}
}
// Websockets module. // Websockets module.
const { createSocket, sendMessage } = useWebSockets(onMessage, onOpen); const { createSocket, send } = useWebSockets(onMessage);
// Handle messages from window/client.
const onClientMessage = (_event: IpcMainEvent, payload: any) => {
console.log("New client message")
const success = sendMessage(payload) export const emitNewMessages = (): void => {
if (state) {
if (!success) { ipcEmit("update-state", {
console.log("Unable to send message: ", payload) newMessages: state.newMessages,
});
} }
} }
export const sendMessage = (payload: Record<string, any>): void => {
try {
send(payload)
} catch(e) {
console.log("Unable to send message: ", payload);
}
}
// Call this to initialize session with Crimata servers. // Call this to initialize session with Crimata servers.
export const initSession = (dev: boolean) => { export const initSession = (cid: string): void => {
console.log("SESS:Creating new session.") console.log("SESS:Creating new session.")
// Load Json or createState. // Load Json or createState.
state = loadState("session.json") state = loadState("session.json");
console.log("SESS:State loaded: \n" +
` key: ${state.key}\n` +
` new: ${state.newMessages}`)
// Open socket connection. // Open socket connection.
let url = "ws://crimata.com:8760"; createSocket();
if (dev) url = "ws://localhost:8760";
createSocket(url)
// Attack browser window init listener.
ipcMain.removeAllListeners("app-mounted")
ipcMain.on("app-mounted", onNewBrowserWindow);
// Attach listeners for frontend.
ipcMain.removeAllListeners("client-message")
ipcMain.on("client-message", onClientMessage);
// Keep win up-to-date. // Keep win up-to-date.
backgroundMitt.on('window-active', (state: boolean) => { backgroundMitt.on('window-active', (state: boolean) => {

16
src/background/store.ts Normal file
View file

@ -0,0 +1,16 @@
const Store = require('electron-store');
const schema = {
key: {
type: 'string',
},
crimataId: {
type: 'string'
}
};
export const store = new Store({
schema,
encryptionKey: "super user test"
});

View file

@ -1,13 +1,16 @@
"use strict"; "use strict";
import WebSocket from 'ws'; import WebSocket from 'ws';
import { ipcMain } from "electron";
let socket: WebSocket; let socket: WebSocket;
const socketUrl = process.env.PLATFORM_URL;
// Run every time we want to connect to backend. // Run every time we want to connect to backend.
export default function useWebSockets(receiveCallback: (s: string) => any, openCallback: () => any) { export default function useWebSockets(
receiveCallback: (s: string) => void,
openCallback?: () => void
) {
// Returns bool (sucess or fail). // Returns bool (sucess or fail).
const sendMessage = (data: any) => { const sendMessage = (data: any) => {
@ -24,11 +27,22 @@ export default function useWebSockets(receiveCallback: (s: string) => any, openC
} }
const onOpen = (event: WebSocket.OpenEvent) => { const send = async (data: Record<string, any>): Promise<boolean> => (
new Promise((resolve, reject) => {
if (socket.readyState !== 1) {
reject(false);
}
socket.send(JSON.stringify(data))
resolve(true);
}))
const onOpen = (_event: WebSocket.OpenEvent) => {
console.log("WS:Connected to WS Server!"); console.log("WS:Connected to WS Server!");
openCallback() if (openCallback) openCallback();
} }
@ -40,7 +54,7 @@ export default function useWebSockets(receiveCallback: (s: string) => any, openC
} }
const onClose = (event: WebSocket.CloseEvent) => { const onClose = (_event: WebSocket.CloseEvent) => {
console.log("WS:Socket closed normally.") console.log("WS:Socket closed normally.")
} }
@ -52,9 +66,9 @@ export default function useWebSockets(receiveCallback: (s: string) => any, openC
setTimeout(createSocket, 1000) setTimeout(createSocket, 1000)
} }
const createSocket = (url: string) => { const createSocket = () => {
if (socketUrl)
socket = new WebSocket(url) socket = new WebSocket(socketUrl)
// Add listeners. // Add listeners.
socket.addEventListener("open", onOpen) socket.addEventListener("open", onOpen)
@ -67,7 +81,8 @@ export default function useWebSockets(receiveCallback: (s: string) => any, openC
return { return {
createSocket, createSocket,
sendMessage sendMessage,
send
} }
} }

View file

@ -57,7 +57,6 @@ const saveWindowState = () => {
// Do this on window mount. // Do this on window mount.
const onWindowMount = (): void => { const onWindowMount = (): void => {
console.log("BW:Adding window listeners. ")
// Must tell initApp that window exists. // Must tell initApp that window exists.
backgroundMitt.emit('window-active', true); backgroundMitt.emit('window-active', true);
@ -70,12 +69,10 @@ const onWindowMount = (): void => {
backgroundMitt.removeAllListeners("ipc-renderer") backgroundMitt.removeAllListeners("ipc-renderer")
backgroundMitt.on("ipc-renderer", renderMessage); backgroundMitt.on("ipc-renderer", renderMessage);
console.log("BW:Listeners created.")
} }
// Do this on window dismount (close). // Do this on window dismount (close).
const onWindowDismount = (): void => { const onWindowDismount = (): void => {
console.log("BW:Window closed.")
win = null; win = null;
backgroundMitt.emit('window-active', false); backgroundMitt.emit('window-active', false);
} }
@ -89,7 +86,6 @@ export async function createWindow(): Promise<void> {
// Load the saved window state. // Load the saved window state.
winState = loadWinState("window.json") winState = loadWinState("window.json")
console.log(`BW:Creating window [${winState.width}, ${winState.height}].`)
// Define the browser window. // Define the browser window.
win = new BrowserWindow({ win = new BrowserWindow({

View file

@ -4,6 +4,8 @@ import { useIpc } from '@/modules/ipc';
import { onMounted, onUnmounted, ref, Ref } from "vue"; import { onMounted, onUnmounted, ref, Ref } from "vue";
import keyboardNameMap from "../keyBoardMaps/keyboardNameMap"; import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
import { renderMessage, clientMessage } from '@/modules/message'; import { renderMessage, clientMessage } from '@/modules/message';
import { postMessage } from "@/ipcRend/session";
import { invokeStopRecord } from "@/ipcRend/audio";
function showRecIcon () { function showRecIcon () {
@ -13,7 +15,7 @@ function showRecIcon () {
opacity: [0, 0.75], opacity: [0, 0.75],
scale: [0.0, 1], scale: [0.0, 1],
duration: 250, duration: 250,
easing: 'linear', easing: 'linear',
}) })
} }
@ -25,7 +27,7 @@ function hideRecIcon () {
opacity: [0.75, 0], opacity: [0.75, 0],
scale: [1, 0], scale: [1, 0],
duration: 250, duration: 250,
easing: 'linear', easing: 'linear',
}) })
} }
@ -50,7 +52,7 @@ export default function useAudioInputController (typing: Ref) {
if (cmd == "SPACE" && !recording.value && !typing.value) { if (cmd == "SPACE" && !recording.value && !typing.value) {
console.log("INPT:Starting record.") console.log("INPT:Starting record.")
post("start-recording", ""); post("start-recording", null);
showRecIcon() showRecIcon()
recording.value = true; recording.value = true;
@ -67,9 +69,9 @@ export default function useAudioInputController (typing: Ref) {
// Create a message. // Create a message.
const message = renderMessage( const message = renderMessage(
"", "",
"", "",
"", "",
"sf" "sf"
) )
@ -78,14 +80,19 @@ export default function useAudioInputController (typing: Ref) {
// Stop recording and get audio from recorder. // Stop recording and get audio from recorder.
console.log("INPT:Stopping record.") console.log("INPT:Stopping record.")
const audio = await invoke("stop-recording", ""); try {
const audio = await invokeStopRecord() as string;
// Send message to the backend for processing. // Send message to the backend for processing.
const clientM = clientMessage("", audio, message.uid); const clientM = clientMessage("", audio, message.uid);
post('client-message', clientM); postMessage(clientM);
} catch(e) {
console.log('Failed to fetch audio.')
} finally {
hideRecIcon();
recording.value = false;
}
hideRecIcon()
recording.value = false;
} }

View file

@ -4,6 +4,8 @@ import { useIpc } from '@/modules/ipc';
import { Ref, ref, watch, onMounted, onUnmounted } from "vue"; import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
import keyboardNameMap from "../keyBoardMaps/keyboardNameMap"; import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
import { clientMessage, renderMessage } from '@/modules/message'; import { clientMessage, renderMessage } from '@/modules/message';
import { postMessage } from "@/ipcRend/session";
//---Animations----------------------------------------------- //---Animations-----------------------------------------------
@ -19,7 +21,7 @@ function showTextInput () {
translateX: [t1, t2], translateX: [t1, t2],
scale: [0.3, 1], scale: [0.3, 1],
duration: 500, duration: 500,
easing: 'easeOutExpo', easing: 'easeOutExpo',
}) })
} }
@ -33,7 +35,7 @@ function hideTextInput() {
translateX: t, translateX: t,
scale: 0.3, scale: 0.3,
duration: 500, duration: 500,
easing: 'easeOutExpo', easing: 'easeOutExpo',
}) })
} }
@ -45,7 +47,7 @@ function switchSide(currentSide: string) {
targets: '#textInput', targets: '#textInput',
translateX: t, translateX: t,
duration: 500, duration: 500,
easing: 'easeOutExpo', easing: 'easeOutExpo',
}) })
} }
@ -75,7 +77,7 @@ export default function useTextInputController(elementX: Ref) {
textInput.value = ""; textInput.value = "";
textInput.blur(); textInput.blur();
} }
hideTextInput() hideTextInput()
firstKey = true; firstKey = true;
typing.value = false; typing.value = false;
@ -87,9 +89,9 @@ export default function useTextInputController(elementX: Ref) {
// Create the message. // Create the message.
const message = renderMessage( const message = renderMessage(
textInput.value, textInput.value,
false, false,
"", "",
"sf" "sf"
) )
@ -97,7 +99,7 @@ export default function useTextInputController(elementX: Ref) {
// Send it to the backend for processing. // Send it to the backend for processing.
const clientM = clientMessage(textInput.value, false, message.uid); const clientM = clientMessage(textInput.value, false, message.uid);
post('client-message', clientM); postMessage(clientM);
clearInput() clearInput()
} }
@ -115,9 +117,9 @@ export default function useTextInputController(elementX: Ref) {
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
const key = keyboardNameMap[e.keyCode] const key = keyboardNameMap[e.keyCode]
if (textInput) { if (textInput) {
// Only runs on firstKey. // Only runs on firstKey.
if (firstKey) { if (firstKey) {
@ -165,7 +167,7 @@ export default function useTextInputController(elementX: Ref) {
switchSide(side) switchSide(side)
side = "left" side = "left"
} }
} }
else { else {
if (winW - elementX > 230) { if (winW - elementX > 230) {
@ -173,7 +175,7 @@ export default function useTextInputController(elementX: Ref) {
side = "right" side = "right"
} }
} }
}); });
onMounted(() => { onMounted(() => {

View file

@ -25,7 +25,7 @@
<!-- submit button; position: fixed --> <!-- submit button; position: fixed -->
<button class="submitButton button" type="submit">Submit</button> <button class="submitButton button" type="submit">Submit</button>
</form> </form>
<!-- back to login button: position: fixed --> <!-- back to login button: position: fixed -->
@ -37,21 +37,34 @@
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import { useIpc } from "@/modules/ipc"; import { useIpc } from "@/modules/ipc";
import { authRequest } from '@/modules/message'; import { authRequest } from '@/modules/message';
import { useProfile } from '@/modules/auth';
import { invokeLogin } from "@/ipcRend/account";
import { Profile } from "@/types";
export default defineComponent({ export default defineComponent({
name: "Login", name: "Login",
setup() { setup() {
const { post } = useIpc(); const { setProfile } = useProfile();
const usr = ref(""); const usr = ref("");
const pwd = ref(""); const pwd = ref("");
// Submit login credentials to the backend. // Submit login credentials to the backend.
const submitForm = () => { const submitForm = async () => {
console.log(`Submitting login form: ${usr.value}, ${pwd.value}`)
post("client-message", authRequest(false, usr.value, pwd.value)) try {
const profile = await invokeLogin({
email: usr.value,
password: pwd.value
}) as Profile;
setProfile(profile);
} catch(e) {
}
} }
return { return {

View file

@ -6,9 +6,9 @@
<!-- List of message bubbles. --> <!-- List of message bubbles. -->
<div id="messenger"> <div id="messenger">
<Message <Message
v-for="message in messages" v-for="message in messages"
:message="message[1]" :message="message[1]"
:key="message[0]" :key="message[0]"
/> />
</div> </div>
@ -48,25 +48,24 @@ export default defineComponent({
// Load the message history. // Load the message history.
if (crimataId === window.localStorage.getItem("last_usr")) { if (crimataId === window.localStorage.getItem("last_usr")) {
prepMessageView(props.newMessages) prepMessageView(props.newMessages);
} else { } else {
messages.value.clear() messages.value.clear();
} }
// New content listeners. // New content listeners.
emitter.on('self-message', (message) => updateMessageView(message)); emitter.on('self-message', (message) => updateMessageView(message));
window.ipcRenderer.on("render-message", (_e: any, payload: any) => { window.ipcRenderer.on("render-message", (_e: any, payload: any) => {
updateMessageView(payload.message) updateMessageView(payload.message);
}); });
// Save the usr for next time. // Save the usr for next time.
window.localStorage.setItem("last_usr", crimataId) window.localStorage.setItem("last_usr", crimataId);
}); });
onUnmounted(() => { onUnmounted(() => {
window.ipcRenderer.removeAllListeners("render-message"); window.ipcRenderer.removeAllListeners("render-message");
window.ipcRenderer.removeAllListeners("annotate-message");
emitter.all.clear(); emitter.all.clear();
}); });

View file

@ -30,6 +30,8 @@
import { defineComponent, ref } from "vue"; import { defineComponent, ref } from "vue";
import { useIpc } from "@/modules/ipc"; import { useIpc } from "@/modules/ipc";
import { logoutRequest } from '@/modules/message'; import { logoutRequest } from '@/modules/message';
import { useProfile } from "@/modules/auth"
import { invokeLogout } from "@/ipcRend/account";
export default defineComponent({ export default defineComponent({
name: "Settings", name: "Settings",
@ -37,7 +39,9 @@
setup() { setup() {
const toggleSettings = ref(false); const toggleSettings = ref(false);
const { post } = useIpc(); const { post, invoke } = useIpc();
const { clearProfile } = useProfile();
// Listen for escape key to close settings. // Listen for escape key to close settings.
const onEscape = (e: any) => { const onEscape = (e: any) => {
@ -54,9 +58,17 @@
} }
// We ask server to log us out. // We ask server to log us out.
const onLogout = () => { const onLogout = async () => {
console.log("Submitting logout request.")
post("client-message", logoutRequest()) console.log("Submitting logout request.");
try {
clearProfile();
await invokeLogout();
} catch(e) {
console.log('error')
}
} }
return { return {

27
src/ipcRend/account.ts Normal file
View file

@ -0,0 +1,27 @@
import { useIpc } from "@/modules/ipc";
import { Profile } from "@/types";
const { invoke } = useIpc();
interface LoginPayload {
email: string;
password: string;
}
export const invokeProfile = async (): Promise<Profile | Error> => (
await invoke('user-profile', null)
);
export const invokeLogin = async (
payload: LoginPayload
): Promise<Profile | Error> => (
await invoke('user-login', JSON.stringify(payload))
);
export const invokeLogout = async (): Promise<void> => (
await invoke("user-logout", null)
);

15
src/ipcRend/audio.ts Normal file
View file

@ -0,0 +1,15 @@
import { useIpc } from "@/modules/ipc";
const { post, invoke } = useIpc();
export const postStartRecord = (): void => (
post("start-recording", null)
);
export const invokeStopRecord = async (): Promise<string | Error> => (
await invoke("stop-recording", null)
);

21
src/ipcRend/session.ts Normal file
View file

@ -0,0 +1,21 @@
import { useIpc } from "@/modules/ipc";
import { ClientMessage } from "@/types";
const { post } = useIpc();
export const postMount = (): void => (
post("app-mounted", null)
);
export const postInitSession = (cid: string): void => (
post("init-session", cid)
);
export const postMessage = (payload: ClientMessage): void => (
post('client-message', payload)
);

23
src/modules/auth.ts Normal file
View file

@ -0,0 +1,23 @@
import { ref } from "vue";
import { Profile } from "@/types";
const profile = ref();
export const useProfile = () => {
const setProfile = (payload: Profile) => {
profile.value = payload;
}
const clearProfile = () => {
profile.value = null;
}
return {
setProfile,
clearProfile,
profile
}
}

54
src/modules/http.ts Normal file
View file

@ -0,0 +1,54 @@
import axios, { AxiosRequestConfig } from 'axios';
const preFix = '/api';
const baseURL = process.env.BUSSINESS_URL + preFix;
interface Request {
endpoint: string;
query?: Record<string, any>;
config?: Record<string, any>;
}
const makeQuery = (reqQuery: Record<string, any>) => {
let result = '';
result = '?' + Object.entries(reqQuery)
.map(([ key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
return result;
};
export const useHttp = () => {
const api = axios.create({
baseURL,
withCredentials: true,
});
const post = async (endpoint: string, payload?: Record<string, any>): Promise<any> => (
await api.post(endpoint, payload)
)
const get = async (req: Request) => {
if (req.query) {
req.endpoint += makeQuery(req.query);
}
const res = await api.get(req.endpoint, req.config);
return res;
};
return {
get, post
}
}

View file

@ -19,7 +19,7 @@ export interface ClientMessage {
} }
export interface ClientRequest { export interface ClientRequest {
intent: string; intent: string;
params: object; params: object;
epic: string | boolean; epic: string | boolean;
confidence: number; confidence: number;
@ -50,8 +50,10 @@ export interface Profile {
} }
export interface AuthProtocol { export interface AuthProtocol {
key: boolean | string; token: null | string;
profile: boolean | Profile; profile: null | Profile;
password?: string;
email?: string;
} }
export interface LogoutRequest { export interface LogoutRequest {
@ -71,4 +73,4 @@ export interface StandardMessage {
}; };
context: string; context: string;
modifier: string; modifier: string;
} }

123
yarn.lock
View file

@ -1279,6 +1279,13 @@
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
"@types/axios@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46"
integrity sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=
dependencies:
axios "*"
"@types/babel__core@^7.1.0": "@types/babel__core@^7.1.0":
version "7.1.12" version "7.1.12"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
@ -2348,6 +2355,13 @@ ajv-errors@^1.0.0:
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
ajv-formats@^2.0.2:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.0.tgz#96eaf83e38d32108b66d82a9cb0cfa24886cdfeb"
integrity sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==
dependencies:
ajv "^8.0.0"
ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
version "3.5.2" version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
@ -2363,6 +2377,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.3, ajv
json-schema-traverse "^0.4.1" json-schema-traverse "^0.4.1"
uri-js "^4.2.2" uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.1.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b"
integrity sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
alphanum-sort@^1.0.0: alphanum-sort@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@ -2710,6 +2734,11 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
atomically@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
autoprefixer@^9.8.6: autoprefixer@^9.8.6:
version "9.8.6" version "9.8.6"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
@ -2733,6 +2762,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios@*, axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0" version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@ -3946,6 +3982,22 @@ condense-newlines@^0.2.1:
is-whitespace "^0.3.0" is-whitespace "^0.3.0"
kind-of "^3.0.2" kind-of "^3.0.2"
conf@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/conf/-/conf-10.0.1.tgz#038093e5cbddc0e59bc14f63382c4ce732a4781d"
integrity sha512-QClEoNcruwBL84QgMEPHibL3ERxWIrRKhbjJKG1VsFBadm5QpS0jsu4QjY/maxUvhyAKXeyrs+ws+lC6PajnEg==
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, config-chain@^1.1.12: config-chain@^1.1.11, config-chain@^1.1.12:
version "1.1.12" version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
@ -4468,6 +4520,13 @@ deasync@^0.1.15:
bindings "^1.5.0" bindings "^1.5.0"
node-addon-api "^1.7.1" node-addon-api "^1.7.1"
debounce-fn@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==
dependencies:
mimic-fn "^3.0.0"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -4827,11 +4886,23 @@ dot-prop@^5.2.0:
dependencies: dependencies:
is-obj "^2.0.0" is-obj "^2.0.0"
dot-prop@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083"
integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==
dependencies:
is-obj "^2.0.0"
dotenv-expand@^5.1.0: dotenv-expand@^5.1.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
dotenv@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^8.2.0: dotenv@^8.2.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
@ -4985,6 +5056,14 @@ electron-publish@22.9.1:
lazy-val "^1.0.4" lazy-val "^1.0.4"
mime "^2.4.6" mime "^2.4.6"
electron-store@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-8.0.0.tgz#81a4e687958e2dae1c5c84cc099a8148be776337"
integrity sha512-ZgRPUZkfrrjWSqxZeaxu7lEvmYf6tgl49dLMqxXGnEmliSiwv3u4rJPG+mH3fBQP9PBqgSh4TCuxHZImMMUgWg==
dependencies:
conf "^10.0.0"
type-fest "^1.0.2"
electron-to-chromium@^1.3.585: electron-to-chromium@^1.3.585:
version "1.3.589" version "1.3.589"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.589.tgz#bd26183ed8697dde6ac19acbc16a3bf33b1f8220" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.589.tgz#bd26183ed8697dde6ac19acbc16a3bf33b1f8220"
@ -5091,6 +5170,11 @@ env-paths@^2.2.0:
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
env-paths@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
errno@^0.1.3, errno@~0.1.7: errno@^0.1.3, errno@~0.1.7:
version "0.1.7" version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@ -5871,6 +5955,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
for-in@^1.0.2: for-in@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -8043,6 +8132,16 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-schema-typed@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9"
integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==
json-schema@0.2.3: json-schema@0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@ -8758,6 +8857,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-fn@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==
mimic-response@^1.0.0, mimic-response@^1.0.1: mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@ -9822,6 +9926,13 @@ pkg-dir@^4.1.0:
dependencies: dependencies:
find-up "^4.0.0" find-up "^4.0.0"
pkg-up@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
dependencies:
find-up "^3.0.0"
please-upgrade-node@^3.1.1: please-upgrade-node@^3.1.1:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
@ -10765,6 +10876,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
require-main-filename@^2.0.0: require-main-filename@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
@ -11116,7 +11232,7 @@ semver@^7.2.1, semver@^7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
semver@^7.3.4: semver@^7.3.4, semver@^7.3.5:
version "7.3.5" version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@ -12388,6 +12504,11 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^1.0.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.1.3.tgz#ea1a602e98e5a968a56a289886a52f04c686fc81"
integrity sha512-CsiQeFMR1jZEq8R+H59qe+bBevnjoV5N2WZTTdlyqxeoODQOOepN2+msQOywcieDq5sBjabKzTn3U+sfHZlMdw==
type-is@~1.6.17, type-is@~1.6.18: type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18" version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"