]> Repos - mime-chat/commitdiff
auth enhancements amongst other things
authorAndrew Gundersen <gundersena@xavier.edu>
Mon, 29 Mar 2021 15:15:54 +0000 (10:15 -0500)
committerAndrew Gundersen <gundersena@xavier.edu>
Mon, 29 Mar 2021 15:15:54 +0000 (10:15 -0500)
21 files changed:
session.json [new file with mode: 0644]
src/App.vue
src/background/audio.ts
src/background/handlers.ts [deleted file]
src/background/helpers.ts [new file with mode: 0644]
src/background/init.ts
src/background/session.ts
src/background/window.ts
src/components/controllers/audioCtrl.ts
src/components/inputItem.vue [moved from src/components/input.vue with 81% similarity]
src/components/login.vue
src/components/messenger.vue
src/components/settings.vue
src/components/textInput.vue [moved from src/components/text.vue with 100% similarity]
src/modules/auth.ts [deleted file]
src/modules/ipc.ts
src/modules/messages.ts
src/modules/scroll.ts
src/modules/websockets.ts [moved from src/modules/ws.ts with 100% similarity]
src/types/message/index.ts
windowState.json

diff --git a/session.json b/session.json
new file mode 100644 (file)
index 0000000..50bed80
--- /dev/null
@@ -0,0 +1 @@
+{"key":"0c116d29-8cfe-491f-a84d-1d021297cc69","newMessages":[]}
\ No newline at end of file
index ce8fa5bdbe70abc6ba891fbf4bad3bee7e027166..7bc0ee1579eb37a5ac0dd0d1bb8bd06bc21fa84c 100644 (file)
@@ -1,5 +1,6 @@
 <template>
-  <div id="app" v-if="(windowReady && sessionReady)">
+  <!-- Only render when profile has been set -->
+  <div id="app" v-if="(ready)">
 
     <button class="titlebar" />
 
@@ -7,21 +8,25 @@
     <span class="menu">
       <button 
         class="menuButton exitButton" 
-        @click.prevent="callNavbar('close')" 
+        @click.prevent="post('nav-bar', 'close')" 
       />
       <button 
         class="menuButton minimizeButton" 
-        @click.prevent="callNavbar('min')" 
+        @click.prevent="post('nav-bar', 'min')" 
       />
     </span>
 
     <!-- Main Pages -->
-    <Messenger v-if="auth"/>
+    <Messenger 
+      v-if="profile" 
+      :profile="profile"
+      :newMessages="newMessages"
+    />
     <Login v-else />
 
   </div>
 
-  <!-- Show spash screen if not ready -->
+  <!-- Show spash screen if ready is false -->
   <Splash v-else />
   
 </template>
@@ -31,6 +36,7 @@ import { defineComponent, onMounted, onUnmounted, ref } from "vue";
 import { IpcRendererEvent } from "electron";
 import { authRequest } from '@/modules/message'; 
 import { useIpc } from "@/modules/ipc";
+import { Profile } from "@/types/message"; 
 
 import Splash from "@/components/splash.vue";
 import Messenger from "@/components/messenger.vue";
@@ -44,74 +50,52 @@ export default defineComponent({
   },
 
   setup() {
-    const { post, invoke } = useIpc();
+    const { post } = useIpc();
 
-    const auth = ref(false);
-    const windowReady = ref(false);
-    const sessionReady = ref(false);
+    // Whether browser has received user info yet.
+    const ready = ref(false);
 
-    // When connection is established, we send key over.
-    const onOpen = (_event: IpcRendererEvent, payload: any) => {
-      console.log(`Connected to Crimata.`)
-      const key = window.localStorage.getItem("key");
+    // Information about current user.
+    const profile = ref(false);
 
-      if (key) {
-        console.log(`Sending key: ${key}`)
-        post("client-message", authRequest(key, false, false))
-      } 
+    // New messages that browser missed while closed.
+    const newMessages = ref([])
 
-      else {
-        console.log("No key, session ready.")
-        sessionReady.value = true
-      }
-
-    }
-
-    // Update authenticate state on auth message from server.
-    const onAuthResponse = (_event: IpcRendererEvent, payload: any) => {
-      const key = payload.message.key
-      const usr = payload.message.usr
-      console.log(`Received auth response: ${usr}, ${key}`)
+    // Receive updated information about the session.
+    const updateState = (_event: IpcRendererEvent, payload: any) => {
+      console.log("APP:Received updated profile and new messages: \n" +
+                  `    profile: ${payload.message.profile}\n` +
+                  `    new: ${payload.message.newMessages}`)
 
-      if (key) {
-        console.log(`Auth success, saving key: ${key}.`)
-        window.localStorage.setItem("key", payload.message.key)
-        auth.value = true;
+      if (payload.message.profile) {
+        console.log(`Logged-in, showing Messenger View.`)
+      } else {
+        console.log(`Logged-out, showing Login View.`)
       }
 
-      else {
-        console.log(`Auth failed, clearing local storage.`)
-        window.localStorage.clear()
-        auth.value = false;
-      }
-
-      console.log("Session is ready.")
-      sessionReady.value = true
+      // Set profile and newMessages.
+      profile.value = payload.message.profile;
+      newMessages.value = payload.message.newMessages;
 
-    }
+      ready.value = true;
 
-    // Post navbar action to backend.
-    const callNavbar = (action: string) => {
-      invoke("nav-bar", action);
     }
 
     onMounted(() => {
-      window.ipcRenderer.on("on-connect", onOpen)
-      window.ipcRenderer.on("auth-response", onAuthResponse)
-      window.ipcRenderer.on("window-ready", () => windowReady.value = true);
+      console.log("APP:mounted.")
+      window.ipcRenderer.on("update-state", updateState)
+      post("app-mounted", "")
     });
 
     onUnmounted(() => {
-      window.ipcRenderer.removeAllListeners("on-connect")
-      window.ipcRenderer.removeAllListeners("window-ready")
-      window.ipcRenderer.removeAllListeners("auth-response")
+      window.ipcRenderer.removeAllListeners("update-state")
     })
 
     return {
-        callNavbar,
-        sessionReady,
-        windowReady,
-        auth
+        ready,
+        profile,
+        post,
+        newMessages
     }
   }
 })
index c4cd6fde84072cc247e527d83daa7b0ff7301806..aa577169a41073e998defe32fa47292a5464f509 100644 (file)
@@ -3,13 +3,13 @@
 "use strict";
 
 import { ipcMain } from "electron";
-import { backgroundMitt } from './emitter';
+import { backgroundMitt } from '@/modules/emitter';
 
 const portAudio = require('naudiodon');
 
 // Audio in and out stream objects.
-let ai: typeof portAudio.AudioIO | null = null;
-let ao: typeof portAudio.AudioIO | null = null;
+let ai: typeof portAudio.AudioIO | Boolean = false;
+let ao: typeof portAudio.AudioIO | Boolean = false;
 
 // Whether activly recording.
 let record = false;
@@ -26,9 +26,15 @@ 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;
+}
 
 // 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) => {
 
@@ -59,7 +65,7 @@ export function initAudioIO(): void {
 
             // If recording, we capture the data.
             if (record) {
-                console.log('Recording...')
+                console.log('AUDIO:Recording...')
                 audioContainer.input += chunk;
             }
 
@@ -84,11 +90,11 @@ export function initAudioIO(): void {
     // Listen to record.
     console.log("AUDIO:Adding recording listeners.")
 
-    ipcMain.removeAllListeners('start-recording');
-    ipcMain.on('start-recording', () => record = true);
+    ipcMain.removeAllListeners("start-recording");
+    ipcMain.on("start-recording", onRecordingStart);
 
-    ipcMain.removeHandler('stop-recording');
-    ipcMain.handle('stop-recording', onRecordingEnd);
+    ipcMain.removeHandler("stop-recording");
+    ipcMain.handle("stop-recording", onRecordingEnd);
 }
 
 
@@ -110,10 +116,13 @@ function bufSplit(buf: Buffer, len: number): Array<Buffer> {
 }
 
 // Audio playback.
-export function play(input: Buffer): void {
-    let i = 0;
+export function play(input: string): void {
 
-    const audio = bufSplit(input, 8192);
+    // Format the audio.
+    const audio = bufSplit(
+        Buffer.from(input as string, 'hex'), 
+        8192
+    );
 
     // Called on end of write.
     const callback = () => {
@@ -131,6 +140,7 @@ export function play(input: Buffer): void {
     function write() {
         let chunk: Buffer;
         let ok = true;
+        let i = 0;
 
         do {
             chunk = audio[i];
@@ -157,42 +167,13 @@ export function play(input: Buffer): void {
 // Get's called on window close.
 export function stopStream() {
     console.log("AUDIO:Stopping audio stream.")
-    if(ai != null) {
-        ai.quit();
-        ai = null;
+    if (ai) {
+        ai.quit()
     }
-    if (ao != null) {
-        ao.quit();
-        ao = null;
+    if (ao) {
+        ao.quit()
     }
+    console.log("AUDIO:Audio closed.")
 }
 
 
-// // Returns recorded audio to frontend.
-// const getAudio = async (_event: any, payload: any) => {
-
-//     return new Promise((resolve, reject) => {
-
-//         // Handle auth response from server.
-//         backgroundMitt.once('auth-res', (res: string) => {
-
-//             if (res == "locked") {
-//                 reject(res);
-//             } else {
-//                 console.log('Success!');
-//                 auth = true;
-//                 resolve(res);
-//             }
-
-//         });
-
-//         if (typeof payload !== "string") {
-//             payload = JSON.stringify(payload)
-//         }
-
-//         // Send token to backend
-//         socket.send(payload);
-
-//     });
-// };
-
diff --git a/src/background/handlers.ts b/src/background/handlers.ts
deleted file mode 100644 (file)
index 4ba8b7a..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-import { backgroundMitt } from "./emitter";
-import { renderMessage } from "@/modules/message";
-import { Profile, StandardMessage, Annotation } from "@/types/message/index";
-import { play } from "./audio";
-
-
-export const handleAuthMessage = (message: string) => {
-    backgroundMitt.emit('ipc-renderer', {
-        endpoint: 'auth-response',
-        message: message
-    });
-}
-
-export const handleProfileMessage = (message: Profile) => {
-    backgroundMitt.emit('ipc-renderer', {
-        endpoint: 'update-profile',
-        message: message
-    });
-}
-
-export const handleStandardMessage = (m: StandardMessage) => {
-
-    // Create a render message object.
-    const message = renderMessage(
-        m.content.text, m.content.audio, m.context, m.modifier);
-
-    // Play audio if any.
-    if (message.content.audio) {
-        const audioBytes = Buffer.from(m.content.audio as string, 'hex');
-        m.content.audio = true;
-        play(audioBytes);
-    }
-
-    backgroundMitt.emit('ipc-renderer', {
-        endpoint: 'render-message',
-        message: message 
-    });
-}
-
-export const handleAnnotation = (message: Annotation) => {
-    backgroundMitt.emit('ipc-renderer', {
-        endpoint: 'render-message',
-        message: message 
-    });
-}
\ No newline at end of file
diff --git a/src/background/helpers.ts b/src/background/helpers.ts
new file mode 100644 (file)
index 0000000..098c203
--- /dev/null
@@ -0,0 +1,37 @@
+import fs from 'fs';
+import { backgroundMitt } from "@/modules/emitter";
+import { SessionState } from "@/types/message"; 
+
+
+export const ipcEmit = (channel: string, payload: any) => {
+    backgroundMitt.emit('ipc-renderer', {
+        endpoint: channel,
+        message: payload
+    });
+}
+
+export const loadState = (fileName: string): SessionState => {
+    let state: SessionState;
+
+    try {
+        state = JSON.parse(fs.readFileSync(fileName).toString());
+    } 
+
+    catch (error) {
+        state = {
+            key: false,
+            newMessages: []
+        }
+    }
+
+    return state
+    
+} 
+
+export const saveState = (fileName: string, state: SessionState) => {
+    fs.writeFile(fileName, JSON.stringify(state), (err) => {
+        if (err) {
+            console.log("Error when saving state.")
+        }      
+    });
+}
\ No newline at end of file
index a805b79220a7657bb3845697fe66bf3e797a855f..69a2938cfed8ab7f9be68470af2931cf9ddf837e 100644 (file)
@@ -37,15 +37,10 @@ export function initApp(dev: boolean): void {
       main()
     });
 
-    // Quit app on window closed.
-    app.on("window-all-closed", () => {
-      console.log("MAIN:Quitting app.")
-      app.quit()
+    app.on("before-quit", () => {
     });
 
-    // Shutdown audio streams peacefully.
-    app.on("before-quit", () => {
-      stopStream()
+    app.on("window-all-closed", () => {
     });
 
     // When user clicks app icon (re-open)
index ece5915de8fcf5b16ef84111f8e66902d9cd7320..828709aaaaae67d2caeef353b900bbc0877ee8b9 100644 (file)
 /*
  * Creates a websocket session with Crimata Servers.
  * 
- * Connects to Servers and attempts token authentication. Will send the result 
- * of the authentication to the window. It will then serve as a communication
- * interface between the window and the servers. It will automatically try to 
- * reconnect on websocket disconnect.
+ * 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 './emitter';
-import { ipcMain, IpcMainEvent } from "electron";
+import { backgroundMitt } from "@/modules/emitter";
+import { ipcEmit, loadState, saveState } from './helpers';
+import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
 
-import useWebSockets from "@/modules/ws";
+import useWebSockets from "@/modules/websockets";
 
-import { handleAuthMessage, handleStandardMessage, handleProfileMessage, handleAnnotation } from "./handlers";
+import { play } from "./audio";
+import { renderMessage } from "@/modules/message";
+import { AuthProtocol, SessionState, Profile } from "@/types/message"; 
 
-// Key for key-based auth.
-let key: string;
+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.")
+        saveState("session.json", state)
+
+    }
+
+}
+
+// Send state on new window.
+const onNewBrowserWindow = (_event: IpcMainInvokeEvent, _payload: any) => {
+    console.log("SESS:New window, sending profile.")
+    if (profile) {
+        ipcEmit("update-state", {
+            profile: profile,
+            newMessages: state.newMessages
+        })
+    }
+}
 
 // Calls appropriate endpoint for a server message.
 const onMessage = (data: string) => {
-    const message = JSON.parse(data)
+    let message = JSON.parse(data)
 
+    // AuthProtocol message.
     if (message.hasOwnProperty("key")) {
-        handleAuthMessage(message)
+        updateState(message)
     }
 
+    // Standard message.
     else if (message.content) {
-        handleStandardMessage(message)
-    }
 
-    else if (message.first) {
-        handleProfileMessage(message)
+        // Convert to render message
+        message = renderMessage(
+            message.content.text,
+            message.content.audio,
+            message.context,
+            message.modifier
+        );
+
+        if (win) {
+            console.log("SESS:Emitting standard message.")
+            if (message.audio) {
+                play(message.audio)
+            }
+            ipcEmit("render-message", message)
+        }
+        
+        else {
+            console.log("SESS:No window: saving message.")
+            state.newMessages.push(message);
+        }
     }
 
     else {
-        handleAnnotation(message)
+        if (win) {
+            ipcEmit("render-message", message)
+        }
     }
 
 };
 
-// Attempt token authentication onOpen.
+// When socket connects, we update state.
 const onOpen = () => {
-    if (key) {
+    console.log(`SESS:Sending key: ${state.key}`)
+
+    if (state) {
         sendMessage({
-            "key": key,
+            "key": state.key,
             "usr": false,
             "pwd": false
         })
     }
 }
 
+// Websockets module.
 const { createSocket, sendMessage } = useWebSockets(onMessage, onOpen);
 
 // Handle messages from window/client.
-const onMessageFromWindow = (_event: IpcMainEvent, payload: any) => {
+const onClientMessage = (_event: IpcMainEvent, payload: any) => {
     console.log("New client message")
 
     const success = sendMessage(payload)
@@ -68,17 +137,30 @@ const onMessageFromWindow = (_event: IpcMainEvent, payload: any) => {
 }
 
 // Call this to initialize session with Crimata servers.
-export const initSession = (key: string) => {
+export const initSession = () => {
+    console.log("SESS:Creating new session.")
 
-    // Set the key.
-    key = key;
+    // Load Json or createState.
+    state = loadState("session.json")
+
+    console.log("SESS:State loaded: \n" +
+                `    key: ${state.key}\n` +
+                `    new: ${state.newMessages}`)
 
     // Open socket connection.
     createSocket()
 
+    // 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", onMessageFromWindow);
+    ipcMain.on("client-message", onClientMessage);
 
-    
-}
\ No newline at end of file
+    // Keep win up-to-date.
+    backgroundMitt.on('window-active', (state: boolean) => {
+        win = state;
+    });
+
+}
index 246f5a15838d91c4a4679938881b40fb1dbeab8e..5d180cc95dbd38a852518025346ddd3205de5e6c 100644 (file)
@@ -2,7 +2,7 @@
 
 import { BrowserWindow, ipcMain } from "electron";
 import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
-import { backgroundMitt } from './emitter';
+import { backgroundMitt } from '@/modules/emitter';
 import { RenderMessage } from "@/types/message/index";
 import * as path from "path";
 import fs from 'fs';
@@ -50,8 +50,9 @@ const saveWindowState = () => {
        );
 
        fs.writeFile('windowState.json', state, (err) => {
-          if (err) throw err;      
-          return;
+          if (err) {
+            console.log("BW:Error saving window state.")
+          }
        });
     }
 }
@@ -64,12 +65,12 @@ const onWindowMount = (): void => {
     backgroundMitt.emit('window-active', true);
 
     // Handle win nav-bar event.
-    ipcMain.removeHandler("nav-bar") // avoid setting duplicate handlers
-    ipcMain.handle("nav-bar", onNavBar);
+    ipcMain.removeAllListeners("nav-bar") // avoid setting duplicate handlers
+    ipcMain.on("nav-bar", onNavBar);
     
     // Gateway for messages to the frontend.
-    backgroundMitt.removeAllListeners('ipc-renderer')
-    backgroundMitt.on('ipc-renderer', renderMessage);
+    backgroundMitt.removeAllListeners("ipc-renderer")
+    backgroundMitt.on("ipc-renderer", renderMessage);
 
     console.log("BW:Listeners created.")
 }
index 0ae0fe8829db28c6bcf23906e6c77eac60b7ddd4..8a73868c5604f43ace3f0430dd68598d8e0ee6b6 100644 (file)
@@ -49,11 +49,11 @@ export default function useAudioInputController (typing: Ref) {
         // Start recording on space bar.
         if (cmd == "SPACE" && !recording.value && !typing.value) {
 
-            post('start-recording', "");
+            console.log("INPT:Starting record.")
+            post("start-recording", "");
 
             showRecIcon()
             recording.value = true;
-            console.log("Recording...")
 
         }
 
@@ -70,7 +70,8 @@ export default function useAudioInputController (typing: Ref) {
             emitter.emit("self-message", message);
 
             // Stop recording and get audio from recorder.
-            const audio = await invoke('stop-recording', "");
+            console.log("INPT:Stopping record.")
+            const audio = await invoke("stop-recording", "");
             
             // Send message to the backend for processing.
             const clientM = clientMessage("", audio, message.uid);
@@ -78,7 +79,6 @@ export default function useAudioInputController (typing: Ref) {
 
             hideRecIcon()
             recording.value = false;
-            console.log("Stopping record...")
 
         }
 
similarity index 81%
rename from src/components/input.vue
rename to src/components/inputItem.vue
index c2d2c90807b3ef642a16b1eb2d9731d037e306d6..b24d9c4c8465fd672b2e27955fee491af1900196 100644 (file)
 import { defineComponent, ref, onMounted, onUnmounted } from "vue";
 import draggify from "@/modules/draggify";
 
-import TextInput from "@/components/inputItem/textInput.vue";
+import TextInput from "@/components/textInput.vue";
+
 import useTextInputController from 
-  "@/components/inputItem/controllers/textCtrl";
+  "@/components/controllers/textCtrl";
 import useAudioInputController from 
-  "@/components/inputItem/controllers/audioCtrl";
+  "@/components/controllers/audioCtrl";
 
 export default defineComponent({
   name: "InputItem",
+
+  props: ["initials"],
+
   components: {
     TextInput
   },
-  setup() {
-
-    // Mark input item with user's initials.
-    const initials = ref("")
 
-    // Set initials.
-    const initialData = window.localStorage.getItem("initials")
-    if (initialData) initials.value = initialData
+  setup() {
 
     // Default values for position.
     const xStart = 15;
@@ -57,25 +55,10 @@ export default defineComponent({
     const { typing } = useTextInputController(elementX)
     const { recording } = useAudioInputController(typing)
 
-    // Update profile functionality.
-    const onUpdateProfile = (_event: any, payload: any) => {
-      initials.value = payload.message.first[0] + payload.message.last[0]
-      window.localStorage.setItem("initials", initials.value)
-    }
-
-    onMounted(() => {
-      window.ipcRenderer.on("update-profile", onUpdateProfile);
-    }) 
-
-    onUnmounted(() => {
-      window.ipcRenderer.removeAllListeners("update-profile");
-    })
-
     return {
       elementX,
       elementY,
-      recording,
-      initials
+      recording
     };
   },
 });
index 838217957c523d0775bbeff7628ae254157e5b64..95cf62f790cb2be38f851869363f80c5abb29ac3 100644 (file)
   </form>
 
     <!-- back to login button: position: fixed  -->
-    <button class="createAccountButton button" @click.prevent="switchView">Create account</button>
+<!--     <button class="createAccountButton button" @click.prevent="switchView">Create account</button> -->
 </template>
 
 <script lang="ts">
 
 import { defineComponent, ref } from "vue";
-import { useAuth } from "@/modules/auth";
 import { useIpc } from "@/modules/ipc";
 import useMessages from "@/modules/messages";
 import { authRequest } from '@/modules/message'; 
@@ -46,11 +45,9 @@ export default defineComponent({
   setup() {
 
     const { post } = useIpc();
-    const { setToken } = useAuth();
-    const { resetMessages } = useMessages();
 
-    let usr = ref("");
-    let pwd = ref("");
+    const usr = ref("");
+    const pwd = ref("");
 
     // Submit login credentials to the backend.
     const submitForm = () => {
index 318c5cf2720aef871e1adc22bbfdeda50620bde6..625f18c0eb00b423e2fdba1b332ee54e253491bc 100644 (file)
@@ -1,51 +1,66 @@
 <template>
-  <InputItem />
+  <InputItem :initials="profile.initials"/>
   <Settings />
 
   <div id="recIcon" />
 
   <!-- List of message bubbles. -->
   <div id="messenger">
-    <MessageItem v-for="message in messages" :message="message[1]" :key="message[0]" />
+    <Message 
+      v-for="message in messages" 
+      :message="message[1]" 
+      :key="message[0]" 
+    />
   </div>
 
 </template>
 
 <script lang="ts">
 import { defineComponent, onMounted, onUnmounted } from "vue";
-import MessageItem from "@/components/messageItem.vue";
-import InputItem from "@/components/inputItem/inputItem.vue";
+import Message from "@/components/message.vue";
+import InputItem from "@/components/inputItem.vue";
 import Settings from "@/components/settings.vue";
 import useMitt from "@/modules/mitt";
-import useMessages from '@/modules/messages';
+import useMessages from "@/modules/messages";
+import { RenderMessage } from "@/types/message/index" 
 
 export default defineComponent({
   name: "Messenger",
+
+  props: ["profile", "newMessages"],
+
   components: {
-    MessageItem,
+    Message,
     InputItem,
     Settings
   },
-  setup() {
+
+  setup(props) {
 
     // Listen for self-messages from inputItem.
     const { emitter } = useMitt();
 
     // Handle messages in view.
-    const { messages, updateMessageView } = useMessages();
-
-    // Update message view on new content.
-    const onNewContent = (_event: any, payload: any) => {
-      updateMessageView(payload.message)
-    }
+    const { messages, prepMessageView, updateMessageView } = useMessages();
 
     onMounted(() => {
+      const crimata_id = props.profile.crimata_id;
 
-      // Front end listener.
+      // Load the message history.
+      if (crimata_id === window.localStorage.getItem("last_usr")) {
+        prepMessageView(props.newMessages)
+      } else {
+        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)
+      });
 
-      // Back end listener.
-      window.ipcRenderer.on("render-message", onNewContent);
+      // Save the usr for next time.
+      window.localStorage.setItem("last_usr", crimata_id)
 
     });
 
@@ -58,11 +73,14 @@ export default defineComponent({
     return {
       messages
     };
+
   },
+
 });
 </script>
 
 <style lang="scss" scoped>
+
 #messenger {
   width: 100vw;
   height: 100vh;
index b9082bf0ec5f4715fd2cec3996a730e96c7e7bbb..a70b797e751e425bb6254948ec7485c2f3835e05 100644 (file)
@@ -26,7 +26,6 @@
 <script lang="ts">
 
   import { defineComponent, ref } from "vue";
-  import { useAuth } from "@/modules/auth";
   import  { useIpc } from "@/modules/ipc";
   import { logoutRequest } from '@/modules/message'; 
 
diff --git a/src/modules/auth.ts b/src/modules/auth.ts
deleted file mode 100644 (file)
index b095034..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-import { reactive, toRefs } from 'vue';
-import { useIpc } from './ipc';
-
-interface AuthState {
-    accessToken: string | null;
-    error?: Error;
-}
-
-const state = reactive<AuthState>({
-    accessToken: null,
-    error: undefined,
-});
-
-const AUTH_KEY = 'crimata_token';
-
-let token: string | null;
-
-token = window.localStorage.getItem(AUTH_KEY);
-if (token) {
-    state.accessToken = token;
-    window.localStorage.setItem(AUTH_KEY, token);
-}
-
-// Token authentication.
-const authToken = async () => {
-    console.log("Calling auth token!!")
-    const { invoke  } = useIpc();
-    try {
-        token = window.localStorage.getItem(AUTH_KEY);
-
-        if (!token) {
-            token = "no-token"
-        }
-
-        console.log(`Token ${token}`)
-        const res = await invoke('auth-session', token);
-        window.localStorage.setItem(AUTH_KEY, res);
-        state.accessToken = res;
-    }
-    catch(e) {
-        state.error = e;
-        console.log('ERROR: failed authentication');
-        window.localStorage.removeItem(AUTH_KEY);
-        state.accessToken = null;
-    }
-}
-
-// Gets called on socket open.
-window.ipcRenderer.on("auth", async (_event, _arg) => {
-    await authToken();
-});
-
-export const useAuth = () => {
-
-    const setToken = (token: string) => {
-        window.localStorage.setItem(AUTH_KEY, token);
-        state.accessToken = token;
-        state.error = undefined;
-    }
-
-    const logout = (): Promise<null> => {
-        window.localStorage.removeItem(AUTH_KEY);
-        return Promise.resolve(state.accessToken = null);
-    }
-
-    return {
-        setToken,
-        logout,
-        ...toRefs(state), // accessToken, error
-    }
-}
index f333d6a457384712919d0cf7601470fa202e0391..36658de32a77d54fe69e8ec2b1a8b6251f7f71c3 100644 (file)
@@ -1,5 +1,6 @@
 
 export const useIpc = () => {
+    
     const invoke = async (endpoint: string, payload: any) => {
         try {
             const res = await window.ipcRenderer.invoke(endpoint, payload);
@@ -10,10 +11,7 @@ export const useIpc = () => {
     }
 
     const post = (endpoint: string, payload: any) => {
-        window.postMessage({
-            endpoint: endpoint,
-            content: payload
-        }, '*')
+        window.ipcRenderer.send(endpoint, payload);
     };
 
     return {
index 4b0c8b7ad3973f9cc1edb35d9be79a8639e62062..a406cc5f895f52785852f0cd3ecc43f2c35fceea 100644 (file)
@@ -14,7 +14,7 @@ const updateMessage = (annotation: Annotation) => {
   message.content.text = annotation.text
 }
 
-const loadMessages = () => {
+const loadSavedMessages = () => {
   const rawData = window.localStorage.getItem("crimata_messages");
   if (rawData) {
     const messageData = JSON.parse(rawData)
@@ -58,51 +58,61 @@ const updateGrouping = () => {
   }
 }
 
-const { setScroll, updateScrollRef, adjustScroll } = useScroll("messenger");
 
-loadMessages()
-setTimeout(setScroll, 1000);
+export default function useMessages() {
 
+  // Scroll controller.
+  const { setScroll, updateScrollRef, adjustScroll } = useScroll("messenger");
 
-export default function useMessages() {
+  // Main function for updating the message view.
+  const updateMessageView = (message: RenderMessage | Annotation) => {
 
-    // Main function for updating the message view.
-    const updateMessageView = (message: RenderMessage | Annotation) => {
+    // Step 1: See if user is scrolled down.
+    updateScrollRef()
 
-      // Step 1: See if user is scrolled down.
-      updateScrollRef()
+    // Step 2: Add the new content to the view.
+    if ("content" in message) {
+      addMessage(message)
+    } else {
+      updateMessage(message)
+    }
 
-      // Step 2: Add the new content to the view.
-      if ("content" in message) {
-        addMessage(message)
-      } else {
-        updateMessage(message)
-      }
+    // Step 3: Pop off oldest message (if > 200).
+    if (messages.value.size >= 200) {
+      const oldest = Array.from(messages.value.keys()).shift();
+      messages.value.delete(oldest);
+    }
 
-      // Step 3: Pop off oldest message (if > 200).
-      if (messages.value.size >= 200) {
-        const oldest = Array.from(messages.value.keys()).shift();
-        messages.value.delete(oldest);
-      }
+    // Step 4: Update grouping.
+    updateGrouping()
 
-      // Step 4: Update grouping.
-      updateGrouping()
+    // Setp 5: Scroll the view (if scrolled down).
+    setTimeout(adjustScroll, 20);
 
-      // Setp 5: Scroll the view (if scrolled down).
-      setTimeout(adjustScroll, 20);
+    // Step 6: Save the view data.
+    saveMessages()
 
-      // Step 6: Save the view data.
-      saveMessages()
+  }
 
-    }
+  // Seed message view with message history.
+  const prepMessageView = (newMessages: RenderMessage[]) => {
+    console.log("MSGR:Prepping messenger view.")
 
-    const resetMessages = () => {
-      messages.value.clear()
-    }
+    // Load and render saved messages and immediately scroll to bottom.
+    loadSavedMessages()
+    setTimeout(setScroll.bind(false), 10);
 
-    return {
-      messages,
-      updateMessageView,
-      resetMessages
+    // Render new messages, then wait 1s to scroll.
+    if (newMessages.length) {
+      console.log("MSGR:Adding new messages")
+      newMessages.forEach(message => addMessage(message))
+      setTimeout(setScroll.bind(true), 1000);
     }
+  }
+
+  return {
+    messages,
+    prepMessageView,
+    updateMessageView
+  }
 }
\ No newline at end of file
index e9e6501f8d53e6dbb4ef931cf9b01b447f70cf0a..58370f718ce11ca4641a7180f40201a7be5e96dc 100644 (file)
@@ -5,13 +5,13 @@ export default function useScroll(element: string) {
   let isScrolledToBottom: boolean;
 
   // Set the initial scroll position.
-  const setScroll = () => {
+  const setScroll = (smooth: boolean) => {
     const view = document.getElementById(element)
 
     if (view) {
       view.scrollTo({
         top: view.scrollHeight - view.clientHeight,
-        behavior: 'smooth'
+        behavior: (smooth) ? 'smooth' : 'auto' 
       });
     }
   }
similarity index 100%
rename from src/modules/ws.ts
rename to src/modules/websockets.ts
index ce571273428baec007615dfb294628d20c785ad4..a1880f8d9a7d8e278639ab0572796419235b8d1b 100644 (file)
@@ -16,27 +16,30 @@ export interface ClientMessage {
     uid: string;
 }
 
+export interface SessionState {
+    key: string | boolean;
+    newMessages: RenderMessage[];
+}
+
 export interface AuthRequest {
     key: boolean | string;
     usr: boolean | string;
     pwd: boolean | string;
 }
 
-export interface LogoutRequest {
-    logout: boolean;
+export interface Profile {
+    crimata_id: string;
+    alias: string;
+    initials: string;
 }
 
-// Possible messages from server:
+export interface AuthProtocol {
+    key: boolean | string;
+    profile: boolean | Profile;
+}
 
-export interface Profile {
-    crimata_id: string;
-    title: string;
-    first: string;
-    middle: string;
-    last: string;
-    suffix: string | number;
-    nickname: string;
-    full: string;
+export interface LogoutRequest {
+    logout: boolean;
 }
 
 export interface Annotation {
index 26c34cbb89f4cdff551eb71ddc99c11db030d81c..fcf3113b2a3529dcd0cbfe20a38a2e08f21a3883 100644 (file)
@@ -1 +1 @@
-{"w":629,"h":500,"x":1423,"y":657}
\ No newline at end of file
+{"w":627,"h":801,"x":1462,"y":450}
\ No newline at end of file