From: Andrew Gundersen Date: Mon, 29 Mar 2021 15:15:54 +0000 (-0500) Subject: auth enhancements amongst other things X-Git-Tag: v0.9~8^2~1 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=85c3799fca3294f98ed55a6d551a1b1c14b46a91;p=mime-chat auth enhancements amongst other things --- diff --git a/session.json b/session.json new file mode 100644 index 0000000..50bed80 --- /dev/null +++ b/session.json @@ -0,0 +1 @@ +{"key":"0c116d29-8cfe-491f-a84d-1d021297cc69","newMessages":[]} \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index ce8fa5b..7bc0ee1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,6 @@ @@ -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 } } }) diff --git a/src/background/audio.ts b/src/background/audio.ts index c4cd6fd..aa57716 100644 --- a/src/background/audio.ts +++ b/src/background/audio.ts @@ -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 { } // 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 index 4ba8b7a..0000000 --- a/src/background/handlers.ts +++ /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 index 0000000..098c203 --- /dev/null +++ b/src/background/helpers.ts @@ -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 diff --git a/src/background/init.ts b/src/background/init.ts index a805b79..69a2938 100644 --- a/src/background/init.ts +++ b/src/background/init.ts @@ -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) diff --git a/src/background/session.ts b/src/background/session.ts index ece5915..828709a 100644 --- a/src/background/session.ts +++ b/src/background/session.ts @@ -1,62 +1,131 @@ /* * 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; + }); + +} diff --git a/src/background/window.ts b/src/background/window.ts index 246f5a1..5d180cc 100644 --- a/src/background/window.ts +++ b/src/background/window.ts @@ -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.") } diff --git a/src/components/controllers/audioCtrl.ts b/src/components/controllers/audioCtrl.ts index 0ae0fe8..8a73868 100644 --- a/src/components/controllers/audioCtrl.ts +++ b/src/components/controllers/audioCtrl.ts @@ -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...") } diff --git a/src/components/input.vue b/src/components/inputItem.vue similarity index 81% rename from src/components/input.vue rename to src/components/inputItem.vue index c2d2c90..b24d9c4 100644 --- a/src/components/input.vue +++ b/src/components/inputItem.vue @@ -26,25 +26,23 @@ 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 }; }, }); diff --git a/src/components/login.vue b/src/components/login.vue index 8382179..95cf62f 100644 --- a/src/components/login.vue +++ b/src/components/login.vue @@ -29,13 +29,12 @@ - +