From: Enrique Hernandez Date: Wed, 10 Feb 2021 15:00:42 +0000 (-0600) Subject: add socket connect and reconnect to session code X-Git-Tag: v0.9~92 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=b05cb202b6e4b5966d01ece8edd15494ed941d8e;p=mime-chat add socket connect and reconnect to session code --- diff --git a/src/background/createWindow.ts b/src/background/createWindow.ts index 02aadf9..22b1cd5 100644 --- a/src/background/createWindow.ts +++ b/src/background/createWindow.ts @@ -4,35 +4,39 @@ import { BrowserWindow } from "electron"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; import * as path from "path"; -export function createWindow(options: { +interface WindowSettings { width: number; height: number; resizable: boolean; -}): BrowserWindow { +} - const win: BrowserWindow = new BrowserWindow({ - width: options.width, - height: options.height, - resizable: options.resizable, - webPreferences: { - // Use pluginOptions.nodeIntegration, leave this alone - // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info - nodeIntegration: (process.env - .ELECTRON_NODE_INTEGRATION as unknown) as boolean, - preload: path.join(__dirname, "preload.js") - } - }); +export const createWindow = async (options: WindowSettings): Promise => { + return new Promise((resolve, reject) => { + const win: BrowserWindow = new BrowserWindow({ + width: options.width, + height: options.height, + resizable: options.resizable, + webPreferences: { + // Use pluginOptions.nodeIntegration, leave this alone + // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info + nodeIntegration: (process.env + .ELECTRON_NODE_INTEGRATION as unknown) as boolean, + preload: path.join(__dirname, "preload.js") + } + }); - if (process.env.WEBPACK_DEV_SERVER_URL) { - // Load the url of the dev server if in development mode - win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); - } else { - createProtocol("app"); - // Load the index.html when not in development - win.loadURL("app://./index.html"); - } + if (process.env.WEBPACK_DEV_SERVER_URL) { + // Load the url of the dev server if in development mode + win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); + } else { + createProtocol("app"); + // Load the index.html when not in development + win.loadURL("app://./index.html"); + } - return win; + win.webContents.on('did-finish-load', () => { + resolve(win); + }); + }) } - diff --git a/src/background/run.ts b/src/background/run.ts index b8af002..63d7b3d 100644 --- a/src/background/run.ts +++ b/src/background/run.ts @@ -1,6 +1,6 @@ import { createWindow } from './createWindow'; import { ipcMain } from "electron"; -import { initSession, sendMessage, authUser, authLogin} from './session'; +import { initSession, sendMessage } from './session'; import { windowEmitter } from './windowEmitter'; const portAudio = require('naudiodon'); @@ -9,13 +9,15 @@ interface ipcRendererPayload { message: any; } +let activeSession = false; + /* * The main function will be run after electron app is ready. */ -export function main(): void { +export async function main() { // create main window. - const win = createWindow({ width: 350, height: 525, resizable: false }); + const win = await createWindow({ width: 350, height: 525, resizable: false }); windowEmitter.on('ipc-renderer', (payload: ipcRendererPayload) => { win.webContents.send(payload.endpoint, { @@ -23,22 +25,24 @@ export function main(): void { }); }); + // Instantiate socket session with crimata-platorm. + if (!activeSession) { + initSession(); + } + activeSession = true; + const handleMessage = (event: any, arg: any) => { sendMessage(arg); } const windowMount = (): void => { - // Instantiate socket session with crimata-platorm. - initSession(); windowEmitter.emit('window-active', true); ipcMain.on('send-message', handleMessage); - ipcMain.on('auth-login', authLogin); } const windowDismount = (): void => { windowEmitter.emit('window-active', false); ipcMain.removeAllListeners('send-message'); - ipcMain.removeAllListeners('auth-login'); } // Handle window mount and dismount. diff --git a/src/background/session.ts b/src/background/session.ts index 4612757..8902281 100644 --- a/src/background/session.ts +++ b/src/background/session.ts @@ -2,77 +2,84 @@ import WebSocket from 'ws'; import { windowEmitter } from './windowEmitter'; import { ipcMain } from "electron"; -type Message = { - type: string; - id: string; -} - -interface RenderMessage extends Message { +interface RenderMessage { + type: 'render'; content: string; context: string; subContext: string; modifiers: string; time: string; + id: string; } -interface AuthUser extends Message { +interface UserCreds { email: string; - firstName: string; - lastName: string; - access_token: string; + password: string; } const ip = 'ws://localhost'; -const port = 8080; +const port = 8081; const reconnectTimeout = 3000; //ms let socket: WebSocket; let success = false; +let auth = false; const receiveMessage = (message: string): void => { - // NOTE assuming message is JSON string - if (message === 'locked') { - windowEmitter.emit('auth-resp', message); - return; - } - const parsed: RenderMessage | AuthUser = JSON.parse(message); - if (parsed.type === "render") { - windowEmitter.emit('ipc-renderer', { - endpoint: 'render-message', - message: parsed - }); - } + + while(!auth) { + windowEmitter.emit('auth-res', message); + return; + } + + const parsed: RenderMessage = JSON.parse(message); + if (parsed.type === "render") { + windowEmitter.emit('ipc-renderer', { + endpoint: 'render-message', + message: parsed + }); + } } -const authToken = async (event: any, token: string): Promise => { +const authUser = async (event: any, payload: string | UserCreds | null): Promise => { return new Promise((resolve, reject) => { console.log('authenticating...'); - windowEmitter.on('auth-resp', (arg: string | AuthUser) => { - if (arg === 'locked') { - reject(arg); + windowEmitter.on('auth-res', (res: string) => { + if (res === 'locked') { + reject(res); + } else { + auth = true; + resolve(res); } - resolve(arg); }); - if (token) { - socket.send(token); - } else { + if (!payload) { socket.send('no-token'); + } else if (typeof payload === 'string' || payload instanceof String) { + socket.send(payload); + } else { + socket.send(JSON.stringify(payload)); } }); }; -// Connect and authenticate -export function initSession() { +export const initSession = () => { + if (socket) { + socket.removeAllListeners(); + socket.terminate(); + socket.close(); + success = false; + } + + console.log('Connecting to Crimata Servers...'); socket = new WebSocket(`${ip}:${port}`); socket.binaryType = 'arraybuffer'; socket.on('open', () => { - console.log('Connected to crimata-platform'); - success = true; + console.log('Success! Connected to Crimata.'); // handle renderer auth-token event - ipcMain.removeHandler('auth-token'); // avoid setting duplicate handlers - ipcMain.handle('auth-token', authToken); + ipcMain.removeHandler('auth-user'); // avoid setting duplicate handlers + ipcMain.handle('auth-user', authUser); // fetch token from renderer windowEmitter.emit('ipc-renderer', { @@ -80,28 +87,28 @@ export function initSession() { message: null }); - }) + success = true; + }); - socket.on("message", receiveMessage); + socket.on('error', (e) => { + console.log('ERROR: Failed to connect.'); + socket.removeAllListeners(); + socket.close(); + setTimeout(()=> { + if (!success) { + console.log('Attempting reconnect.'); + initSession(); + } + }, reconnectTimeout); - setTimeout(()=> { - if (!success) { - // initSession(); - throw new Error('Unable to connect to crimata-platform'); - } - }, reconnectTimeout); -} + }) -export const authLogin = (event: any, arg: any) => { - try { - sendMessage(arg) - windowEmitter.on('auth-resp', (payload: AuthUser) => { - event.reply('auth-login-reply', payload); - }) - } catch(e) { - console.log('Error loging in' + e); - event.reply('auth-login-reply', e); - } + socket.on('close', () => { + console.log('Connection droped. Restarting.') + initSession(); + }) + + socket.on("message", receiveMessage); } export const sendMessage = (content: string | Buffer) => { diff --git a/src/background/windowEmitter.ts b/src/background/windowEmitter.ts index a56c7df..65bf9ae 100644 --- a/src/background/windowEmitter.ts +++ b/src/background/windowEmitter.ts @@ -3,9 +3,3 @@ const EventEmitter = require('events'); class WindowEmitter extends EventEmitter {} export const windowEmitter = new WindowEmitter(); - -const ipcRend = (endpoint: string, message: any) => { - win.webContents.send(endpoint, { - message: message - }) -} diff --git a/src/modules/auth.ts b/src/modules/auth.ts index ec8e444..ae908f1 100644 --- a/src/modules/auth.ts +++ b/src/modules/auth.ts @@ -1,23 +1,13 @@ import { reactive, watch, toRefs } from 'vue'; import { useIpc } from './ipc'; -interface User { - id: string; - email: string; - firstName: string; - lastName: string; - access_token: string; -} - interface AuthState { - authenticating: boolean; - user?: User; + access_token?: string | null; error?: Error; } const state = reactive({ - authenticating: false, - user: undefined, + access_token: undefined, error: undefined, }); @@ -26,11 +16,11 @@ const AUTH_KEY = 'crimata_token'; const token = window.localStorage.getItem(AUTH_KEY); // authenticate on socket connect -window.ipcRenderer.on("fetch-token", async (event, payload) => { - const { data, invoke } = useIpc('auth-token'); +window.ipcRenderer.on("fetch-token", async (event, payload: null) => { + const { data, invoke } = useIpc('auth-user'); try { await invoke(token); - state.user = data.value; + state.access_token = data.value; } catch(e) { state.error = e; @@ -39,51 +29,25 @@ window.ipcRenderer.on("fetch-token", async (event, payload) => { } }); -if (token) { - const { loading, error, data, send, invoke } = useIpc('auth-user'); - - state.authenticating = true; - - // authenticate token against crimata-platform - console.log('sending token to main') - send({ - token: token - }); - - watch(loading, () => { - if (error.value) { - console.log('ERROR: failed authentication') - window.localStorage.removeItem(AUTH_KEY); - } - else if (data.value) { - state.user = data.value; - } - - state.authenticating = false; - }) -} else { - console.log('no token to send') -} - export const useAuth = () => { - const setUser = (payload: User, remember: boolean) => { + const setToken = (token: string, remember: boolean) => { if (remember) { // Save - window.localStorage.setItem(AUTH_KEY, payload.access_token); + window.localStorage.setItem(AUTH_KEY, token); } - state.user = payload; + state.access_token = token; state.error = undefined; } const logout = (): Promise => { window.localStorage.removeItem(AUTH_KEY); - return Promise.resolve(state.user = undefined); + return Promise.resolve(state.access_token = undefined); } return { - setUser, + setToken, logout, - ...toRefs(state), // authenticating, user, error + ...toRefs(state), // access_token, error } } diff --git a/src/router.ts b/src/router.ts index 30c92b2..a0602ab 100644 --- a/src/router.ts +++ b/src/router.ts @@ -35,13 +35,13 @@ const router = createRouter({ }); router.beforeEach((to, from, next) => { - const { user } = useAuth(); + const { access_token } = useAuth(); // Not logged into a guarded route? - if (to.meta.requiresAuth && !user?.value) next({ name: 'login' }); + if (to.meta.requiresAuth && !access_token?.value) next({ name: 'login' }); // Logged in for an auth route - else if ((to.name == 'login' || to.name == 'register') && user!.value) next({ name: 'home' }); + else if ((to.name == 'login' || to.name == 'register') && access_token!.value) next({ name: 'home' }); // Carry On... else next(); diff --git a/src/views/login.vue b/src/views/login.vue index 185b231..38b96b6 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -12,13 +12,16 @@ + + +