]> Repos - mime-chat/commitdiff
refactor main and rendered ipc
authorriqo <hernandeze2@xavier.edu>
Sat, 22 May 2021 20:02:49 +0000 (15:02 -0500)
committerEnrique Hernandez <hernandeze2@xavier.edu>
Sun, 30 May 2021 18:02:59 +0000 (14:02 -0400)
28 files changed:
.env [new file with mode: 0644]
.gitignore
package.json
src/App.vue
src/api/account.ts [new file with mode: 0644]
src/background.ts
src/background/audio.ts
src/background/init.ts
src/background/ipc/account.ts [new file with mode: 0644]
src/background/ipc/audio.ts [new file with mode: 0644]
src/background/ipc/index.ts [new file with mode: 0644]
src/background/ipc/session.ts [new file with mode: 0644]
src/background/session.ts
src/background/store.ts [new file with mode: 0644]
src/background/websockets.ts [moved from src/modules/websockets.ts with 64% similarity]
src/background/window.ts
src/components/controllers/audioCtrl.ts
src/components/controllers/textCtrl.ts
src/components/login.vue
src/components/messenger.vue
src/components/settings.vue
src/ipcRend/account.ts [new file with mode: 0644]
src/ipcRend/audio.ts [new file with mode: 0644]
src/ipcRend/session.ts [new file with mode: 0644]
src/modules/auth.ts [new file with mode: 0644]
src/modules/http.ts [new file with mode: 0644]
src/types.ts
yarn.lock

diff --git a/.env b/.env
new file mode 100644 (file)
index 0000000..6aa5026
--- /dev/null
+++ b/.env
@@ -0,0 +1,4 @@
+
+BUSINESS_URL="http://localhost:3000"
+
+PLATFORM_URL="ws://127.0.0.1:8760"
index 46762a2d8026977681654cc9be80bef640c4e2e5..20128fcb9af542bb1621c6eb55798b0cc5be56e0 100644 (file)
@@ -9,6 +9,7 @@ crash.log
 # local env files
 .env.local
 .env.*.local
+.env
 
 # Log files
 npm-debug.log*
index 04f3603002e61c2702a2135f4c6a9b4b065289c7..38c1348361c9b89f6208c0a29820d9a7ec3f231e 100644 (file)
     "@types/uuid": "^8.3.0",
     "@types/ws": "^7.2.7",
     "animejs": "^3.2.0",
+    "axios": "^0.21.1",
     "core-js": "^3.6.5",
+    "dotenv": "^10.0.0",
     "electron-is-dev": "^2.0.0",
+    "electron-store": "^8.0.0",
     "electron-updater": "^4.3.8",
     "mitt": "^2.1.0",
     "naudiodon": "^2.3.2",
@@ -36,6 +39,7 @@
     "ws": "^7.3.1"
   },
   "devDependencies": {
+    "@types/axios": "^0.14.0",
     "@types/electron-devtools-installer": "^2.2.0",
     "@types/jest": "^24.0.19",
     "@typescript-eslint/eslint-plugin": "^2.33.0",
index 320b62f88aa83aca9f6ad41690a534386e7a0df5..5a29c17fef61b23420ee734e8b66130bc86e00a6 100644 (file)
 import { defineComponent, onMounted, onUnmounted, ref } from "vue";
 import { IpcRendererEvent } from "electron";
 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 Messenger from "@/components/messenger.vue";
 import Login from "@/components/login.vue";
 
+import { Profile } from "@/types";
+
+
 export default defineComponent({
+
   components: {
     Splash,
     Messenger,
@@ -48,39 +55,38 @@ export default defineComponent({
   },
 
   setup() {
-    const { post } = useIpc();
+
+    const { post, invoke } = useIpc();
+
+    const { profile, setProfile, clearProfile } = useProfile();
 
     // Whether browser has received user info yet.
     const ready = ref(false);
 
-    // Information about current user.
-    const profile = ref(false);
-
     // New messages that browser missed while closed.
-    const newMessages = ref([])
+    const newMessages = ref([]);
 
     // Receive updated information about the session.
     const updateState = (_event: IpcRendererEvent, payload: any) => {
+      console.log('YAYAYAYAYA');
       console.log("APP:Received updated profile and new messages: \n" +
                   `    profile: ${payload.message.profile}\n` +
-                  `    new: ${payload.message.newMessages}`)
+                  `    new: ${payload.message.newMessages}`);
 
       if (payload.message.profile) {
-        console.log(`APP:Logged-in, showing Messenger View.`)
+        console.log(`APP:Logged-in, showing Messenger View.`);
       } 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;
 
       ready.value = true;
 
     }
 
-    onMounted(() => {
-      console.log("APP:mounted.")
+    onMounted(async () => {
+      console.log("APP:mounted.");
       window.ipcRenderer.on("update-state", updateState)
       window.ipcRenderer.on("update_available", () => {
           console.log('testing auto update');
@@ -89,18 +95,35 @@ export default defineComponent({
       window.ipcRenderer.on("update_downloaded", () => {
           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(() => {
-      window.ipcRenderer.removeAllListeners("update-state")
+      window.ipcRenderer.removeAllListeners("update-state");
     })
 
     return {
         ready,
-        profile,
         post,
-        newMessages
+        newMessages,
+        profile
     }
   }
 })
diff --git a/src/api/account.ts b/src/api/account.ts
new file mode 100644 (file)
index 0000000..1d72822
--- /dev/null
@@ -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,
+      }
+    })
+)
+
index 8fafb4f7b93866955b4b90252c59f609c61a88ab..1ecee8380e50ba418692a8fbfa1d5e0b4927dc74 100644 (file)
@@ -7,6 +7,7 @@
 
 import { initApp } from './background/init';
 import { protocol } from "electron";
+require('dotenv').config()
 
 // Scheme must be registered before the app is ready
 protocol.registerSchemesAsPrivileged([
index 0a98206086013a9eb2c7e98e498b81c55e234040..fa6efb2eff06ef42c08aa3f07d8dc206cc31a344 100644 (file)
@@ -2,9 +2,7 @@
 
 "use strict";
 
-import { ipcMain } from "electron";
 import { backgroundMitt } from '@/modules/emitter';
-
 const portAudio = require('naudiodon');
 
 // Audio in and out stream objects.
@@ -26,27 +24,22 @@ const audioOptions = {
     closeOnError: false,
 }
 
-// Toggles record to true to begin capturing chunks.
-const onRecordingStart = (_event: any, _payload: any) => {
-    console.log("AUDIO: Beginning audio capture.")
-    record = true;
-}
+export const toggleRecord = (): void => { record = !record };
+
 
-// Returns recorded audio to frontend and sets record to false.
-const onRecordingEnd = async (_event: any, payload: any) => {
-    console.log("AUDIO:Sending audio to browser.")
+export const fetchAudioInput = (): Promise<Error | string> => (
 
-    return new Promise((resolve, reject) => {
+    new Promise((resolve, reject) => {
 
         try {
             resolve(audioContainer.input);
-            record = false;
+            toggleRecord();
         } catch (e) {
-            reject()
+            reject(new Error('Failed to fetch the audio.'))
         }
 
-    });
-};
+    })
+)
 
 
 // Main audio function run by run.ts module.
@@ -86,15 +79,6 @@ export function initAudioIO(): void {
         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);
 }
 
 
index f2512467d420bd16c20643318dca72e338d4a3eb..1f472146c2fa6a7c34448834e3207a75f12432cb 100644 (file)
@@ -3,9 +3,10 @@
 
 import { app, dialog } from "electron";
 import { createWindow } from './window';
-import { initSession } from './session';
-import { initAudioIO, stopStream } from './audio';
+import { stopStream } from './audio';
 import { backgroundMitt } from '@/modules/emitter';
+import useIpc from "@/background/ipc/index";
+const { autoUpdater } = require('electron-updater');
 
 let win: boolean;
 
@@ -15,7 +16,6 @@ backgroundMitt.on('window-active', (state: boolean) => {
 });
 
 // Auto updating.
-const { autoUpdater } = require('electron-updater')
 autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' }
 
 autoUpdater.on('update-available', (info: any) => {
@@ -38,18 +38,17 @@ autoUpdater.on('update-downloaded', (info: any) => {
 
 })
 
+
+
 // Run when electron app is initialized.
-async function main(dev: boolean) {
-    console.log("MAIN:Initializing Electron App.")
+async function main(): Promise<void> {
 
-    // Must wait til window is created.
-    await createWindow();
+    console.log("MAIN:Initializing Electron App.");
 
-    // Instantiate socket session with crimata-platorm.
-    initSession(dev);
+    useIpc();
 
-    // Begin audio stream.
-    initAudioIO();
+    // Must wait til window is created.
+    await createWindow();
 
 }
 
@@ -58,8 +57,8 @@ export function initApp(dev: boolean): void {
 
     // On initial startup.
     app.on("ready", () => {
-        if (!dev) autoUpdater.checkForUpdates()
-        main(dev)
+        // autoUpdater.checkForUpdates()
+        main();
     });
 
     // Must keep to ensure app doesn't quit on close.
diff --git a/src/background/ipc/account.ts b/src/background/ipc/account.ts
new file mode 100644 (file)
index 0000000..c336ac3
--- /dev/null
@@ -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);
+
+}
diff --git a/src/background/ipc/audio.ts b/src/background/ipc/audio.ts
new file mode 100644 (file)
index 0000000..1419273
--- /dev/null
@@ -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);
+
+}
diff --git a/src/background/ipc/index.ts b/src/background/ipc/index.ts
new file mode 100644 (file)
index 0000000..933bda1
--- /dev/null
@@ -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();
+
+}
diff --git a/src/background/ipc/session.ts b/src/background/ipc/session.ts
new file mode 100644 (file)
index 0000000..a2496dc
--- /dev/null
@@ -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);
+
+}
index 89a88027a3ba808f0a8eb879dcfc543056bbf527..fc3e4fdf76b95e45b6158a95303536e67f53d61b 100644 (file)
@@ -1,82 +1,35 @@
 /*
  * Creates a websocket session with Crimata Servers.
- * 
- * Connects to Servers and attempts key authentication. Server will respond 
- * 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 
- * communication interface between the window and the servers. It will 
+ *
+ * Connects to Servers and attempts key authentication. Server will respond
+ * 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
+ * communication interface between the window and the servers. It will
  * automatically try to reconnect on websocket disconnect.
  */
 
 import { backgroundMitt } from "@/modules/emitter";
-import { ipcEmit, loadState, saveToJson } from './helpers';
-import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
+import { ipcEmit, loadState } from './helpers';
 
-import useWebSockets from "@/modules/websockets";
+import useWebSockets from "./websockets";
 
 import { play } from "./audio";
 import { renderMessage } from "@/modules/message";
-import { AuthProtocol, SessionState, Profile } from "@/types"; 
+import { SessionState } from "@/types";
 
 let win = true;
 
 // Info saved to json on quit (key, newMessages).
 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.
-const onMessage = (data: string) => {
-    let message = JSON.parse(data)
-
-    // AuthProtocol message.
-    if (message.hasOwnProperty("key")) {
-        updateState(message)
-    }
+const onMessage = (data: string): void => {
+    let message = JSON.parse(data);
+    console.log('received new message', message);
 
     // Standard message.
-    else if (message.content) {
+    if (message.content) {
 
         // Convert to render message
         message = renderMessage(
@@ -93,7 +46,7 @@ const onMessage = (data: string) => {
             }
             ipcEmit("render-message", message)
         }
-        
+
         else {
             console.log("SESS:No window: saving 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}`)
 
+// Websockets module.
+const { createSocket, send } = useWebSockets(onMessage);
+
+
+export const emitNewMessages = (): void => {
     if (state) {
-        sendMessage({
-            "key": state.key,
-            "usr": false,
-            "pwd": false
-        })
+        ipcEmit("update-state", {
+            newMessages: state.newMessages,
+        });
     }
-}
 
-// Websockets module.
-const { createSocket, sendMessage } = useWebSockets(onMessage, onOpen);
-
-// Handle messages from window/client.
-const onClientMessage = (_event: IpcMainEvent, payload: any) => {
-    console.log("New client message")
+}
 
-    const success = sendMessage(payload)
 
-    if (!success) {
-        console.log("Unable to send message: ", payload)
+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.
-export const initSession = (dev: boolean) => {
+export const initSession = (cid: string): void => {
     console.log("SESS:Creating new session.")
 
     // Load Json or createState.
-    state = loadState("session.json")
-
-    console.log("SESS:State loaded: \n" +
-                `    key: ${state.key}\n` +
-                `    new: ${state.newMessages}`)
+    state = loadState("session.json");
 
     // Open socket connection.
-    let url = "ws://crimata.com:8760";
-    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);
+    createSocket();
 
     // Keep win up-to-date.
     backgroundMitt.on('window-active', (state: boolean) => {
diff --git a/src/background/store.ts b/src/background/store.ts
new file mode 100644 (file)
index 0000000..16f8409
--- /dev/null
@@ -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"
+});
similarity index 64%
rename from src/modules/websockets.ts
rename to src/background/websockets.ts
index c75fc11a8d0100f0716e2657b9559eeadf2d60c1..9fcc5e8215bafca59145c6c6ee92cac9b25394b6 100644 (file)
@@ -1,13 +1,16 @@
 "use strict";
 
 import WebSocket from 'ws';
-import { ipcMain } from "electron";
 
 let socket: WebSocket;
 
+const socketUrl = process.env.PLATFORM_URL;
 
 // 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).
     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!");
 
-        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.")
     }
 
@@ -52,9 +66,9 @@ export default function useWebSockets(receiveCallback: (s: string) => any, openC
         setTimeout(createSocket, 1000)
     }
 
-    const createSocket = (url: string) => {
-
-        socket = new WebSocket(url)
+    const createSocket = () => {
+        if (socketUrl)
+        socket = new WebSocket(socketUrl)
 
         // Add listeners.
         socket.addEventListener("open", onOpen)
@@ -67,7 +81,8 @@ export default function useWebSockets(receiveCallback: (s: string) => any, openC
 
     return {
         createSocket,
-        sendMessage
+        sendMessage,
+        send
     }
 
-}
\ No newline at end of file
+}
index e3125721eb5c6275e453a90a02d4ebf3b9ebf890..162b3e1481cf6a5779d6a0d1c479692fa29b367e 100644 (file)
@@ -57,7 +57,6 @@ const saveWindowState = () => {
 
 // Do this on window mount.
 const onWindowMount = (): void => {
-    console.log("BW:Adding window listeners. ")
 
     // Must tell initApp that window exists.
     backgroundMitt.emit('window-active', true);
@@ -70,12 +69,10 @@ const onWindowMount = (): void => {
     backgroundMitt.removeAllListeners("ipc-renderer")
     backgroundMitt.on("ipc-renderer", renderMessage);
 
-    console.log("BW:Listeners created.")
 }
 
 // Do this on window dismount (close).
 const onWindowDismount = (): void => {
-    console.log("BW:Window closed.")
     win = null;
     backgroundMitt.emit('window-active', false);
 }
@@ -89,7 +86,6 @@ export async function createWindow(): Promise<void> {
 
     // Load the saved window state.
     winState = loadWinState("window.json")
-    console.log(`BW:Creating window [${winState.width}, ${winState.height}].`)
 
     // Define the browser window.
     win = new BrowserWindow({
index c9efaa9331980fed04d8d604f533a716fa96fd56..f635c0b883798cf88462a2dc32de94999292f270 100644 (file)
@@ -4,6 +4,8 @@ import { useIpc } from '@/modules/ipc';
 import { onMounted, onUnmounted, ref, Ref } from "vue";
 import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
 import { renderMessage, clientMessage } from '@/modules/message';
+import { postMessage } from "@/ipcRend/session";
+import { invokeStopRecord } from "@/ipcRend/audio";
 
 
 function showRecIcon () {
@@ -13,7 +15,7 @@ function showRecIcon () {
         opacity: [0, 0.75],
         scale: [0.0, 1],
         duration: 250,
-        easing: 'linear', 
+        easing: 'linear',
     })
 
 }
@@ -25,7 +27,7 @@ function hideRecIcon () {
         opacity: [0.75, 0],
         scale: [1, 0],
         duration: 250,
-        easing: 'linear', 
+        easing: 'linear',
     })
 
 }
@@ -50,7 +52,7 @@ export default function useAudioInputController (typing: Ref) {
         if (cmd == "SPACE" && !recording.value && !typing.value) {
 
             console.log("INPT:Starting record.")
-            post("start-recording", "");
+            post("start-recording", null);
 
             showRecIcon()
             recording.value = true;
@@ -67,9 +69,9 @@ export default function useAudioInputController (typing: Ref) {
 
             // Create a message.
             const message = renderMessage(
-                "", 
-                "", 
-                "", 
+                "",
+                "",
+                "",
                 "sf"
             )
 
@@ -78,14 +80,19 @@ export default function useAudioInputController (typing: Ref) {
 
             // Stop recording and get audio from recorder.
             console.log("INPT:Stopping record.")
-            const audio = await invoke("stop-recording", "");
-            
-            // Send message to the backend for processing.
-            const clientM = clientMessage("", audio, message.uid);
-            post('client-message', clientM);
-
-            hideRecIcon()
-            recording.value = false;
+            try {
+                const audio = await invokeStopRecord() as string;
+                // Send message to the backend for processing.
+                const clientM = clientMessage("", audio, message.uid);
+                postMessage(clientM);
+            } catch(e) {
+                console.log('Failed to fetch audio.')
+            } finally {
+                hideRecIcon();
+                recording.value = false;
+            }
+
+
 
         }
 
index 7a2e2e81bfcb1a9135757dc248b8650d6850bcd7..ced8aaf37019c00212298614c395c2b7524d6e29 100644 (file)
@@ -4,6 +4,8 @@ import { useIpc } from '@/modules/ipc';
 import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
 import keyboardNameMap from "../keyBoardMaps/keyboardNameMap";
 import { clientMessage, renderMessage } from '@/modules/message';
+import { postMessage } from "@/ipcRend/session";
+
 
 //---Animations-----------------------------------------------
 
@@ -19,7 +21,7 @@ function showTextInput () {
         translateX: [t1, t2],
         scale: [0.3, 1],
         duration: 500,
-        easing: 'easeOutExpo', 
+        easing: 'easeOutExpo',
     })
 
 }
@@ -33,7 +35,7 @@ function hideTextInput() {
         translateX: t,
         scale: 0.3,
         duration: 500,
-        easing: 'easeOutExpo', 
+        easing: 'easeOutExpo',
     })
 
 }
@@ -45,7 +47,7 @@ function switchSide(currentSide: string) {
         targets: '#textInput',
         translateX: t,
         duration: 500,
-        easing: 'easeOutExpo', 
+        easing: 'easeOutExpo',
     })
 
 }
@@ -75,7 +77,7 @@ export default function useTextInputController(elementX: Ref) {
             textInput.value = "";
             textInput.blur();
         }
-        
+
         hideTextInput()
         firstKey = true;
         typing.value = false;
@@ -87,9 +89,9 @@ export default function useTextInputController(elementX: Ref) {
 
             // Create the message.
             const message = renderMessage(
-                textInput.value, 
-                false, 
-                "", 
+                textInput.value,
+                false,
+                "",
                 "sf"
             )
 
@@ -97,7 +99,7 @@ export default function useTextInputController(elementX: Ref) {
 
             // Send it to the backend for processing.
             const clientM = clientMessage(textInput.value, false, message.uid);
-            post('client-message', clientM);
+            postMessage(clientM);
 
             clearInput()
         }
@@ -115,9 +117,9 @@ export default function useTextInputController(elementX: Ref) {
 
     const onKeyDown = (e: KeyboardEvent) => {
         const key = keyboardNameMap[e.keyCode]
-        
+
         if (textInput) {
-           
+
             // Only runs on firstKey.
             if (firstKey) {
 
@@ -165,7 +167,7 @@ export default function useTextInputController(elementX: Ref) {
                 switchSide(side)
                 side = "left"
             }
-        } 
+        }
 
         else {
             if (winW - elementX > 230) {
@@ -173,7 +175,7 @@ export default function useTextInputController(elementX: Ref) {
                 side = "right"
             }
         }
-        
+
     });
 
     onMounted(() => {
index 521517c272c2b9fb56188e4278fb80e8570e5893..8232a671bf05ecde82d9a812a0a6505d0027afc6 100644 (file)
@@ -25,7 +25,7 @@
 
     <!-- submit button; position: fixed -->
     <button class="submitButton button" type="submit">Submit</button>
-    
+
   </form>
 
     <!-- back to login button: position: fixed  -->
 import { defineComponent, ref } from "vue";
 import { useIpc } from "@/modules/ipc";
 import { authRequest } from '@/modules/message';
+import { useProfile } from '@/modules/auth';
+import { invokeLogin } from "@/ipcRend/account";
+import { Profile } from "@/types";
 
 export default defineComponent({
   name: "Login",
 
   setup() {
 
-    const { post } = useIpc();
+    const { setProfile } = useProfile();
 
     const usr = ref("");
     const pwd = ref("");
 
     // Submit login credentials to the backend.
-    const submitForm = () => {
-      console.log(`Submitting login form: ${usr.value}, ${pwd.value}`)
-      post("client-message", authRequest(false, usr.value, pwd.value))
+    const submitForm = async () => {
+
+      try {
+          const profile = await invokeLogin({
+              email: usr.value,
+              password: pwd.value
+          }) as Profile;
+
+          setProfile(profile);
+
+      } catch(e) {
+
+      }
     }
 
     return {
index edc50a1954d78bba3bdeaf93390d41e1fdc4829b..f5512a918f64a22d1c21a95a4bd008cc63da9603 100644 (file)
@@ -6,9 +6,9 @@
 
   <!-- List of message bubbles. -->
   <div id="messenger">
-    <Message 
-      v-for="message in messages" 
-      :message="message[1]" 
+    <Message
+      v-for="message in messages"
+      :message="message[1]"
       :key="message[0]"
     />
   </div>
@@ -48,25 +48,24 @@ export default defineComponent({
 
       // Load the message history.
       if (crimataId === window.localStorage.getItem("last_usr")) {
-        prepMessageView(props.newMessages)
+        prepMessageView(props.newMessages);
       } else {
-        messages.value.clear()
+        messages.value.clear();
       }
 
       // New content listeners.
       emitter.on('self-message', (message) => updateMessageView(message));
       window.ipcRenderer.on("render-message", (_e: any, payload: any) => {
-        updateMessageView(payload.message)
+        updateMessageView(payload.message);
       });
 
       // Save the usr for next time.
-      window.localStorage.setItem("last_usr", crimataId)
+      window.localStorage.setItem("last_usr", crimataId);
 
     });
 
     onUnmounted(() => {
       window.ipcRenderer.removeAllListeners("render-message");
-      window.ipcRenderer.removeAllListeners("annotate-message");
       emitter.all.clear();
     });
 
index 51347b28ecfccd43519e6996aedd0e61781db13d..7c4b9ceec27e3b4beb8c842ac30dca554d538c27 100644 (file)
@@ -30,6 +30,8 @@
   import { defineComponent, ref } from "vue";
   import  { useIpc } from "@/modules/ipc";
   import { logoutRequest } from '@/modules/message';
+  import { useProfile } from "@/modules/auth"
+  import { invokeLogout } from "@/ipcRend/account";
 
   export default defineComponent({
     name: "Settings",
@@ -37,7 +39,9 @@
     setup() {
       const toggleSettings = ref(false);
 
-      const { post } = useIpc();
+      const { post, invoke } = useIpc();
+
+      const { clearProfile } = useProfile();
 
       // Listen for escape key to close settings.
       const onEscape = (e: any) => {
       }
 
       // We ask server to log us out.
-      const onLogout = () => {
-        console.log("Submitting logout request.")
-        post("client-message", logoutRequest())
+      const onLogout = async () => {
+
+        console.log("Submitting logout request.");
+
+        try {
+            clearProfile();
+            await invokeLogout();
+        } catch(e) {
+            console.log('error')
+        }
+
       }
 
       return {
diff --git a/src/ipcRend/account.ts b/src/ipcRend/account.ts
new file mode 100644 (file)
index 0000000..9300aa6
--- /dev/null
@@ -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)
+);
diff --git a/src/ipcRend/audio.ts b/src/ipcRend/audio.ts
new file mode 100644 (file)
index 0000000..6f25d29
--- /dev/null
@@ -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)
+);
diff --git a/src/ipcRend/session.ts b/src/ipcRend/session.ts
new file mode 100644 (file)
index 0000000..966b121
--- /dev/null
@@ -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)
+);
diff --git a/src/modules/auth.ts b/src/modules/auth.ts
new file mode 100644 (file)
index 0000000..5fb40c1
--- /dev/null
@@ -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
+    }
+
+}
diff --git a/src/modules/http.ts b/src/modules/http.ts
new file mode 100644 (file)
index 0000000..de241e0
--- /dev/null
@@ -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
+    }
+}
index 37b18ac6239fcde0e9f56b0eb08ffc1330b22a70..2e35fc5ccd9a2fe9a4295b95bcbaa337e6bcc78d 100644 (file)
@@ -19,7 +19,7 @@ export interface ClientMessage {
 }
 
 export interface ClientRequest {
-    intent: string; 
+    intent: string;
     params: object;
     epic: string | boolean;
     confidence: number;
@@ -50,8 +50,10 @@ export interface Profile {
 }
 
 export interface AuthProtocol {
-    key: boolean | string;
-    profile: boolean | Profile;
+    token: null | string;
+    profile: null | Profile;
+    password?: string;
+    email?: string;
 }
 
 export interface LogoutRequest {
@@ -71,4 +73,4 @@ export interface StandardMessage {
     };
     context: string;
     modifier: string;
-}
\ No newline at end of file
+}
index 8dc3fba9d440f5152db03ab9a7d54297067ce2ad..465adc92b1561f2f99ed954c208ccf343d9ac1b0 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
   resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
   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":
   version "7.1.12"
   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"
   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:
   version "3.5.2"
   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"
     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:
   version "1.0.2"
   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"
   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:
   version "9.8.6"
   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"
   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:
   version "6.26.0"
   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"
     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:
   version "1.1.12"
   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"
     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:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -4827,11 +4886,23 @@ dot-prop@^5.2.0:
   dependencies:
     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:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
   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:
   version "8.2.0"
   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"
     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:
   version "1.3.589"
   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"
   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:
   version "0.1.7"
   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"
   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:
   version "1.0.2"
   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"
   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:
   version "0.2.3"
   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"
   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:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@@ -9822,6 +9926,13 @@ pkg-dir@^4.1.0:
   dependencies:
     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:
   version "3.2.0"
   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"
   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:
   version "2.0.0"
   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"
   integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
 
-semver@^7.3.4:
+semver@^7.3.4, semver@^7.3.5:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   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"
   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:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"