From 0079c72ea023bee0622e5b566cd99c5755d6a8a5 Mon Sep 17 00:00:00 2001 From: Enrique Hernandez Date: Tue, 1 Jun 2021 09:50:25 -0400 Subject: [PATCH 01/14] send auth payload on websocket open close session on logout add socket reconnect on close --- .env | 4 ---- package.json | 1 - src/App.vue | 12 +++++++++--- src/background.ts | 5 ++--- src/background/authPayload.ts | 13 +++++++++++++ src/background/ipc/account.ts | 4 +++- src/background/ipc/session.ts | 4 ++-- src/background/session.ts | 21 ++++++++++++++++++--- src/background/websockets.ts | 34 +++++++++++++++++++--------------- src/components/login.vue | 10 +++++++++- src/ipcRend/session.ts | 6 ++++-- src/modules/http.ts | 2 +- tests/server.js | 5 +++-- 13 files changed, 83 insertions(+), 38 deletions(-) delete mode 100644 .env create mode 100644 src/background/authPayload.ts diff --git a/.env b/.env deleted file mode 100644 index 6aa5026..0000000 --- a/.env +++ /dev/null @@ -1,4 +0,0 @@ - -BUSINESS_URL="http://localhost:3000" - -PLATFORM_URL="ws://127.0.0.1:8760" diff --git a/package.json b/package.json index 38c1348..ac64b5a 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "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", diff --git a/src/App.vue b/src/App.vue index 5a29c17..99ffe6a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -85,8 +85,14 @@ export default defineComponent({ } + const onSessionAuthFail = (_event: IpcRendererEvent, _payload: null) => { + clearProfile(); + } + onMounted(async () => { console.log("APP:mounted."); + + window.ipcRenderer.on("session-auth-fail", onSessionAuthFail) window.ipcRenderer.on("update-state", updateState) window.ipcRenderer.on("update_available", () => { console.log('testing auto update'); @@ -99,17 +105,16 @@ export default defineComponent({ postMount(); try { - const profile = await invokeProfile() as Profile; setProfile(profile); } catch(e) { - console.log('[AUTH]', e); + console.log('[REND] Auth:', e); clearProfile(); } finally { ready.value = true; if (profile.value.crimataId) { // start session - postInitSession(profile.value.crimataId); + postInitSession(); } } @@ -117,6 +122,7 @@ export default defineComponent({ onUnmounted(() => { window.ipcRenderer.removeAllListeners("update-state"); + window.ipcRenderer.removeAllListeners("session-auth-fail"); }) return { diff --git a/src/background.ts b/src/background.ts index 1ecee83..977c265 100644 --- a/src/background.ts +++ b/src/background.ts @@ -7,7 +7,6 @@ import { initApp } from './background/init'; import { protocol } from "electron"; -require('dotenv').config() // Scheme must be registered before the app is ready protocol.registerSchemesAsPrivileged([ @@ -18,9 +17,9 @@ protocol.registerSchemesAsPrivileged([ const isDev = require('electron-is-dev'); // NOTE Program Begins Here -(async () => { +(() => { console.log('Starting Crimata electron app.'); - await initApp(isDev); + initApp(isDev); })(); diff --git a/src/background/authPayload.ts b/src/background/authPayload.ts new file mode 100644 index 0000000..61d68b6 --- /dev/null +++ b/src/background/authPayload.ts @@ -0,0 +1,13 @@ + +import { store } from "@/background/store"; + +interface PlatformAuthProtocol { + key: string; + crimata_id: string; +} + +export const getAuthPayload = (): PlatformAuthProtocol => ({ + key: store.get('key'), + crimata_id: store.get('crimataId') +}); + diff --git a/src/background/ipc/account.ts b/src/background/ipc/account.ts index c336ac3..5898509 100644 --- a/src/background/ipc/account.ts +++ b/src/background/ipc/account.ts @@ -5,6 +5,7 @@ import { Profile } from "@/types"; import { submit, fetchProfile, logout } from "@/api/account"; import { ipcMain, IpcMainInvokeEvent } from "electron"; import { store } from "@/background/store"; +import { endSession } from "@/background/session"; const parseAuthRes = (authRes: any) => { @@ -70,7 +71,7 @@ const onLogin = async ( resolve(parsed.profile); } catch(e) { - console.log('[API]', e.response); + console.log('[API]', e); reject(new Error('Failed to authenticate')); } } @@ -94,6 +95,7 @@ const onLogout = async ( store.delete('crimataId'); // TODO: kill crimata platform session + endSession(); resolve(); } catch(e) { diff --git a/src/background/ipc/session.ts b/src/background/ipc/session.ts index a2496dc..7c42ea2 100644 --- a/src/background/ipc/session.ts +++ b/src/background/ipc/session.ts @@ -10,12 +10,12 @@ import { ClientMessage } from "@/types"; // Instantiate socket session with crimata-platorm. const onSessionInit = ( _event: IpcMainInvokeEvent, - cid: string + _payload: null ): void => { console.log('[IPC]: init-session'); - initSession(cid); + initSession(); initAudioIO(); } diff --git a/src/background/session.ts b/src/background/session.ts index fc3e4fd..c90d32a 100644 --- a/src/background/session.ts +++ b/src/background/session.ts @@ -10,6 +10,7 @@ import { backgroundMitt } from "@/modules/emitter"; import { ipcEmit, loadState } from './helpers'; +import WebSocket from 'ws'; import useWebSockets from "./websockets"; @@ -22,11 +23,17 @@ let win = true; // Info saved to json on quit (key, newMessages). let state: SessionState; +let socket: WebSocket | null = null; + // Calls appropriate endpoint for a server message. const onMessage = (data: string): void => { let message = JSON.parse(data); - console.log('received new message', message); + + if (message === "CLOSE_AUTH_FAIL") { + ipcEmit("session-auth-fail", null) + return; + } // Standard message. if (message.content) { @@ -85,16 +92,24 @@ export const sendMessage = (payload: Record): void => { } +export const endSession = (): void => { + if (socket) { + socket.close(); + socket = null; + } +} + // Call this to initialize session with Crimata servers. -export const initSession = (cid: string): void => { +export const initSession = (): void => { console.log("SESS:Creating new session.") // Load Json or createState. state = loadState("session.json"); // Open socket connection. - createSocket(); + if (!socket) + socket = createSocket(); // Keep win up-to-date. backgroundMitt.on('window-active', (state: boolean) => { diff --git a/src/background/websockets.ts b/src/background/websockets.ts index 9fcc5e8..70eb7ff 100644 --- a/src/background/websockets.ts +++ b/src/background/websockets.ts @@ -1,10 +1,12 @@ + "use strict"; import WebSocket from 'ws'; +import { getAuthPayload } from "./authPayload"; let socket: WebSocket; -const socketUrl = process.env.PLATFORM_URL; +const socketUrl = "ws://127.0.0.1:8760" // Run every time we want to connect to backend. export default function useWebSockets( @@ -41,7 +43,8 @@ export default function useWebSockets( const onOpen = (_event: WebSocket.OpenEvent) => { console.log("WS:Connected to WS Server!"); - + const jwt = getAuthPayload(); + socket.send(JSON.stringify(jwt)); if (openCallback) openCallback(); } @@ -54,29 +57,30 @@ export default function useWebSockets( } - const onClose = (_event: WebSocket.CloseEvent) => { - console.log("WS:Socket closed normally.") + const onClose = (event: WebSocket.CloseEvent) => { + console.log("WS:Socket closed normally.", event.wasClean) + if (!event.wasClean) { + setTimeout(createSocket, 1000); + } } // Reconnect automatically on error. const onError = (event: WebSocket.ErrorEvent) => { console.log("WS:WebSocket error: ", event.message); - - console.log("Attempting reconnect in 1s.") - setTimeout(createSocket, 1000) } - const createSocket = () => { - if (socketUrl) - socket = new WebSocket(socketUrl) + const createSocket = (): WebSocket => { + + socket = new WebSocket(socketUrl); // Add listeners. - socket.addEventListener("open", onOpen) - socket.addEventListener("message", onServerMessage) - socket.addEventListener("close", onClose) - socket.addEventListener("error", onError) + socket.addEventListener("open", onOpen); + socket.addEventListener("message", onServerMessage); + socket.addEventListener("close", onClose); + socket.addEventListener("error", onError); + + return socket; - console.log("WS:New socket created.") } return { diff --git a/src/components/login.vue b/src/components/login.vue index 8232a67..9467a21 100644 --- a/src/components/login.vue +++ b/src/components/login.vue @@ -40,13 +40,14 @@ import { authRequest } from '@/modules/message'; import { useProfile } from '@/modules/auth'; import { invokeLogin } from "@/ipcRend/account"; import { Profile } from "@/types"; +import { postInitSession } from "@/ipcRend/session"; export default defineComponent({ name: "Login", setup() { - const { setProfile } = useProfile(); + const { profile, setProfile } = useProfile(); const usr = ref(""); const pwd = ref(""); @@ -63,6 +64,13 @@ export default defineComponent({ setProfile(profile); } catch(e) { + console.log('Error login in.') + } finally{ + + if (profile.value.crimataId) { + // start session + postInitSession(); + } } } diff --git a/src/ipcRend/session.ts b/src/ipcRend/session.ts index 966b121..a5ff5fc 100644 --- a/src/ipcRend/session.ts +++ b/src/ipcRend/session.ts @@ -11,11 +11,13 @@ export const postMount = (): void => ( ); -export const postInitSession = (cid: string): void => ( - post("init-session", cid) +export const postInitSession = (): void => ( + post("init-session", null) ); export const postMessage = (payload: ClientMessage): void => ( post('client-message', payload) ); + + diff --git a/src/modules/http.ts b/src/modules/http.ts index de241e0..a9519a8 100644 --- a/src/modules/http.ts +++ b/src/modules/http.ts @@ -3,7 +3,7 @@ import axios, { AxiosRequestConfig } from 'axios'; const preFix = '/api'; -const baseURL = process.env.BUSSINESS_URL + preFix; +const baseURL = "http://127.0.0.1:3000" + preFix; interface Request { diff --git a/tests/server.js b/tests/server.js index 6731be9..9616e8a 100644 --- a/tests/server.js +++ b/tests/server.js @@ -6,6 +6,7 @@ const wss = new WebSocket.Server({ let auth = false; wss.on("connection", function connection(ws, req) { + ws.on("message", function incoming(message) { console.log(message) @@ -19,13 +20,13 @@ wss.on("connection", function connection(ws, req) { auth = true; } else { const parsed = JSON.parse(message); - console.log('got a message', message) + console.log('got a message', message); } } else { const parsed = JSON.parse(message); - console.log('parsed', parsed) + console.log('parsed', parsed); } }); From 25b03d501cd0d82075178cb8796274e44cde156a Mon Sep 17 00:00:00 2001 From: riqo Date: Tue, 8 Jun 2021 07:57:57 -0500 Subject: [PATCH 02/14] add websocket ping connection check --- src/background/ipc/account.ts | 2 +- src/background/websockets.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/background/ipc/account.ts b/src/background/ipc/account.ts index 5898509..4bf6411 100644 --- a/src/background/ipc/account.ts +++ b/src/background/ipc/account.ts @@ -42,7 +42,7 @@ const onProfile = async ( resolve(parsed.profile); } catch(e) { - reject(new Error('Failed to fetch profile.')); + reject(new Error('Unable to authenticate and fetch account profile.')); } }) ) diff --git a/src/background/websockets.ts b/src/background/websockets.ts index 70eb7ff..dec0203 100644 --- a/src/background/websockets.ts +++ b/src/background/websockets.ts @@ -3,11 +3,17 @@ import WebSocket from 'ws'; import { getAuthPayload } from "./authPayload"; +import { ipcEmit } from './helpers'; let socket: WebSocket; const socketUrl = "ws://127.0.0.1:8760" +const _connectionCheckTimeout = 4000; +const _reconnectTimeout = 1000; +let _connectionCheckInterval: ReturnType; + + // Run every time we want to connect to backend. export default function useWebSockets( receiveCallback: (s: string) => void, @@ -45,6 +51,20 @@ export default function useWebSockets( console.log("WS:Connected to WS Server!"); const jwt = getAuthPayload(); socket.send(JSON.stringify(jwt)); + + // ping server + _connectionCheckInterval = setInterval(() => { + + if (socket) socket.ping(null, true, (e: Error) => { + if (e) { + ipcEmit('connection-alive', false); + socket.close(); + setTimeout(createSocket, 1000); + } + }); + + }, _connectionCheckTimeout); + if (openCallback) openCallback(); } @@ -59,7 +79,11 @@ export default function useWebSockets( const onClose = (event: WebSocket.CloseEvent) => { console.log("WS:Socket closed normally.", event.wasClean) + + clearInterval(_connectionCheckInterval); + if (!event.wasClean) { + ipcEmit('connection-alive', false); setTimeout(createSocket, 1000); } } @@ -69,8 +93,11 @@ export default function useWebSockets( console.log("WS:WebSocket error: ", event.message); } + const createSocket = (): WebSocket => { + if (_connectionCheckInterval) clearInterval(_connectionCheckInterval); + socket = new WebSocket(socketUrl); // Add listeners. @@ -78,6 +105,9 @@ export default function useWebSockets( socket.addEventListener("message", onServerMessage); socket.addEventListener("close", onClose); socket.addEventListener("error", onError); + socket.addEventListener("pong", () => { + ipcEmit('connection-alive', true); + }); return socket; From 8203a90de56851ee26b2df5b3e698ff306fb1fcb Mon Sep 17 00:00:00 2001 From: riqo Date: Wed, 9 Jun 2021 09:03:52 -0500 Subject: [PATCH 03/14] add initial state shape --- src/composables/store.ts | 41 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/composables/store.ts b/src/composables/store.ts index 16f8409..a74d209 100644 --- a/src/composables/store.ts +++ b/src/composables/store.ts @@ -1,16 +1,51 @@ const Store = require('electron-store'); + const schema = { + // should be separate, used to authenticate against business and platform key: { type: 'string', }, - crimataId: { - type: 'string' - } + + profile: { + type: + }, + + messages: { + new: Message[], + saved: ViewMessages[] + }, + }; export const store = new Store({ schema, encryptionKey: "super user test" }); + + +export const emitInitialState = () => { + + const profile = store.get(profile); + const messages = store.get(messages); + + emit("initial-state", { + messages, + profile + }); +} + + +/* add a message to state.messages */ + +export function addMessage(message: Message) { + + // update state + // TODO: add logic to handle new vs saved + state.messages.new.push(message); + state.messages.saved.push(message); + + saveState(); + // emit new message +} From 9e8641b4def1a49c8e607475b1dfe1d8a2176b4f Mon Sep 17 00:00:00 2001 From: Andrew Gundersen Date: Sat, 12 Jun 2021 09:49:41 -0500 Subject: [PATCH 04/14] more changes --- src/api/account.ts | 27 +++- src/composables/canvas.ts | 36 +++++ src/composables/store.ts | 16 -- src/composables/websockets.ts | 54 +++---- src/main.ts | 60 ++++---- src/render/App.vue | 12 +- .../controllers/messenger.control.ts | 139 +++--------------- src/render/components/messenger.vue | 20 ++- src/session.ts | 58 ++++---- src/state.ts | 29 ---- src/types.ts | 7 - 11 files changed, 170 insertions(+), 288 deletions(-) create mode 100644 src/composables/canvas.ts delete mode 100644 src/composables/store.ts delete mode 100644 src/state.ts diff --git a/src/api/account.ts b/src/api/account.ts index de1335b..98d3c69 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -4,20 +4,33 @@ import axios from "axios"; const { post } = useHttp(); -export const submit = async (email: string, password: string) => ( - await post('/account/login', { email, password }) -) +export const usrPwdAuth = async (email: string, password: string) => { -export const fetchAccount = async (email: string, token: string) => ( - await axios({ + try { + return await post('/account/login', { email, password }) + + } catch (e) { + return null; + } + +} + +export const tokenAuth = async (cid: string, token: string) => { + + try { + return await axios({ url: "http://127.0.0.1:3000/api/account/profile", headers: { Cookie: `jwt=${token}` }, method: 'GET', data: { - email, + cid, } }) -) + } catch (e) { + return null; + } + +} \ No newline at end of file diff --git a/src/composables/canvas.ts b/src/composables/canvas.ts new file mode 100644 index 0000000..c85f7ed --- /dev/null +++ b/src/composables/canvas.ts @@ -0,0 +1,36 @@ + + + +export default class Canvas { + + messages: Message[]; + + /* seed canvas with messages on init */ + constructor(messages: Message[]) { + this.messages = messages; + ipcEmit("seed-view", this.messages); + } + + /* add a new message to the canvas */ + add(message: Message) { + this.messages.push(message); + ipcEmit("update-view", message); + } + + /* update an existing message */ + update(message: Message) { + + /* get the target message */ + let target_message = this.messages.filter((m: Message) => { + return m.uid = message.uid; + })[0]; + + /* replace the target message */ + if (target_message) { + target_message = message; + ipcEmit("update-view", message); + } + + } + +} diff --git a/src/composables/store.ts b/src/composables/store.ts deleted file mode 100644 index 16f8409..0000000 --- a/src/composables/store.ts +++ /dev/null @@ -1,16 +0,0 @@ - -const Store = require('electron-store'); - -const schema = { - key: { - type: 'string', - }, - crimataId: { - type: 'string' - } -}; - -export const store = new Store({ - schema, - encryptionKey: "super user test" -}); diff --git a/src/composables/websockets.ts b/src/composables/websockets.ts index 779f00b..d034027 100644 --- a/src/composables/websockets.ts +++ b/src/composables/websockets.ts @@ -3,7 +3,7 @@ import WebSocket from 'ws'; -export default function useWebSockets(receiveCallback: (s: string) => void, openCallback?: () => void) { +export default function useWebSockets(onMessageCallback: (s: string) => void) { let socket: WebSocket | null = null; @@ -19,36 +19,25 @@ export default function useWebSockets(receiveCallback: (s: string) => void, open }); } - const onOpen = (_event: WebSocket.OpenEvent) => { - console.log("WS:Connected to WS Server!"); - if (openCallback) openCallback(); - } - - const onServerMessage = (event: WebSocket.MessageEvent) => { - console.log("WS:Message received: ", event.data); - receiveCallback(event.data.toString()) - } - - const onClose = (event: WebSocket.CloseEvent) => { - console.log("WS:Socket closed normally.") - } - - // Reconnect automatically on error. - const onError = (event: WebSocket.ErrorEvent) => { - console.log("WS:WebSocket error: ", event.message); - console.log("Attempting reconnect in 1s.") - setTimeout(createSocket, 1000); - } - - const createSocket = (socketUrl: string) => { + const connect = (socketUrl: string, secret: string) => { + /* create a new socket */ socket = new WebSocket(socketUrl) - // Add listeners. - socket.addEventListener("open", onOpen); - socket.addEventListener("message", onServerMessage); - socket.addEventListener("close", onClose); - socket.addEventListener("error", onError); + /* add event listeners */ + + socket.on("open", () => { + if (socket) + socket.send(secret); + }); + + socket.on("message", (event: WebSocket.MessageEvent) => { + onMessageCallback(event.data.toString()) + }); + + socket.on("close", () => { + return + }); } @@ -59,15 +48,10 @@ export default function useWebSockets(receiveCallback: (s: string) => void, open } } - const checkConnection = () => { - return true; - } - return { - createSocket, + connect, send, - close, - checkConnection + close }; } diff --git a/src/main.ts b/src/main.ts index 744643d..9d6414c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,37 +9,29 @@ * */ -import { fetchAccount, submit } from "@/api/account"; +import { tokenAuth, usrPwdAuth } from "@/api/account"; import { launchSession, endSession } from "@/session"; import useIpc from "@/ipc/index"; import store from "@/composables/store"; -/* user profile, signals whether user is logged in */ -let auth: Profile | null = null; - /* authenticate the user */ export async function authenticate(email: string, password: string) { /* attempt normal login */ - try { - auth = await submit(email, password); - } catch (e) { - console.log(e); - } + const platformKey, token, crimataId = await usrPwdAuth(email, password); - /* launch if profile */ - if (auth) { - launchSession(auth); - } + /* launch if successful */ + if (platformKey) + launchSession(platformKey, crimataId); + + /* save the token */ + store.set("token", token); } /* logout the user, end the session */ export function deauthenticate() { - /* set profile back to null */ - auth = null; - /* terminate the session */ endSession(); @@ -47,26 +39,24 @@ export function deauthenticate() { export default async function main() { - /* launch browser window */ - // await createWindow(); - - /* attempt key-based authentication with business api */ - const token = store.get('key', null); - const crimataId = store.get('crimataId', null); - - try { - const res = await fetchAccount(crimataId, token); - auth = parseAuthRes(res); - } catch (e) { - console.log('[MAIN]', e); - } - - /* connect to Crimata, or listen for manual login req */ - if (auth) { - launchSession(auth); - } - /* initiate controls for frontend to use when needed */ useIpc(); + /* launch browser window */ + await createWindow(); + + /* attempt to get a login token from the store */ + const token = store.get("token"); + + /* try to login with it, returns platform secret and new token on success */ + if (token) + const newToken, profile = await tokenAuth(token); + + /* if secret, we launch a session */ + if (newToken) + launchSession(newToken, profile); + + /* finally, save the most recent token */ + store.set("token", newToken); + } \ No newline at end of file diff --git a/src/render/App.vue b/src/render/App.vue index e5c477d..d90bcc0 100644 --- a/src/render/App.vue +++ b/src/render/App.vue @@ -6,8 +6,9 @@ @@ -39,13 +40,13 @@ export default defineComponent({ setup() { - const state: Ref; + const crimataId = ref(false); onMounted(async () => { console.log("[APP]:mounted."); /* listen for auth related messages */ - window.addEventListener("update-state", (event: any) => { + window.addEventListener("update-auth", (event: any) => { state.value = event.data; }); @@ -56,7 +57,7 @@ export default defineComponent({ }); return { - profile + crimataId } } }) @@ -68,7 +69,6 @@ export default defineComponent({ html, body { margin: 0; padding: 0; - // Background color set in window.ts } #app { diff --git a/src/render/components/controllers/messenger.control.ts b/src/render/components/controllers/messenger.control.ts index 63f15ee..9ae7d47 100644 --- a/src/render/components/controllers/messenger.control.ts +++ b/src/render/components/controllers/messenger.control.ts @@ -1,143 +1,38 @@ import { ref } from 'vue'; -import invokeSavedMessages from "@/render/ipc"; import useScroll from "@/render/composables/scroll"; -const messages = ref(new Map()); +const canvas = ref(); -function getTimeStamp(): number { - const currentdate = new Date(); - return currentdate.getTime(); +/* seed the canvas with messages */ +const seedCanvas = (messages: Message[]) => { + canvas.value = messages; } -const newViewMessage = (message: Message): ViewMessage => { - return { - text: message.text, - context: message.context, - audio: message.audio, - from: message.from, - uid: message.uid, - time: getTimeStamp(), - isChild: "none", - seen: false, - newMessage: false - }; -} - -const addMessage = (message: Message, newMessage=false) => { - const viewMessage = newViewMessage(message); - if (newMessage) viewMessage.newMessage = true; - messages.value.set(viewMessage.uid, viewMessage); +const addMessage = (message: Message) => { + canvas.value.push(message); } const updateMessage = (message: Message) => { - const viewMessage = messages.value.get(message.uid); - viewMessage.context = message.context; - viewMessage.text = message.text; -} -const loadSavedMessages = async () => { - const messageData = await invokeSavedMessages(); - messages.value = new Map(Object.entries(messageData)); -} + let target_message = canvas.value.filter((m: Message) => { + return m.uid = message.uid; + })[0]; -const saveMessages = () => { - const messageData = Object.fromEntries(messages.value); - // must save to json. -} - -const pruneMessages = (limit=200) => { - if (messages.value.size >= limit) { - const oldest = Array.from(messages.value.keys()).shift(); - messages.value.delete(oldest); - } -} - -const updateGrouping = () => { - - const isSimmilar = (messageA: ViewMessage, messageB: ViewMessage) => { - if ((Math.abs(messageA.time - messageB.time) < 20000) && (messageA.from == messageB.from) && (messageA.context == messageB.context)) { - return true - } - return false + if (target_message) { + target_message = message; } - const refs = Array.from(messages.value.keys()) - - // Get the last three messages. - const first = messages.value.get(refs[refs.length - 1]) - const second = messages.value.get(refs[refs.length - 2]) - const third = messages.value.get(refs[refs.length - 3]) - - // If messages are simmilar, update the classes. - if ((first) && (second)) { - if (isSimmilar(first, second)) { - first.isChild = "last" // i.e. last in group. - second.isChild = "first" - - if (third) { - if ((third.isChild == "first") || (third.isChild == "middle")) { - second.isChild = "middle" - } - } - } - } } export default function useMessages() { const { updateScrollRef, adjustScroll } = useScroll("messenger"); - /* Given new message object, update the view accordingly */ - const updateMessageView = (newMessages: Message[]) => { - - const bottom = updateScrollRef(); // see if the user is scrolled down - - // take each message and apply view - newMessages.forEach((message: Message) => { - - // add or update message depending - if (messages.value.has(message.uid)) { - updateMessage(message); - } else addMessage(message); - - pruneMessages(); // pop off old messages from view - updateGrouping(); // group like message together - - if (bottom) adjustScroll(); // only scroll if user was at bottom - - saveMessages(); - - }); - - } - return { - messages, - updateMessageView, - loadSavedMessages - } + canvas, + seedCanvas, + addMessage, + updateMessage + }; -} - - - // // Seed message view with message history. - // const prepMessageView = async (newMessages: Message[]) => { - // console.log("MSGR:Prepping messenger view.") - - // // Load and render saved messages and immediately scroll to bottom. - // await loadSavedMessages(); - // setTimeout(setScroll.bind(false), 10); - - // // Render new messages, then wait 1s to scroll. - // if (newMessages.length) { - - // console.log("MSGR:Adding new messages") - - // newMessages.forEach(message => { - // addMessage(message, true); - // }) - - // setTimeout(setScroll.bind(true), 1000); - - // } - // } \ No newline at end of file +} \ No newline at end of file diff --git a/src/render/components/messenger.vue b/src/render/components/messenger.vue index 77d54f3..4ea2d5b 100644 --- a/src/render/components/messenger.vue +++ b/src/render/components/messenger.vue @@ -11,6 +11,7 @@ v-for="message in messages" :text="message.text" :context="message.context" + :child :key="message[0]" /> @@ -28,7 +29,7 @@ import useMessages from "@/render/composables/messages"; export default defineComponent({ name: "Messenger", - props: ["state"], + props: ["profile", "messages"], components: { Message, @@ -43,12 +44,19 @@ export default defineComponent({ onMounted(() => { - /* populate the message view with existing messages */ - updateMessageView(state.savedMessages, state.newMessages); + /* seed messages */ + window.ipcRenderer.on("init-messages", (e_: any, payload: any) => { + seedMessages(payload.messages); + }); - /* wait and listen for new messages to come in */ - window.ipcRenderer.on("new-message", (_e: any, payload: any) => { - updateMessageView(payload.message); + /* add a new message */ + window.ipcRenderer.on("add-message", (_e: any, payload: any) => { + addMessage(payload.message); + }); + + /* update an existing message */ + window.ipcRenderer.on("update-message", (_e: any, payload: any) => { + updateMessage(payload.message); }); }); diff --git a/src/session.ts b/src/session.ts index 0a893d8..afebe5b 100644 --- a/src/session.ts +++ b/src/session.ts @@ -3,7 +3,11 @@ import useAudio from "@/audio"; -import { loadState, saveState, emitState } from "@/state"; +import Canvas from "@/composables/convas"; +import ipcEmit from "@/composables/emitter"; + +/* data structure of messages that's tied to the UI */ +let msgrState: UIState | null = null; /* start and stop audio functionality */ const { initAudio, closeAudio } = useAudio(); @@ -12,53 +16,57 @@ const { initAudio, closeAudio } = useAudio(); * Controls for interfacing with the platform. * Takes an onMessage callback which we define below. */ -const { connect, send, close } = usePlatform((message: Message) => { +const { connect, send, close } = useWebsockets((content: any) => { - /* add the message to the state */ - addMessage(message); + /* if the platform fails to authenticate, we must back down */ + if (content === "auth_error") { + deauthenticate(); + return; + } - /* push the message to the browser */ - if (win) emit("new-message", message); + /* on init, platform sends state, used to init canvas */ + if (isInitMessage(content)) { + uiState.set(content); + } + + + else if (isAddMessage(content)) { + uiState.add(content); + } + + else { + uiState.update(content); + } }); /* send a message to the platform */ export function sendMessage(message: Message) { - /* add the message to the state */ - addMessage(message); - - /* push the message to the browser */ - if (win) emit("new-message", message); - /* socket send */ send(message); } /* launch a new session (the main process for authenticated users) */ -export function launchSession(profile: Profile) { - - /* load any previously saved state for that user */ - loadState(profile); +export function launchSession(platformKey: string, crimata_id: string) { /* connect to the platform */ - connect(profile); + connect(PLATFORM_URL, platformKey); /* initialize the audio streams */ - // initAudio(); + initAudio(); - /* finally we can push state to browser */ - if (win) emitState(); + /* push profile to window */ + if (win) + ipcEmit("update-auth", crimata_id); } export function endSession() { - closeAudioStreams(); + closeAudio(); - closeSocket(); + close(); - state.clear(); - -} \ No newline at end of file +} diff --git a/src/state.ts b/src/state.ts deleted file mode 100644 index 1d6ce02..0000000 --- a/src/state.ts +++ /dev/null @@ -1,29 +0,0 @@ -const Store = require('electron-store'); - -/* simple data persistance */ -const store = new Store; - -/* state of the session (e.g. profile and messages for now) */ -let state: State | null = null; - -/* load saved state in electron store for given user */ -export function loadState(profile: Profile) { - state = store.get("state", null); -} - -/* add a message to state.messages */ -export function addMessage(message: Message) { - if (state) { - state.messages.push(message); - saveState(); - } -} - -export function saveState() { - store.set("state", state); -} - -export function emitState() { - emit("update-state", state); -} - diff --git a/src/types.ts b/src/types.ts index b437ffa..e8682b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,8 +9,6 @@ interface Message { interface ViewMessage extends Message { child: string; - seen: boolean; - newMessage: boolean; } interface WindowState { @@ -26,11 +24,6 @@ interface Profile { initials: string; } -interface State { - profile: Profile | null; - messages -} - interface LoginPayload { email: string; password: string; From d851db2fccf166927d862756eb7ecaf0db3934d3 Mon Sep 17 00:00:00 2001 From: riqo Date: Mon, 14 Jun 2021 08:52:01 -0500 Subject: [PATCH 05/14] update sockets, account, auth --- build/config.gypi | 79 ------------------- package.json | 4 +- public/index.html | 6 +- src/account.ts | 105 +++++++++++++++++++++++++ src/api/account.ts | 49 ++++++------ src/auth.ts | 13 ++++ src/composables/http.ts | 7 +- src/composables/ipcHandler.ts | 50 ++++++++++++ src/composables/json.ts | 10 ++- src/composables/store.ts | 19 +++++ src/composables/websockets.ts | 49 +++++++++--- src/config.ts | 17 +++++ src/init.ts | 4 +- src/ipc/account.ts | 140 ++++++---------------------------- src/ipc/index.ts | 23 +++++- src/ipc/session.ts | 6 +- src/main.ts | 60 +++++---------- src/session.ts | 49 +++++++----- src/types.ts | 23 ++++++ src/window.ts | 20 ++--- 20 files changed, 409 insertions(+), 324 deletions(-) delete mode 100644 build/config.gypi create mode 100644 src/account.ts create mode 100644 src/auth.ts create mode 100644 src/composables/ipcHandler.ts create mode 100644 src/composables/store.ts create mode 100644 src/config.ts diff --git a/build/config.gypi b/build/config.gypi deleted file mode 100644 index 6f84ed7..0000000 --- a/build/config.gypi +++ /dev/null @@ -1,79 +0,0 @@ -# Do not edit. File was generated by node-gyp's "configure" step -{ - "target_defaults": { - "cflags": [], - "default_configuration": "Release", - "defines": [], - "include_dirs": [], - "libraries": [] - }, - "variables": { - "asan": 0, - "build_v8_with_gn": "false", - "coverage": "false", - "dcheck_always_on": 0, - "debug_nghttp2": "false", - "debug_node": "false", - "enable_lto": "false", - "enable_pgo_generate": "false", - "enable_pgo_use": "false", - "error_on_warn": "false", - "force_dynamic_crt": 0, - "host_arch": "x64", - "icu_data_in": "../../deps/icu-tmp/icudt67l.dat", - "icu_endianness": "l", - "icu_gyp_path": "tools/icu/icu-generic.gyp", - "icu_path": "deps/icu-small", - "icu_small": "false", - "icu_ver_major": "67", - "is_debug": 0, - "llvm_version": "0.0", - "napi_build_version": "6", - "node_byteorder": "little", - "node_debug_lib": "false", - "node_enable_d8": "false", - "node_install_npm": "true", - "node_module_version": 83, - "node_no_browser_globals": "false", - "node_prefix": "/", - "node_release_urlbase": "https://nodejs.org/download/release/", - "node_shared": "false", - "node_shared_brotli": "false", - "node_shared_cares": "false", - "node_shared_http_parser": "false", - "node_shared_libuv": "false", - "node_shared_nghttp2": "false", - "node_shared_openssl": "false", - "node_shared_zlib": "false", - "node_tag": "", - "node_target_type": "executable", - "node_use_bundled_v8": "true", - "node_use_dtrace": "true", - "node_use_etw": "false", - "node_use_node_code_cache": "true", - "node_use_node_snapshot": "true", - "node_use_openssl": "true", - "node_use_v8_platform": "true", - "node_with_ltcg": "false", - "node_without_node_options": "false", - "openssl_fips": "", - "openssl_is_fips": "false", - "shlib_suffix": "83.dylib", - "target_arch": "x64", - "v8_enable_31bit_smis_on_64bit_arch": 0, - "v8_enable_gdbjit": 0, - "v8_enable_i18n_support": 1, - "v8_enable_inspector": 1, - "v8_enable_pointer_compression": 0, - "v8_no_strict_aliasing": 1, - "v8_optimized_debug": 1, - "v8_promise_internal_field_count": 1, - "v8_random_seed": 0, - "v8_trace_maps": 0, - "v8_use_siphash": 1, - "want_separate_host_toolset": 0, - "xcode_version": "11.0", - "nodedir": "/Users/Enrique/Library/Caches/node-gyp/14.4.0", - "standalone_static_library": 1 - } -} diff --git a/package.json b/package.json index 38c1348..2b4b7fd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "postinstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps" }, - "main": "background.js", + "main": "init.js", "dependencies": { "@google-cloud/speech": "^4.2.0", "@types/animejs": "^3.1.2", @@ -73,7 +73,7 @@ "lintOnSave": false, "pluginOptions": { "electronBuilder": { - "preload": "src/preload.ts", + "preload": "src/renderer/preload.ts", "builderOptions": { "appId": "com.crimata.ElectronUpdaterApp", "artifactName": "${productName}-${version}.${ext}", diff --git a/public/index.html b/public/index.html index 48809d8..8f79d27 100644 --- a/public/index.html +++ b/public/index.html @@ -11,11 +11,7 @@ - -
+
diff --git a/src/account.ts b/src/account.ts new file mode 100644 index 0000000..4884135 --- /dev/null +++ b/src/account.ts @@ -0,0 +1,105 @@ + +import { postAuth, postLogin, postLogout } from "@/api/account"; +import { endSession, launchSession } from "@/session"; +import { getToken, clearToken, setToken } from "@/composables/store"; +import { parseAuthRes } from "./auth"; + +export const accountAuth = async (): Promise => { + + /* attempt to get a login token from the store */ + const token = getToken(); + + /* try to login with it, returns platform secret and new token on success */ + if (token) { + try { + + const res = await postAuth(token); + + const parsed = parseAuthRes(res); + + setToken(parsed.token) + + return { + profile: parsed.profile, + token: parsed.token + }; + + } catch(e) { + console.log('[ACCOUNT]', e); + clearToken(); + throw(new Error('Failed to authenticate.')); + + } + } else { + throw(new Error('Unable to authenticate.')); + } +}; + +export const accountLogin: IpcHandlerCallback = async (payload) => { + const account = payload as AccountCredentials; + try { + + // attempt login with email password + const res = await postLogin(account.email, account.password); + const parsed = parseAuthRes(res); + + // save jwt token and profile + setToken(parsed.token) + + // launch session + launchSession(parsed.token) + + // return profile to renderer + return parsed.profile; + + } catch(e) { + throw e; + } +} + +// export const accountLogin = async (account: Account): Promise => { +// +// try { +// +// // attempt login with email password +// const res = await postLogin(account.email, account.password); +// const parsed = parseAuthRes(res); +// +// // save jwt token and profile +// setToken(parsed.token) +// +// // launch session +// launchSession(parsed.token) +// +// // return profile to renderer +// return parsed.profile; +// +// } catch(e) { +// console.log('[ACCOUNT]', e); +// throw (new Error('Failed to authenticate')); +// } +// +// } + +export const accountLogout = async (): Promise => { + + try { + // post logout to backend + await postLogout(); + + // remove key and crimataId + clearToken(); + + // kill crimata platform session + endSession(); + + return; + + } catch(e) { + console.log('[ACCOUNT]', e); + return (new Error('Failed to logout. Please try again.')); + } + +} + + diff --git a/src/api/account.ts b/src/api/account.ts index 98d3c69..a907dfb 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -1,36 +1,31 @@ import { useHttp } from "@/composables/http"; import axios from "axios"; +import {config} from "@/config"; const { post } = useHttp(); -export const usrPwdAuth = async (email: string, password: string) => { - - try { - return await post('/account/login', { email, password }) - - } catch (e) { - return null; - } - -} - -export const tokenAuth = async (cid: string, token: string) => { - - try { - return await axios({ - url: "http://127.0.0.1:3000/api/account/profile", - headers: { - Cookie: `jwt=${token}` - }, - method: 'GET', - data: { - cid, - } +export const postAuth = async (token: string) => ( + await axios({ + url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate', + headers: { + Cookie: `jwt=${token}` + }, + method: 'POST', }) +).data; + + +export const postLogin = async (email: string, password: string) => ( + await post('/account/login', { email, password }) +).data; + + +export const postLogout = + async (): Promise => (await post('/account/logout')); + + + + - } catch (e) { - return null; - } -} \ No newline at end of file diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..14ed36a --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,13 @@ + +export 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 + } +}; + + + + diff --git a/src/composables/http.ts b/src/composables/http.ts index a9519a8..e789afa 100644 --- a/src/composables/http.ts +++ b/src/composables/http.ts @@ -1,10 +1,8 @@ import axios, { AxiosRequestConfig } from 'axios'; +import {config} from "@/config"; -const preFix = '/api'; - -const baseURL = "http://127.0.0.1:3000" + preFix; - +const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX; interface Request { endpoint: string; @@ -12,7 +10,6 @@ interface Request { config?: Record; } - const makeQuery = (reqQuery: Record) => { let result = ''; diff --git a/src/composables/ipcHandler.ts b/src/composables/ipcHandler.ts new file mode 100644 index 0000000..b588102 --- /dev/null +++ b/src/composables/ipcHandler.ts @@ -0,0 +1,50 @@ + +import { ipcMain, IpcMainInvokeEvent } from "electron"; + +export class IpcHandler implements IIpcHandler { + + readonly channel: string; + + readonly _handlerCallback: IpcHandlerCallback; + + constructor(options: { + channel: string; + handlerCallback: IpcHandlerCallback; + }) { + this.channel = options.channel; + this._handlerCallback = options.handlerCallback; + } + + handle() { + ipcMain.handle(this.channel, this._onInvoke); + } + + remove() { + ipcMain.removeHandler(this.channel); + } + + private async _onInvoke(_e: IpcMainInvokeEvent, payload?: string | null): Promise { + + return new Promise(async (resolve, reject) => { + + console.log(`[IPC]:${this.channel}`); + + try { + + const params = payload ? JSON.parse(payload) : null; + + const res = await this._handlerCallback(params); + + resolve(res as unknown as ReturnType); + + } catch(e) { + console.log(`[IPC]:${this.channel}`, e); + reject(e); + } + }); + } + +} + + + diff --git a/src/composables/json.ts b/src/composables/json.ts index 43b3804..06ab4b9 100644 --- a/src/composables/json.ts +++ b/src/composables/json.ts @@ -1,9 +1,13 @@ + +import {config} from "@/config"; +import fs from 'fs'; + export const saveToJson = (fileName: string, data: any) => { - fs.writeFile(configPath + fileName, JSON.stringify(data), (err) => { + fs.writeFile(config.configPath + fileName, JSON.stringify(data), (err) => { if (err) { console.log("Error when saving to json.") - } + } }) -} \ No newline at end of file +} diff --git a/src/composables/store.ts b/src/composables/store.ts new file mode 100644 index 0000000..88b9c17 --- /dev/null +++ b/src/composables/store.ts @@ -0,0 +1,19 @@ +const Store = require('electron-store'); + +const schema = { + key: { + type: 'string', + }, +}; + +const store = new Store({ + schema, + encryptionKey: "super user test" +}); + +export const getToken = (): string | undefined => (store.get("token")); + +export const clearToken = (): void => (store.delete("token")); + +export const setToken = (token: string): void => (store.set('token', token)); + diff --git a/src/composables/websockets.ts b/src/composables/websockets.ts index d034027..8208109 100644 --- a/src/composables/websockets.ts +++ b/src/composables/websockets.ts @@ -3,9 +3,18 @@ import WebSocket from 'ws'; -export default function useWebSockets(onMessageCallback: (s: string) => void) { - let socket: WebSocket | null = null; +const _connectionCheckTimeout = 4000; +const _reconnectTimeout = 1000; +let _connectionCheckInterval: ReturnType; + + +export default function useWebSockets( + messageCallback: (message: string) => void, + connectionStatusCallback: (alive: boolean) => void, +) { + + let socket: WebSocket; const send = async (data: Record): Promise => { return new Promise((resolve, reject) => { @@ -21,30 +30,52 @@ export default function useWebSockets(onMessageCallback: (s: string) => void) { const connect = (socketUrl: string, secret: string) => { + // avoid setting multiple interval; + if (_connectionCheckInterval) clearInterval(_connectionCheckInterval); + /* create a new socket */ - socket = new WebSocket(socketUrl) + socket = new WebSocket(socketUrl); /* add event listeners */ - socket.on("open", () => { - if (socket) + socket.send(secret); + + // ping server + _connectionCheckInterval = setInterval(() => { + + socket.ping(null, true, (e: Error) => { + if (e) { + socket.close(); + connectionStatusCallback(false); + setTimeout(() => connect(socketUrl, secret), _reconnectTimeout); + } + }); + + }, _connectionCheckTimeout); + }); socket.on("message", (event: WebSocket.MessageEvent) => { - onMessageCallback(event.data.toString()) + messageCallback(event.data.toString()) }); - socket.on("close", () => { - return + socket.on("close", (event: WebSocket.CloseEvent) => { + connectionStatusCallback(false); + clearInterval(_connectionCheckInterval); + if (!event.wasClean) { + setTimeout(() => connect(socketUrl, secret), _reconnectTimeout); + } + }); + socket.on("pong", () => connectionStatusCallback(true)); + } const close = () => { if (socket) { socket.close(); - socket = null; } } diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..b0eb27e --- /dev/null +++ b/src/config.ts @@ -0,0 +1,17 @@ + +import { app } from "electron"; + +const env = process.env; + +const PLATFORM_PORT = env.PLATFORM_PORT || 8760; +const PLATFORM_IP = env.PLATFORM_IP || 'http://127.0.0.1'; + +const BUSINESS_PORT = env.BUSINESS_PORT || 3010; +const BUSINESS_IP = env.BUSINESS_IP || 'http://127.0.0.1'; + +export const config = { + PLATFORM_URL: `${PLATFORM_IP}:${PLATFORM_PORT}`, + BUSINESS_URL: `${BUSINESS_IP}:${BUSINESS_PORT}`, + BUSINESS_PREFIX: '/api', + configPath: app.getPath('userData') +} diff --git a/src/init.ts b/src/init.ts index 3d0b721..e68e766 100644 --- a/src/init.ts +++ b/src/init.ts @@ -9,8 +9,6 @@ import { app, protocol } from "electron"; import createWindow from "./window"; import main from "./main"; -require('dotenv').config(); - console.log('Starting Crimata electron app.'); // Scheme must be registered before the app is ready @@ -43,4 +41,4 @@ if (isDev) { process.on("SIGTERM", () => { app.quit(); }); -} \ No newline at end of file +} diff --git a/src/ipc/account.ts b/src/ipc/account.ts index 3860019..531d1a7 100644 --- a/src/ipc/account.ts +++ b/src/ipc/account.ts @@ -1,126 +1,32 @@ "use strict"; -import { submit, fetchProfile, logout } from "../api/account"; -import { ipcMain, IpcMainInvokeEvent } from "electron"; -import { store } from "@/composables/store"; +import { accountLogin, accountLogout } from "@/account"; +import {IpcHandler} from "@/composables/ipcHandler"; +const LOGIN_CHANNEL = "account-login"; +const LOGOUT_CHANNEL = "account-logout"; -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 loginHandler = new IpcHandler({ + channel: LOGIN_CHANNEL, + handlerCallback: accountLogin +}); +const logoutHandler = new IpcHandler({ + channel: LOGOUT_CHANNEL, + handlerCallback: accountLogout +}); +const handlers = [loginHandler, logoutHandler]; +export default handlers; - -/** - * Get user profile from store and try to login with it. - */ -const onTokenLogin = async ( - _event: IpcMainInvokeEvent, - _payload: null -): Promise => ( - - 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 { - - // attempt login with email token - const res = await fetchProfile(crimataId, token); - const parsed = parseAuthRes(res); - - // return profile to renderer - resolve(parsed.profile); - - } catch(e) { - reject(new Error('Failed to fetch profile.')); - } - }) -) - - -const onLogin = async ( - _event: IpcMainInvokeEvent, - payload: string -): Promise => ( - - new Promise(async (resolve, reject) => { - console.log('[IPC]: user-login'); - - const account = JSON.parse(payload); - - if ( account.password && account.email ) { - try { - - // attempt login with email password - 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); - - // init session - - // return profile to renderer - resolve(parsed.profile); - - } catch(e) { - console.log('[API]', e); - reject(new Error('Failed to authenticate')); - } - } - }) -) - -const onLogout = async ( - _event: IpcMainInvokeEvent, - _payload: null -): Promise => ( - - 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 - // endSession(); - - 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); - -} +// export default function useAccountListeners(): void { +// +// ipcMain.removeHandler(LOGIN_HANDLER); +// ipcMain.handle(LOGIN_HANDLER, onLogin); +// +// ipcMain.removeHandler(LOGOUT_HANDLER); +// ipcMain.handle(LOGOUT_HANDLER, onLogout); +// +// } diff --git a/src/ipc/index.ts b/src/ipc/index.ts index 9152ef2..73a5901 100644 --- a/src/ipc/index.ts +++ b/src/ipc/index.ts @@ -1,17 +1,36 @@ "use strict"; -import useAccountListeners from "./account"; +import { IpcHandler } from "@/composables/ipcHandler"; +import handlers from "./account"; import useSessionListeners from "./session"; // import useAudioListeners from "./audio"; +interface IPCHandlers { + [channel: string]: IpcHandler; +} + +const ipcHandlers: IPCHandlers = {}; + +const _initHandlers = () => { + handlers.forEach((h) => { + if (!(h.channel in ipcHandlers)) { + ipcHandlers[h.channel] = h; + h.handle(); + } + }); +} export default function useIpc(): void { - useAccountListeners(); + _initHandlers(); + + // useAccountListeners(); useSessionListeners(); // useAudioListeners(); } + + diff --git a/src/ipc/session.ts b/src/ipc/session.ts index fa50aa1..bbaf0e9 100644 --- a/src/ipc/session.ts +++ b/src/ipc/session.ts @@ -10,11 +10,11 @@ function onSendMessage(_event: IpcMainEvent, payload: Message): void { } // Login attempt, returns success or not. -function onLogin(_event: IpcMainEvent, payload: LoginPayload) => { - authenticate(payload.email, payload.password); +function onLogin(_event: IpcMainEvent, payload: LoginPayload): void { + console.log('hello') } export default function useSessionListeners(): void { ipcMain.removeAllListeners("client-message"); ipcMain.on("client-message", onSendMessage); -} \ No newline at end of file +} diff --git a/src/main.ts b/src/main.ts index 9d6414c..b015cd9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,41 +1,21 @@ /** * Where the background logic really begins, gets called by app.onReady(). - * + * * Handles authentication. If profile is set, we launch a session, which consis * of opening a connection with the platform, initializing the audio streams. - * + * * The session is primarily an interface between the frontend and the platform, * relaying messages from one to the other. - * + * */ -import { tokenAuth, usrPwdAuth } from "@/api/account"; -import { launchSession, endSession } from "@/session"; import useIpc from "@/ipc/index"; -import store from "@/composables/store"; +import { accountAuth } from "./account"; +import { launchSession } from "./session"; +import ipcEmit from "./composables/emitter"; +import createWindow from "./window"; -/* authenticate the user */ -export async function authenticate(email: string, password: string) { - - /* attempt normal login */ - const platformKey, token, crimataId = await usrPwdAuth(email, password); - - /* launch if successful */ - if (platformKey) - launchSession(platformKey, crimataId); - - /* save the token */ - store.set("token", token); - -} - -/* logout the user, end the session */ -export function deauthenticate() { - - /* terminate the session */ - endSession(); - -} +let authState: AuthState | null; export default async function main() { @@ -45,18 +25,14 @@ export default async function main() { /* launch browser window */ await createWindow(); - /* attempt to get a login token from the store */ - const token = store.get("token"); + try { + authState = await accountAuth() as AuthState; + } catch(e) { + console.log('AUTH:', e); + authState = null; + } finally { + if (authState) launchSession(authState.token as string); + ipcEmit("set-profile", authState?.profile); + } - /* try to login with it, returns platform secret and new token on success */ - if (token) - const newToken, profile = await tokenAuth(token); - - /* if secret, we launch a session */ - if (newToken) - launchSession(newToken, profile); - - /* finally, save the most recent token */ - store.set("token", newToken); - -} \ No newline at end of file +} diff --git a/src/session.ts b/src/session.ts index afebe5b..158c41e 100644 --- a/src/session.ts +++ b/src/session.ts @@ -2,24 +2,34 @@ -import useAudio from "@/audio"; -import Canvas from "@/composables/convas"; +// import useAudio from "@/audio"; import ipcEmit from "@/composables/emitter"; +import useWebsockets from "./composables/websockets"; +import {config} from "@/config"; /* data structure of messages that's tied to the UI */ -let msgrState: UIState | null = null; +const uiState: any | null = null; /* start and stop audio functionality */ -const { initAudio, closeAudio } = useAudio(); +// const { initAudio, closeAudio } = useAudio(); + +let isInitMessage: any; + +let deauthenticate: any; +let isAddMessage: any /** * Controls for interfacing with the platform. * Takes an onMessage callback which we define below. */ -const { connect, send, close } = useWebsockets((content: any) => { + + +const onMessageCallback = (message: string) => { + + const content = JSON.parse(message); /* if the platform fails to authenticate, we must back down */ - if (content === "auth_error") { + if (content === "CLOSE_AUTH_FAIL") { deauthenticate(); return; } @@ -28,8 +38,8 @@ const { connect, send, close } = useWebsockets((content: any) => { if (isInitMessage(content)) { uiState.set(content); } - - + + else if (isAddMessage(content)) { uiState.add(content); } @@ -37,10 +47,15 @@ const { connect, send, close } = useWebsockets((content: any) => { else { uiState.update(content); } +} -}); +const onConnectionStatusCallback = (alive: boolean) => { + ipcEmit('connection-state', alive); +} -/* send a message to the platform */ +const { connect, send, close } = useWebsockets(onMessageCallback, onConnectionStatusCallback); + +/* send a message to the platform */ export function sendMessage(message: Message) { /* socket send */ @@ -48,24 +63,22 @@ export function sendMessage(message: Message) { } + /* launch a new session (the main process for authenticated users) */ -export function launchSession(platformKey: string, crimata_id: string) { +export function launchSession(platformKey: string) { /* connect to the platform */ - connect(PLATFORM_URL, platformKey); + connect(config.PLATFORM_URL, platformKey); /* initialize the audio streams */ - initAudio(); - - /* push profile to window */ - if (win) - ipcEmit("update-auth", crimata_id); + // initAudio(); } + export function endSession() { - closeAudio(); + // closeAudio(); close(); diff --git a/src/types.ts b/src/types.ts index e8682b8..583371e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,4 @@ + interface Message { text: boolean | string; context: boolean | string; @@ -28,3 +29,25 @@ interface LoginPayload { email: string; password: string; } + +interface AuthState { + profile: Profile | null; + token: string | null; +} + +interface AccountCredentials { + email: string; + password: string; +} + +interface IpcHandlerCallback { + (payload: I | null): Promise; +} + +interface IIpcHandler { + handle(): void; + remove(): void; + readonly _handlerCallback: IpcHandlerCallback; +} + + diff --git a/src/window.ts b/src/window.ts index 4c69387..807064a 100644 --- a/src/window.ts +++ b/src/window.ts @@ -1,10 +1,12 @@ "use strict"; -import { BrowserWindow, ipcMain } from "electron"; +import { BrowserWindow, ipcMain, app } from "electron"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; -import { backgroundMitt } from './coposables/emitter'; -import { saveToJson } from "./coposables/json"; +import { backgroundMitt } from './composables/emitter'; +import { saveToJson } from "./composables/json"; import * as path from "path"; +import fs from 'fs'; +import { config } from "@/config"; const { autoUpdater } = require('electron-updater'); interface IpcRendererPayload { @@ -19,8 +21,8 @@ const loadWinState = (fileName: string): WindowState => { let state: WindowState; try { - state = JSON.parse(fs.readFileSync(configPath + fileName).toString()); - } + state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString()); + } catch (error) { state = { @@ -32,8 +34,8 @@ const loadWinState = (fileName: string): WindowState => { } return state - -} + +} // Called when a NavBar button is pressed. const onNavBar = (_event: any, action: string): void => { @@ -110,8 +112,8 @@ export default async function createWindow(): Promise { win = new BrowserWindow({ width: winState.width, height: winState.height, - x: winState.x, - y: winState.y, + x: winState.x as number, + y: winState.y as number, resizable: true, backgroundColor: '#EBEBEB', frame: false, From 0a3c9f71d81f54f0d1784c68b26b34e4f32ffe9b Mon Sep 17 00:00:00 2001 From: riqo Date: Tue, 15 Jun 2021 08:48:14 -0500 Subject: [PATCH 06/14] rename composable files to composable notation --- src/audio.ts | 187 ++++++++++++++++++ src/composables/audio.ts | 187 ------------------ src/composables/{emitter.ts => useEmitter.ts} | 0 src/composables/{http.ts => useHttp.ts} | 0 .../{ipcHandler.ts => useIpcMain.ts} | 0 .../{canvas.ts => useMessageCanvas.ts} | 0 src/composables/{json.ts => useSaveToJSON.ts} | 0 .../{websockets.ts => useWebsockets.ts} | 0 .../controllers/messenger.control.ts | 12 +- src/render/components/messenger.vue | 15 +- src/render/components/settings.vue | 2 +- .../{draggify.ts => useDraggify.ts} | 0 .../composables/{ipc.ts => useIpcRend.ts} | 0 src/render/composables/useMessages.ts | 0 .../composables/{auth.ts => useProfile.ts} | 0 .../composables/{scroll.ts => useScroll.ts} | 0 src/render/main.ts | 16 ++ src/{composables => }/store.ts | 0 18 files changed, 217 insertions(+), 202 deletions(-) delete mode 100644 src/composables/audio.ts rename src/composables/{emitter.ts => useEmitter.ts} (100%) rename src/composables/{http.ts => useHttp.ts} (100%) rename src/composables/{ipcHandler.ts => useIpcMain.ts} (100%) rename src/composables/{canvas.ts => useMessageCanvas.ts} (100%) rename src/composables/{json.ts => useSaveToJSON.ts} (100%) rename src/composables/{websockets.ts => useWebsockets.ts} (100%) rename src/render/composables/{draggify.ts => useDraggify.ts} (100%) rename src/render/composables/{ipc.ts => useIpcRend.ts} (100%) create mode 100644 src/render/composables/useMessages.ts rename src/render/composables/{auth.ts => useProfile.ts} (100%) rename src/render/composables/{scroll.ts => useScroll.ts} (100%) create mode 100644 src/render/main.ts rename src/{composables => }/store.ts (100%) diff --git a/src/audio.ts b/src/audio.ts index e69de29..844e791 100644 --- a/src/audio.ts +++ b/src/audio.ts @@ -0,0 +1,187 @@ +/* eslint @typescript-eslint/no-var-requires: "off" */ + +"use strict"; + +// where the audio goes +let buffer: ArrayBuffer[] = []; + +// place audio data in buffer +export function collect (chunk: ArrayBuffer) { + buffer.push(chunk); +} + +// return audio and clear buffer +export function flush () { + const bufferCopy = buffer; + buffer = []; + return bufferCopy; +} + +// import { backgroundMitt } from '@/modules/emitter'; +// const portAudio = require('naudiodon'); + +// // Audio in and out stream objects. +// let ai: typeof portAudio.AudioIO | boolean = false; +// let ao: typeof portAudio.AudioIO | boolean = false; + +// // Whether activly recording. +// let record = false; + +// const audioContainer = { +// input: '', +// } + +// const audioOptions = { +// channelCount: 1, +// sampleFormat: 16, +// sampleRate: 16000, +// deviceId: -1, +// closeOnError: false, +// } + +// export const toggleRecord = (): void => { record = !record }; + + +// export const fetchAudioInput = (): Promise => ( + +// new Promise((resolve, reject) => { + +// try { +// resolve(audioContainer.input); +// toggleRecord(); +// } catch (e) { +// reject(new Error('Failed to fetch the audio.')) +// } + +// }) +// ) + + +// // Main audio function run by run.ts module. +// export function initAudioIO(): void { +// console.log("AUDIO:Starting io streams.") + +// if (!ai) { + +// // Initialize and start input stream. +// ai = new portAudio.AudioIO({ inOptions: audioOptions }); +// ai.setEncoding("hex"); +// ai.start(); + +// // On each data chunk... +// ai.on('data', (chunk: string) => { + +// // If recording, we capture the data. +// if (record) { +// console.log('AUDIO:Recording...') +// audioContainer.input += chunk; +// } + +// // Else, we don't capture and also clear audioContainer. +// else { +// if (audioContainer.input.length) { +// audioContainer.input = ""; +// } +// } + +// }); +// } + +// if (!ao) { + +// // Initialize and start input stream. +// ao = new portAudio.AudioIO({ outOptions: audioOptions }); +// ao.start(); + +// } +// } + + +// // ---Audio playback-------------------------------------------- + +// // Split Buffer into an array of len-sized Buffers. +// function bufSplit(buf: Buffer, len: number): Array { +// const chunks = []; +// let i = 0; +// let L = len; + +// while(i < buf.byteLength) { +// chunks.push(buf.slice(i, L)); +// i = L; +// L += len; +// } + +// return chunks; +// } + +// // Audio playback. +// export function play(input: string): void { + +// // Format the audio. +// const audio = bufSplit( +// Buffer.from(input as string, 'hex'), +// 8192 +// ); + +// // Called on end of write. +// const callback = () => { + +// // We stop audio playback anim. +// backgroundMitt.emit('ipc-renderer', { +// endpoint: 'stop-playback-anim' +// }); + +// } + +// write(); + +// // Iterate through audio array and write buffers to portAudio writable. +// function write() { +// let chunk: Buffer; +// let ok = true; +// let i = 0; + +// do { +// chunk = audio[i]; +// if (i === audio.length - 1) { +// // write last chunk. +// ao.write(chunk, null, callback); +// } else { +// // check for backpreassure. +// ok = ao.write(chunk, null); +// } +// i++; +// } while (i < audio.length && ok); + +// if (i < audio.length) { +// // Had to stop early! +// // Write some more once it drains. +// ao.once('drain', write); +// } +// } +// } + +// // ------------------------------------------------------------- + +// // Get's called on window close. +// export async function stopStream() { +// console.log("AUDIO:Stopping audio stream.") +// if (ai) { +// try { +// await ai.quit() +// } catch(e){ +// console.log('AUDIO: Failed to shutdown audio input.'); +// throw e; +// } +// } +// if (ao) { +// try { +// await ao.quit() +// } catch(e){ +// console.log('AUDIO: Failed to shutdown audio output.'); +// throw e; +// } +// } +// } + + diff --git a/src/composables/audio.ts b/src/composables/audio.ts deleted file mode 100644 index 844e791..0000000 --- a/src/composables/audio.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint @typescript-eslint/no-var-requires: "off" */ - -"use strict"; - -// where the audio goes -let buffer: ArrayBuffer[] = []; - -// place audio data in buffer -export function collect (chunk: ArrayBuffer) { - buffer.push(chunk); -} - -// return audio and clear buffer -export function flush () { - const bufferCopy = buffer; - buffer = []; - return bufferCopy; -} - -// import { backgroundMitt } from '@/modules/emitter'; -// const portAudio = require('naudiodon'); - -// // Audio in and out stream objects. -// let ai: typeof portAudio.AudioIO | boolean = false; -// let ao: typeof portAudio.AudioIO | boolean = false; - -// // Whether activly recording. -// let record = false; - -// const audioContainer = { -// input: '', -// } - -// const audioOptions = { -// channelCount: 1, -// sampleFormat: 16, -// sampleRate: 16000, -// deviceId: -1, -// closeOnError: false, -// } - -// export const toggleRecord = (): void => { record = !record }; - - -// export const fetchAudioInput = (): Promise => ( - -// new Promise((resolve, reject) => { - -// try { -// resolve(audioContainer.input); -// toggleRecord(); -// } catch (e) { -// reject(new Error('Failed to fetch the audio.')) -// } - -// }) -// ) - - -// // Main audio function run by run.ts module. -// export function initAudioIO(): void { -// console.log("AUDIO:Starting io streams.") - -// if (!ai) { - -// // Initialize and start input stream. -// ai = new portAudio.AudioIO({ inOptions: audioOptions }); -// ai.setEncoding("hex"); -// ai.start(); - -// // On each data chunk... -// ai.on('data', (chunk: string) => { - -// // If recording, we capture the data. -// if (record) { -// console.log('AUDIO:Recording...') -// audioContainer.input += chunk; -// } - -// // Else, we don't capture and also clear audioContainer. -// else { -// if (audioContainer.input.length) { -// audioContainer.input = ""; -// } -// } - -// }); -// } - -// if (!ao) { - -// // Initialize and start input stream. -// ao = new portAudio.AudioIO({ outOptions: audioOptions }); -// ao.start(); - -// } -// } - - -// // ---Audio playback-------------------------------------------- - -// // Split Buffer into an array of len-sized Buffers. -// function bufSplit(buf: Buffer, len: number): Array { -// const chunks = []; -// let i = 0; -// let L = len; - -// while(i < buf.byteLength) { -// chunks.push(buf.slice(i, L)); -// i = L; -// L += len; -// } - -// return chunks; -// } - -// // Audio playback. -// export function play(input: string): void { - -// // Format the audio. -// const audio = bufSplit( -// Buffer.from(input as string, 'hex'), -// 8192 -// ); - -// // Called on end of write. -// const callback = () => { - -// // We stop audio playback anim. -// backgroundMitt.emit('ipc-renderer', { -// endpoint: 'stop-playback-anim' -// }); - -// } - -// write(); - -// // Iterate through audio array and write buffers to portAudio writable. -// function write() { -// let chunk: Buffer; -// let ok = true; -// let i = 0; - -// do { -// chunk = audio[i]; -// if (i === audio.length - 1) { -// // write last chunk. -// ao.write(chunk, null, callback); -// } else { -// // check for backpreassure. -// ok = ao.write(chunk, null); -// } -// i++; -// } while (i < audio.length && ok); - -// if (i < audio.length) { -// // Had to stop early! -// // Write some more once it drains. -// ao.once('drain', write); -// } -// } -// } - -// // ------------------------------------------------------------- - -// // Get's called on window close. -// export async function stopStream() { -// console.log("AUDIO:Stopping audio stream.") -// if (ai) { -// try { -// await ai.quit() -// } catch(e){ -// console.log('AUDIO: Failed to shutdown audio input.'); -// throw e; -// } -// } -// if (ao) { -// try { -// await ao.quit() -// } catch(e){ -// console.log('AUDIO: Failed to shutdown audio output.'); -// throw e; -// } -// } -// } - - diff --git a/src/composables/emitter.ts b/src/composables/useEmitter.ts similarity index 100% rename from src/composables/emitter.ts rename to src/composables/useEmitter.ts diff --git a/src/composables/http.ts b/src/composables/useHttp.ts similarity index 100% rename from src/composables/http.ts rename to src/composables/useHttp.ts diff --git a/src/composables/ipcHandler.ts b/src/composables/useIpcMain.ts similarity index 100% rename from src/composables/ipcHandler.ts rename to src/composables/useIpcMain.ts diff --git a/src/composables/canvas.ts b/src/composables/useMessageCanvas.ts similarity index 100% rename from src/composables/canvas.ts rename to src/composables/useMessageCanvas.ts diff --git a/src/composables/json.ts b/src/composables/useSaveToJSON.ts similarity index 100% rename from src/composables/json.ts rename to src/composables/useSaveToJSON.ts diff --git a/src/composables/websockets.ts b/src/composables/useWebsockets.ts similarity index 100% rename from src/composables/websockets.ts rename to src/composables/useWebsockets.ts diff --git a/src/render/components/controllers/messenger.control.ts b/src/render/components/controllers/messenger.control.ts index 9ae7d47..a0f9b09 100644 --- a/src/render/components/controllers/messenger.control.ts +++ b/src/render/components/controllers/messenger.control.ts @@ -1,20 +1,20 @@ import { ref } from 'vue'; import useScroll from "@/render/composables/scroll"; -const canvas = ref(); +const messagesRef = ref(); /* seed the canvas with messages */ const seedCanvas = (messages: Message[]) => { - canvas.value = messages; + messagesRef.value = messages; } const addMessage = (message: Message) => { - canvas.value.push(message); + messagesRef.value.push(message); } const updateMessage = (message: Message) => { - let target_message = canvas.value.filter((m: Message) => { + let target_message = messagesRef.value.filter((m: Message) => { return m.uid = message.uid; })[0]; @@ -29,10 +29,10 @@ export default function useMessages() { const { updateScrollRef, adjustScroll } = useScroll("messenger"); return { - canvas, + messagesRef, seedCanvas, addMessage, updateMessage }; -} \ No newline at end of file +} diff --git a/src/render/components/messenger.vue b/src/render/components/messenger.vue index 4ea2d5b..9bc6448 100644 --- a/src/render/components/messenger.vue +++ b/src/render/components/messenger.vue @@ -8,10 +8,9 @@
@@ -24,7 +23,7 @@ import { defineComponent, onMounted, onUnmounted } from "vue"; import Message from "@/render/components/message.vue"; import InputItem from "@/render/components/inputItem.vue"; import Settings from "@/render/components/settings.vue"; -import useMessages from "@/render/composables/messages"; +import useMessages from "./controllers/messenger.control"; export default defineComponent({ name: "Messenger", @@ -40,23 +39,23 @@ export default defineComponent({ setup(props) { // Handle messages in view. - const { messages, updateMessageView } = useMessages(); + const { messagesRef, updateMessageView } = useMessages(); onMounted(() => { /* seed messages */ window.ipcRenderer.on("init-messages", (e_: any, payload: any) => { - seedMessages(payload.messages); + // seedMessages(payload.messages); }); /* add a new message */ window.ipcRenderer.on("add-message", (_e: any, payload: any) => { - addMessage(payload.message); + // addMessage(payload.message); }); /* update an existing message */ window.ipcRenderer.on("update-message", (_e: any, payload: any) => { - updateMessage(payload.message); + // updateMessage(payload.message); }); }); @@ -66,7 +65,7 @@ export default defineComponent({ }); return { - messages + messagesRef }; }, diff --git a/src/render/components/settings.vue b/src/render/components/settings.vue index 7c4b9ce..9b4f3e5 100644 --- a/src/render/components/settings.vue +++ b/src/render/components/settings.vue @@ -31,7 +31,7 @@ import { useIpc } from "@/modules/ipc"; import { logoutRequest } from '@/modules/message'; import { useProfile } from "@/modules/auth" - import { invokeLogout } from "@/ipcRend/account"; + import { invokeLogout } from "@/render/ipc"; export default defineComponent({ name: "Settings", diff --git a/src/render/composables/draggify.ts b/src/render/composables/useDraggify.ts similarity index 100% rename from src/render/composables/draggify.ts rename to src/render/composables/useDraggify.ts diff --git a/src/render/composables/ipc.ts b/src/render/composables/useIpcRend.ts similarity index 100% rename from src/render/composables/ipc.ts rename to src/render/composables/useIpcRend.ts diff --git a/src/render/composables/useMessages.ts b/src/render/composables/useMessages.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/render/composables/auth.ts b/src/render/composables/useProfile.ts similarity index 100% rename from src/render/composables/auth.ts rename to src/render/composables/useProfile.ts diff --git a/src/render/composables/scroll.ts b/src/render/composables/useScroll.ts similarity index 100% rename from src/render/composables/scroll.ts rename to src/render/composables/useScroll.ts diff --git a/src/render/main.ts b/src/render/main.ts new file mode 100644 index 0000000..50a594a --- /dev/null +++ b/src/render/main.ts @@ -0,0 +1,16 @@ + +// src/main.ts + +import App from "./App.vue"; + +import mitt from "mitt"; +import { createApp } from "vue"; + + +// Handle events. +const emitter = mitt(); + +const app = createApp(App) + +app.provide("mitt", emitter) +app.mount("#app"); diff --git a/src/composables/store.ts b/src/store.ts similarity index 100% rename from src/composables/store.ts rename to src/store.ts From d963f7d8a8e6badf4b376d1300772fbc8354202d Mon Sep 17 00:00:00 2001 From: riqo Date: Wed, 16 Jun 2021 08:42:47 -0500 Subject: [PATCH 07/14] initial working version --- package.json | 4 ++- src/account.ts | 25 +------------------ src/api/account.ts | 2 +- src/composables/useEmitter.ts | 9 ++++--- src/composables/useHttp.ts | 2 +- src/init.ts | 8 ++++++ src/ipc/account.ts | 2 +- src/main.ts | 10 +++++--- src/render/App.vue | 6 ++--- src/render/components/controllers/helpers.ts | 2 +- .../controllers/inputItem.control.audio.ts | 8 +++--- .../controllers/inputItem.control.text.ts | 8 +++--- .../controllers/messenger.control.ts | 2 +- src/render/components/header.vue | 18 +++++++++---- src/render/components/inputItem.vue | 23 +++++++---------- src/render/components/login.vue | 19 +++++++------- src/render/components/messenger.vue | 4 +-- src/render/components/settings.vue | 8 ++---- src/render/composables/useProfile.ts | 2 ++ src/render/ipc.ts | 18 ++++++------- src/session.ts | 4 +-- src/types.ts | 1 - src/window.ts | 4 +-- 23 files changed, 89 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index 2b4b7fd..7fff91a 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,9 @@ "lintOnSave": false, "pluginOptions": { "electronBuilder": { - "preload": "src/renderer/preload.ts", + "mainProcessFile": "./src/init.ts", + "rendererProcessFile": "./src/render/main.ts", + "preload": "./src/render/preload.ts", "builderOptions": { "appId": "com.crimata.ElectronUpdaterApp", "artifactName": "${productName}-${version}.${ext}", diff --git a/src/account.ts b/src/account.ts index 4884135..95fe629 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,7 +1,7 @@ import { postAuth, postLogin, postLogout } from "@/api/account"; import { endSession, launchSession } from "@/session"; -import { getToken, clearToken, setToken } from "@/composables/store"; +import { getToken, clearToken, setToken } from "./store"; import { parseAuthRes } from "./auth"; export const accountAuth = async (): Promise => { @@ -57,29 +57,6 @@ export const accountLogin: IpcHandlerCallback = asy } } -// export const accountLogin = async (account: Account): Promise => { -// -// try { -// -// // attempt login with email password -// const res = await postLogin(account.email, account.password); -// const parsed = parseAuthRes(res); -// -// // save jwt token and profile -// setToken(parsed.token) -// -// // launch session -// launchSession(parsed.token) -// -// // return profile to renderer -// return parsed.profile; -// -// } catch(e) { -// console.log('[ACCOUNT]', e); -// throw (new Error('Failed to authenticate')); -// } -// -// } export const accountLogout = async (): Promise => { diff --git a/src/api/account.ts b/src/api/account.ts index a907dfb..59872c8 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -1,5 +1,5 @@ -import { useHttp } from "@/composables/http"; +import useHttp from "@/composables/useHttp"; import axios from "axios"; import {config} from "@/config"; diff --git a/src/composables/useEmitter.ts b/src/composables/useEmitter.ts index ee6bb5e..240c557 100644 --- a/src/composables/useEmitter.ts +++ b/src/composables/useEmitter.ts @@ -1,15 +1,16 @@ -/* eslint-disable */ // Backend emitter - +// const EventEmitter = require('events'); class BackgroundMitt extends EventEmitter { } export const backgroundMitt = new BackgroundMitt(); -export default function ipcEmit (channel: string, payload: any) { +export const ipcEmit = (channel: string, payload: any) => { backgroundMitt.emit('ipc-renderer', { endpoint: channel, message: payload }); -} +}; + + diff --git a/src/composables/useHttp.ts b/src/composables/useHttp.ts index e789afa..6b9ee56 100644 --- a/src/composables/useHttp.ts +++ b/src/composables/useHttp.ts @@ -22,7 +22,7 @@ const makeQuery = (reqQuery: Record) => { }; -export const useHttp = () => { +export default function useHttp() { const api = axios.create({ baseURL, diff --git a/src/init.ts b/src/init.ts index e68e766..9a0fdf6 100644 --- a/src/init.ts +++ b/src/init.ts @@ -8,6 +8,7 @@ import { app, protocol } from "electron"; import createWindow from "./window"; import main from "./main"; +import { backgroundMitt } from '@/composables/useEmitter'; console.log('Starting Crimata electron app.'); @@ -18,6 +19,13 @@ protocol.registerSchemesAsPrivileged([ const isDev = require('electron-is-dev'); +let win: boolean; + +// Listen for window creation. +backgroundMitt.on('window-active', (state: boolean) => { + win = state; +}); + /* Start main process on ready */ app.on("ready", async () => { await main(); diff --git a/src/ipc/account.ts b/src/ipc/account.ts index 531d1a7..7759d12 100644 --- a/src/ipc/account.ts +++ b/src/ipc/account.ts @@ -2,7 +2,7 @@ "use strict"; import { accountLogin, accountLogout } from "@/account"; -import {IpcHandler} from "@/composables/ipcHandler"; +import {IpcHandler} from "@/composables/useIpcMain"; const LOGIN_CHANNEL = "account-login"; const LOGOUT_CHANNEL = "account-logout"; diff --git a/src/main.ts b/src/main.ts index b015cd9..9103cae 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,7 +12,7 @@ import useIpc from "@/ipc/index"; import { accountAuth } from "./account"; import { launchSession } from "./session"; -import ipcEmit from "./composables/emitter"; +import { ipcEmit } from "./composables/useEmitter"; import createWindow from "./window"; let authState: AuthState | null; @@ -31,8 +31,12 @@ export default async function main() { console.log('AUTH:', e); authState = null; } finally { - if (authState) launchSession(authState.token as string); - ipcEmit("set-profile", authState?.profile); + let profile = null; + if (authState) { + launchSession(authState.token as string); + profile = authState.profile; + } + ipcEmit("set-profile", profile); } } diff --git a/src/render/App.vue b/src/render/App.vue index d90bcc0..dfdaeb9 100644 --- a/src/render/App.vue +++ b/src/render/App.vue @@ -21,8 +21,8 @@ @@ -78,4 +86,4 @@ .minimizeButton:active { background-color: #c08e38; } - \ No newline at end of file + diff --git a/src/render/components/inputItem.vue b/src/render/components/inputItem.vue index 30978b3..edd844f 100644 --- a/src/render/components/inputItem.vue +++ b/src/render/components/inputItem.vue @@ -6,16 +6,16 @@ :style="{ top: `${elementY}px`, left: `${elementX}px` }" >
{{ initials }}
- + - @@ -27,24 +27,19 @@ - - diff --git a/src/authPayload.ts b/src/authPayload.ts deleted file mode 100644 index 61d68b6..0000000 --- a/src/authPayload.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import { store } from "@/background/store"; - -interface PlatformAuthProtocol { - key: string; - crimata_id: string; -} - -export const getAuthPayload = (): PlatformAuthProtocol => ({ - key: store.get('key'), - crimata_id: store.get('crimataId') -}); - diff --git a/src/background.ts b/src/background.ts deleted file mode 100644 index 977c265..0000000 --- a/src/background.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Entry point for Crimata electron app. - * "Look on my Works, ye Mighty, and despair!" - */ - -"use strict"; - -import { initApp } from './background/init'; -import { protocol } from "electron"; - -// Scheme must be registered before the app is ready -protocol.registerSchemesAsPrivileged([ - { scheme: "app", privileges: { secure: true, standard: true } } -]); - -// Load environment variable -const isDev = require('electron-is-dev'); - -// NOTE Program Begins Here -(() => { - - console.log('Starting Crimata electron app.'); - initApp(isDev); - -})(); diff --git a/src/background/ipc/account.ts b/src/background/ipc/account.ts deleted file mode 100644 index 4bf6411..0000000 --- a/src/background/ipc/account.ts +++ /dev/null @@ -1,119 +0,0 @@ - -"use strict"; - -import { Profile } from "@/types"; -import { submit, fetchProfile, logout } from "@/api/account"; -import { ipcMain, IpcMainInvokeEvent } from "electron"; -import { store } from "@/background/store"; -import { endSession } from "@/background/session"; - - -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 => ( - - 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('Unable to authenticate and fetch account profile.')); - } - }) -) - - -const onLogin = async ( - _event: IpcMainInvokeEvent, - payload: string -): Promise => ( - - 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); - reject(new Error('Failed to authenticate')); - } - } - }) -) - -const onLogout = async ( - _event: IpcMainInvokeEvent, - _payload: null -): Promise => ( - - 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 - endSession(); - - 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/session.ts b/src/background/ipc/session.ts deleted file mode 100644 index 7c42ea2..0000000 --- a/src/background/ipc/session.ts +++ /dev/null @@ -1,62 +0,0 @@ - -"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, - _payload: null -): void => { - - console.log('[IPC]: init-session'); - - initSession(); - - 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); - -} diff --git a/src/background/session.ts b/src/background/session.ts deleted file mode 100644 index c90d32a..0000000 --- a/src/background/session.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 - * automatically try to reconnect on websocket disconnect. - */ - -import { backgroundMitt } from "@/modules/emitter"; -import { ipcEmit, loadState } from './helpers'; -import WebSocket from 'ws'; - -import useWebSockets from "./websockets"; - -import { play } from "./audio"; -import { renderMessage } from "@/modules/message"; -import { SessionState } from "@/types"; - -let win = true; - -// Info saved to json on quit (key, newMessages). -let state: SessionState; - -let socket: WebSocket | null = null; - - -// Calls appropriate endpoint for a server message. -const onMessage = (data: string): void => { - let message = JSON.parse(data); - - if (message === "CLOSE_AUTH_FAIL") { - ipcEmit("session-auth-fail", null) - return; - } - - // Standard message. - if (message.content) { - - // 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 { - if (win) { - ipcEmit("render-message", message) - } - } - -}; - - -// Websockets module. -const { createSocket, send } = useWebSockets(onMessage); - - -export const emitNewMessages = (): void => { - if (state) { - ipcEmit("update-state", { - newMessages: state.newMessages, - }); - } - -} - - -export const sendMessage = (payload: Record): void => { - try { - send(payload) - } catch(e) { - console.log("Unable to send message: ", payload); - } - -} - -export const endSession = (): void => { - if (socket) { - socket.close(); - socket = null; - } -} - - -// Call this to initialize session with Crimata servers. -export const initSession = (): void => { - console.log("SESS:Creating new session.") - - // Load Json or createState. - state = loadState("session.json"); - - // Open socket connection. - if (!socket) - socket = createSocket(); - - // Keep win up-to-date. - backgroundMitt.on('window-active', (state: boolean) => { - win = state; - }); - -} diff --git a/src/background/websockets.ts b/src/background/websockets.ts deleted file mode 100644 index dec0203..0000000 --- a/src/background/websockets.ts +++ /dev/null @@ -1,122 +0,0 @@ - -"use strict"; - -import WebSocket from 'ws'; -import { getAuthPayload } from "./authPayload"; -import { ipcEmit } from './helpers'; - -let socket: WebSocket; - -const socketUrl = "ws://127.0.0.1:8760" - -const _connectionCheckTimeout = 4000; -const _reconnectTimeout = 1000; -let _connectionCheckInterval: ReturnType; - - -// Run every time we want to connect to backend. -export default function useWebSockets( - receiveCallback: (s: string) => void, - openCallback?: () => void -) { - - // Returns bool (sucess or fail). - const sendMessage = (data: any) => { - console.log("WS:Sending message: ", data) - - if (socket.readyState !== 1) { - return false - } - - else { - socket.send(JSON.stringify(data)) - return true - } - - } - - const send = async (data: Record): Promise => ( - 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!"); - const jwt = getAuthPayload(); - socket.send(JSON.stringify(jwt)); - - // ping server - _connectionCheckInterval = setInterval(() => { - - if (socket) socket.ping(null, true, (e: Error) => { - if (e) { - ipcEmit('connection-alive', false); - socket.close(); - setTimeout(createSocket, 1000); - } - }); - - }, _connectionCheckTimeout); - - if (openCallback) openCallback(); - - } - - const onServerMessage = (event: WebSocket.MessageEvent) => { - - console.log("WS:Message received: ", event.data); - - receiveCallback(event.data.toString()) - - } - - const onClose = (event: WebSocket.CloseEvent) => { - console.log("WS:Socket closed normally.", event.wasClean) - - clearInterval(_connectionCheckInterval); - - if (!event.wasClean) { - ipcEmit('connection-alive', false); - setTimeout(createSocket, 1000); - } - } - - // Reconnect automatically on error. - const onError = (event: WebSocket.ErrorEvent) => { - console.log("WS:WebSocket error: ", event.message); - } - - - const createSocket = (): WebSocket => { - - if (_connectionCheckInterval) clearInterval(_connectionCheckInterval); - - socket = new WebSocket(socketUrl); - - // Add listeners. - socket.addEventListener("open", onOpen); - socket.addEventListener("message", onServerMessage); - socket.addEventListener("close", onClose); - socket.addEventListener("error", onError); - socket.addEventListener("pong", () => { - ipcEmit('connection-alive', true); - }); - - return socket; - - } - - return { - createSocket, - sendMessage, - send - } - -} diff --git a/src/ipcRend/session.ts b/src/ipcRend/session.ts deleted file mode 100644 index a5ff5fc..0000000 --- a/src/ipcRend/session.ts +++ /dev/null @@ -1,23 +0,0 @@ - -import { useIpc } from "@/modules/ipc"; - -import { ClientMessage } from "@/types"; - -const { post } = useIpc(); - - -export const postMount = (): void => ( - post("app-mounted", null) -); - - -export const postInitSession = (): void => ( - post("init-session", null) -); - - -export const postMessage = (payload: ClientMessage): void => ( - post('client-message', payload) -); - - From 56196bcbcf3d75d5402eeb9aeb5b50268047e064 Mon Sep 17 00:00:00 2001 From: riqo Date: Sat, 19 Jun 2021 15:22:17 -0500 Subject: [PATCH 12/14] refactor frontend ipc --- src/account.ts | 19 +++++++++++--- src/composables/useIpcMain.ts | 18 +++++-------- src/ipc/handlers.ts | 3 ++- src/ipc/listeners.ts | 7 +++++ src/main.ts | 7 ++--- src/render/App.vue | 24 ++++------------- src/render/composables/useIpcRend.ts | 39 +++++++++++++++++++++++++--- src/render/composables/useProfile.ts | 16 +++++++++--- src/render/ipc.ts | 31 ++++++++++++++++++++++ src/render/listeners.ts | 32 +++++++++++++++++++++++ src/render/main.ts | 9 +++++-- src/store.ts | 14 +++++++++- src/types.ts | 10 ++++--- 13 files changed, 177 insertions(+), 52 deletions(-) create mode 100644 src/render/listeners.ts diff --git a/src/account.ts b/src/account.ts index 9f867f6..581da0e 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,8 +1,9 @@ import { postAuth, postLogin, postLogout } from "@/api/account"; import { endSession, launchSession } from "@/session"; -import { getToken, clearToken, setToken } from "./store"; +import { getToken, setToken, setProfile, getProfile, clearStore } from "./store"; import { parseAuthRes } from "./auth"; +import { ipcEmit } from "@/composables/useEmitter"; export const accountAuth = async (): Promise => { @@ -18,6 +19,7 @@ export const accountAuth = async (): Promise => { const parsed = parseAuthRes(res); setToken(parsed.token) + setProfile(parsed.profile); return { profile: parsed.profile, @@ -26,7 +28,7 @@ export const accountAuth = async (): Promise => { } catch(e) { console.log('[ACCOUNT]', e); - clearToken(); + clearStore(); throw(new Error('Failed to authenticate.')); } @@ -45,6 +47,7 @@ export const accountLogin: IpcHandlerCallback = asy // save jwt token and profile setToken(parsed.token); + setProfile(parsed.profile); // launch session launchSession(parsed.token); @@ -53,6 +56,7 @@ export const accountLogin: IpcHandlerCallback = asy return parsed.profile; } catch(e) { + clearStore(); throw e; } } @@ -65,7 +69,7 @@ export const accountLogout = async (): Promise => { await postLogout(); // remove key and crimataId - clearToken(); + clearStore(); // kill crimata platform session endSession(); @@ -79,4 +83,13 @@ export const accountLogout = async (): Promise => { } +export const updateAppState = (): void => { + + const profile = getProfile(); + + ipcEmit("set-profile", profile); + + // ipcEmit('messages') etc + +} diff --git a/src/composables/useIpcMain.ts b/src/composables/useIpcMain.ts index 57a2cb3..ebfc4bc 100644 --- a/src/composables/useIpcMain.ts +++ b/src/composables/useIpcMain.ts @@ -1,22 +1,21 @@ import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron"; -export class IpcHandler implements IIpcHandler { +export class IpcHandler implements IIpcHandler { readonly channel: string; - readonly _handlerCallback: IpcHandlerCallback; + readonly _handlerCallback: IpcHandlerCallback; constructor(options: { channel: string; - handlerCallback: IpcHandlerCallback; + handlerCallback: IpcHandlerCallback; }) { this.channel = options.channel; this._handlerCallback = options.handlerCallback; } handle() { - console.log(`[IPC] INIT: ${this.channel}`); this.remove(); ipcMain.handle(this.channel, this._onInvoke); } @@ -49,22 +48,21 @@ export class IpcHandler implements IIpcHandler implements IIpcListener { +export class IpcListener implements IIpcListener { readonly channel: string; - readonly _listenerCallback: IpcListenerCallback; + readonly _listenerCallback: IpcListenerCallback; constructor(options: { channel: string; - listenerCallback: IpcListenerCallback; + listenerCallback: IpcListenerCallback; }) { this.channel = options.channel; this._listenerCallback = options.listenerCallback; } listen() { - console.log(`[IPC] Init: ${this.channel}`); this.remove(); ipcMain.on(this.channel, this._onPost); } @@ -82,7 +80,3 @@ export class IpcListener implements IIpcListener { } } - - - - diff --git a/src/ipc/handlers.ts b/src/ipc/handlers.ts index bc44fc2..5529662 100644 --- a/src/ipc/handlers.ts +++ b/src/ipc/handlers.ts @@ -1,10 +1,11 @@ "use strict"; -import { accountLogin, accountLogout } from "@/account"; +import { accountLogin, accountLogout, accountProfile } from "@/account"; import { IpcHandler } from "@/composables/useIpcMain"; import { flush } from "@/audio"; + const LOGIN_CHANNEL = "invoke-account-login"; const LOGOUT_CHANNEL = "invoke-account-logout"; const GET_AUDIO_CHANNEL = "invoke-audio-flush"; diff --git a/src/ipc/listeners.ts b/src/ipc/listeners.ts index f88887a..8ef5d45 100644 --- a/src/ipc/listeners.ts +++ b/src/ipc/listeners.ts @@ -2,9 +2,11 @@ import { IpcListener } from "@/composables/useIpcMain" import { sendMessage } from '@/session'; import { collect } from "@/audio"; +import { updateAppState } from "@/account"; const CLIENT_MESSAGE_CHANNEL = "post-session-send" const GET_AUDIO_CHANNEL = "post-audio-collect"; +const APP_MOUNT_CHANNEL = "post-app-mount"; export const messageListener = new IpcListener({ channel: CLIENT_MESSAGE_CHANNEL, @@ -15,3 +17,8 @@ export const audioChunkListener = new IpcListener({ channel: GET_AUDIO_CHANNEL, listenerCallback: collect }); + +export const appMountListener = new IpcListener({ + channel: APP_MOUNT_CHANNEL, + listenerCallback: updateAppState +}); diff --git a/src/main.ts b/src/main.ts index 40dd4da..a360835 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,9 +10,8 @@ */ import initIpcMain from "@/ipc/index"; -import { accountAuth } from "./account"; +import { accountAuth, updateAppState } from "./account"; import { launchSession } from "./session"; -import { ipcEmit } from "./composables/useEmitter"; import createWindow from "./window"; let authState: AuthState | null; @@ -31,12 +30,10 @@ export default async function main() { console.log('AUTH:', e); authState = null; } finally { - let profile = null; if (authState) { launchSession(authState.token as string); - profile = authState.profile; } - ipcEmit("set-profile", profile); + updateAppState(); } } diff --git a/src/render/App.vue b/src/render/App.vue index d53788f..a7645d1 100644 --- a/src/render/App.vue +++ b/src/render/App.vue @@ -23,14 +23,16 @@