refactor main and rendered ipc
This commit is contained in:
parent
17e4b06948
commit
ddb2f4df0f
28 changed files with 758 additions and 211 deletions
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import { ipcMain } from "electron";
|
||||
import { backgroundMitt } from '@/modules/emitter';
|
||||
|
||||
const portAudio = require('naudiodon');
|
||||
|
||||
// Audio in and out stream objects.
|
||||
|
|
@ -26,27 +24,22 @@ const audioOptions = {
|
|||
closeOnError: false,
|
||||
}
|
||||
|
||||
// Toggles record to true to begin capturing chunks.
|
||||
const onRecordingStart = (_event: any, _payload: any) => {
|
||||
console.log("AUDIO: Beginning audio capture.")
|
||||
record = true;
|
||||
}
|
||||
export const toggleRecord = (): void => { record = !record };
|
||||
|
||||
// Returns recorded audio to frontend and sets record to false.
|
||||
const onRecordingEnd = async (_event: any, payload: any) => {
|
||||
console.log("AUDIO:Sending audio to browser.")
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
export const fetchAudioInput = (): Promise<Error | string> => (
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
|
||||
try {
|
||||
resolve(audioContainer.input);
|
||||
record = false;
|
||||
toggleRecord();
|
||||
} catch (e) {
|
||||
reject()
|
||||
reject(new Error('Failed to fetch the audio.'))
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
// Main audio function run by run.ts module.
|
||||
|
|
@ -86,15 +79,6 @@ export function initAudioIO(): void {
|
|||
ao.start();
|
||||
|
||||
}
|
||||
|
||||
// Listen to record.
|
||||
console.log("AUDIO:Adding recording listeners.")
|
||||
|
||||
ipcMain.removeAllListeners("start-recording");
|
||||
ipcMain.on("start-recording", onRecordingStart);
|
||||
|
||||
ipcMain.removeHandler("stop-recording");
|
||||
ipcMain.handle("stop-recording", onRecordingEnd);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
|
||||
import { app, dialog } from "electron";
|
||||
import { createWindow } from './window';
|
||||
import { initSession } from './session';
|
||||
import { initAudioIO, stopStream } from './audio';
|
||||
import { stopStream } from './audio';
|
||||
import { backgroundMitt } from '@/modules/emitter';
|
||||
import useIpc from "@/background/ipc/index";
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
|
||||
let win: boolean;
|
||||
|
||||
|
|
@ -15,7 +16,6 @@ backgroundMitt.on('window-active', (state: boolean) => {
|
|||
});
|
||||
|
||||
// Auto updating.
|
||||
const { autoUpdater } = require('electron-updater')
|
||||
autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' }
|
||||
|
||||
autoUpdater.on('update-available', (info: any) => {
|
||||
|
|
@ -38,19 +38,18 @@ autoUpdater.on('update-downloaded', (info: any) => {
|
|||
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Run when electron app is initialized.
|
||||
async function main(dev: boolean) {
|
||||
console.log("MAIN:Initializing Electron App.")
|
||||
async function main(): Promise<void> {
|
||||
|
||||
console.log("MAIN:Initializing Electron App.");
|
||||
|
||||
useIpc();
|
||||
|
||||
// Must wait til window is created.
|
||||
await createWindow();
|
||||
|
||||
// Instantiate socket session with crimata-platorm.
|
||||
initSession(dev);
|
||||
|
||||
// Begin audio stream.
|
||||
initAudioIO();
|
||||
|
||||
}
|
||||
|
||||
// Root function of app.
|
||||
|
|
@ -58,8 +57,8 @@ export function initApp(dev: boolean): void {
|
|||
|
||||
// On initial startup.
|
||||
app.on("ready", () => {
|
||||
if (!dev) autoUpdater.checkForUpdates()
|
||||
main(dev)
|
||||
// autoUpdater.checkForUpdates()
|
||||
main();
|
||||
});
|
||||
|
||||
// Must keep to ensure app doesn't quit on close.
|
||||
|
|
|
|||
117
src/background/ipc/account.ts
Normal file
117
src/background/ipc/account.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import { Profile } from "@/types";
|
||||
import { submit, fetchProfile, logout } from "@/api/account";
|
||||
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
||||
import { store } from "@/background/store";
|
||||
|
||||
|
||||
const parseAuthRes = (authRes: any) => {
|
||||
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
|
||||
const profile = authRes.data as Profile;
|
||||
return {
|
||||
token,
|
||||
profile
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const onProfile = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
_payload: null
|
||||
): Promise<Profile | Error> => (
|
||||
|
||||
new Promise(async (resolve, reject) => {
|
||||
console.log('[IPC]: user-profile');
|
||||
|
||||
// get jwt token and crimataId from store
|
||||
const token = store.get('key');
|
||||
const crimataId = store.get('crimataId');
|
||||
|
||||
// authenticate and fetch profile
|
||||
try {
|
||||
|
||||
const res = await fetchProfile(
|
||||
crimataId,
|
||||
token
|
||||
);
|
||||
|
||||
const parsed = parseAuthRes(res);
|
||||
resolve(parsed.profile);
|
||||
|
||||
} catch(e) {
|
||||
reject(new Error('Failed to fetch profile.'));
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
const onLogin = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
payload: string
|
||||
): Promise<Profile | Error> => (
|
||||
|
||||
new Promise(async (resolve, reject) => {
|
||||
console.log('[IPC]: user-login');
|
||||
|
||||
const account = JSON.parse(payload);
|
||||
|
||||
if ( account.password && account.email ) {
|
||||
try {
|
||||
const res = await submit(account.email, account.password);
|
||||
const parsed = parseAuthRes(res);
|
||||
|
||||
// save jwt token and profile
|
||||
store.set('key', parsed.token);
|
||||
store.set('crimataId', parsed.profile.crimataId);
|
||||
|
||||
// return profile to renderer
|
||||
resolve(parsed.profile);
|
||||
|
||||
} catch(e) {
|
||||
console.log('[API]', e.response);
|
||||
reject(new Error('Failed to authenticate'));
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const onLogout = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
_payload: null
|
||||
): Promise<void> => (
|
||||
|
||||
new Promise(async (resolve, reject) => {
|
||||
console.log('[IPC]: user-logout');
|
||||
|
||||
try {
|
||||
// post logout to backend
|
||||
await logout();
|
||||
|
||||
// remove key and crimataId
|
||||
store.delete('key');
|
||||
store.delete('crimataId');
|
||||
|
||||
// TODO: kill crimata platform session
|
||||
|
||||
resolve();
|
||||
} catch(e) {
|
||||
reject(new Error('Failed to logout. Please try again.'));
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
export default function useAccountListeners(): void {
|
||||
|
||||
ipcMain.removeHandler("user-profile");
|
||||
ipcMain.handle("user-profile", onProfile);
|
||||
|
||||
ipcMain.removeHandler("user-login");
|
||||
ipcMain.handle("user-login", onLogin);
|
||||
|
||||
ipcMain.removeHandler("user-logout");
|
||||
ipcMain.handle("user-logout", onLogout);
|
||||
|
||||
}
|
||||
40
src/background/ipc/audio.ts
Normal file
40
src/background/ipc/audio.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
|
||||
import { fetchAudioInput, toggleRecord } from "@/background/audio";
|
||||
|
||||
|
||||
// Toggles record to true to begin capturing chunks.
|
||||
const onRecordingStart = (
|
||||
_event: IpcMainEvent,
|
||||
_payload: null
|
||||
): void => {
|
||||
|
||||
console.log('[IPC]: start-recording');
|
||||
|
||||
toggleRecord();
|
||||
};
|
||||
|
||||
|
||||
// Returns recorded audio to frontend and sets record to false.
|
||||
const onRecordingStop = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
_payload: null
|
||||
): Promise<Error | string> => {
|
||||
|
||||
console.log('[IPC]: stop-recording');
|
||||
|
||||
return await fetchAudioInput()
|
||||
};
|
||||
|
||||
|
||||
export default function useAudioListeners(): void {
|
||||
|
||||
ipcMain.removeAllListeners("start-recording");
|
||||
ipcMain.on("start-recording", onRecordingStart);
|
||||
|
||||
ipcMain.removeHandler("stop-recording");
|
||||
ipcMain.handle("stop-recording", onRecordingStop);
|
||||
|
||||
}
|
||||
17
src/background/ipc/index.ts
Normal file
17
src/background/ipc/index.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import useAccountListeners from "./account";
|
||||
import useSessionListeners from "./session";
|
||||
import useAudioListeners from "./audio";
|
||||
|
||||
|
||||
export default function useIpc(): void {
|
||||
|
||||
useAccountListeners();
|
||||
|
||||
useSessionListeners();
|
||||
|
||||
useAudioListeners();
|
||||
|
||||
}
|
||||
62
src/background/ipc/session.ts
Normal file
62
src/background/ipc/session.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
import { initSession, emitNewMessages, sendMessage } from '@/background/session';
|
||||
import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
|
||||
import { initAudioIO } from "@/background/audio";
|
||||
import { ClientMessage } from "@/types";
|
||||
|
||||
|
||||
// Instantiate socket session with crimata-platorm.
|
||||
const onSessionInit = (
|
||||
_event: IpcMainInvokeEvent,
|
||||
cid: string
|
||||
): void => {
|
||||
|
||||
console.log('[IPC]: init-session');
|
||||
|
||||
initSession(cid);
|
||||
|
||||
initAudioIO();
|
||||
}
|
||||
|
||||
|
||||
const onAppMounted = (
|
||||
_event: IpcMainInvokeEvent,
|
||||
_payload: null
|
||||
): void => {
|
||||
|
||||
console.log('[IPC]: app-mounted');
|
||||
|
||||
emitNewMessages()
|
||||
};
|
||||
|
||||
|
||||
// Handle messages from window/client.
|
||||
const onClientMessage = (
|
||||
_event: IpcMainEvent,
|
||||
payload: ClientMessage
|
||||
): void => {
|
||||
|
||||
console.log('[IPC]: client-message');
|
||||
|
||||
sendMessage(payload);
|
||||
}
|
||||
|
||||
|
||||
export default function useSessionListeners(): void {
|
||||
|
||||
console.log('[IPC]: Init session listeners.');
|
||||
|
||||
// Attach listeners for frontend.
|
||||
ipcMain.removeAllListeners("client-message");
|
||||
ipcMain.on("client-message", onClientMessage);
|
||||
|
||||
// Attack browser window init listener.
|
||||
ipcMain.removeAllListeners("app-mounted");
|
||||
ipcMain.on("app-mounted", onAppMounted);
|
||||
|
||||
ipcMain.removeAllListeners("init-session");
|
||||
ipcMain.on("init-session", onSessionInit);
|
||||
|
||||
}
|
||||
|
|
@ -1,82 +1,35 @@
|
|||
/*
|
||||
* Creates a websocket session with Crimata Servers.
|
||||
*
|
||||
* Connects to Servers and attempts key authentication. Server will respond
|
||||
* with key and user profile. We send the profile to the browser. We also
|
||||
* resend this information on new broser window. We then serve as a
|
||||
* communication interface between the window and the servers. It will
|
||||
*
|
||||
* Connects to Servers and attempts key authentication. Server will respond
|
||||
* with key and user profile. We send the profile to the browser. We also
|
||||
* resend this information on new broser window. We then serve as a
|
||||
* communication interface between the window and the servers. It will
|
||||
* automatically try to reconnect on websocket disconnect.
|
||||
*/
|
||||
|
||||
import { backgroundMitt } from "@/modules/emitter";
|
||||
import { ipcEmit, loadState, saveToJson } from './helpers';
|
||||
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
|
||||
import { ipcEmit, loadState } from './helpers';
|
||||
|
||||
import useWebSockets from "@/modules/websockets";
|
||||
import useWebSockets from "./websockets";
|
||||
|
||||
import { play } from "./audio";
|
||||
import { renderMessage } from "@/modules/message";
|
||||
import { AuthProtocol, SessionState, Profile } from "@/types";
|
||||
import { SessionState } from "@/types";
|
||||
|
||||
let win = true;
|
||||
|
||||
// Info saved to json on quit (key, newMessages).
|
||||
let state: SessionState;
|
||||
|
||||
// Profile of current user.
|
||||
let profile: Profile | boolean;
|
||||
|
||||
// Called when server sends auth message.
|
||||
const updateState = (res: AuthProtocol) => {
|
||||
console.log("SESS:Auth message received: \n" +
|
||||
` key: ${res.key}\n` +
|
||||
` alias: ${res.profile}`)
|
||||
|
||||
if (state) {
|
||||
|
||||
// Update key.
|
||||
state.key = res.key;
|
||||
|
||||
// Update the user profile.
|
||||
profile = res.profile;
|
||||
|
||||
// Send upated profile to frontend.
|
||||
console.log("SESS:Sending updated user profile to browser.")
|
||||
ipcEmit("update-state", {
|
||||
profile: profile,
|
||||
newMessages: state.newMessages
|
||||
})
|
||||
|
||||
// Save the updated state to json.
|
||||
console.log("SESS:Saving session state.")
|
||||
saveToJson("session.json", state)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Send state on new window.
|
||||
const onNewBrowserWindow = (_event: IpcMainInvokeEvent, _payload: any) => {
|
||||
if (typeof profile !== 'undefined') {
|
||||
console.log("SESS:Sending user profile to browser.")
|
||||
ipcEmit("update-state", {
|
||||
profile: profile,
|
||||
newMessages: state.newMessages
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Calls appropriate endpoint for a server message.
|
||||
const onMessage = (data: string) => {
|
||||
let message = JSON.parse(data)
|
||||
|
||||
// AuthProtocol message.
|
||||
if (message.hasOwnProperty("key")) {
|
||||
updateState(message)
|
||||
}
|
||||
const onMessage = (data: string): void => {
|
||||
let message = JSON.parse(data);
|
||||
console.log('received new message', message);
|
||||
|
||||
// Standard message.
|
||||
else if (message.content) {
|
||||
if (message.content) {
|
||||
|
||||
// Convert to render message
|
||||
message = renderMessage(
|
||||
|
|
@ -93,7 +46,7 @@ const onMessage = (data: string) => {
|
|||
}
|
||||
ipcEmit("render-message", message)
|
||||
}
|
||||
|
||||
|
||||
else {
|
||||
console.log("SESS:No window: saving message.")
|
||||
state.newMessages.push(message);
|
||||
|
|
@ -108,57 +61,40 @@ const onMessage = (data: string) => {
|
|||
|
||||
};
|
||||
|
||||
// When socket connects, we update state.
|
||||
const onOpen = () => {
|
||||
console.log(`SESS:Sending key: ${state.key}`)
|
||||
|
||||
if (state) {
|
||||
sendMessage({
|
||||
"key": state.key,
|
||||
"usr": false,
|
||||
"pwd": false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Websockets module.
|
||||
const { createSocket, sendMessage } = useWebSockets(onMessage, onOpen);
|
||||
const { createSocket, send } = useWebSockets(onMessage);
|
||||
|
||||
// Handle messages from window/client.
|
||||
const onClientMessage = (_event: IpcMainEvent, payload: any) => {
|
||||
console.log("New client message")
|
||||
|
||||
const success = sendMessage(payload)
|
||||
|
||||
if (!success) {
|
||||
console.log("Unable to send message: ", payload)
|
||||
export const emitNewMessages = (): void => {
|
||||
if (state) {
|
||||
ipcEmit("update-state", {
|
||||
newMessages: state.newMessages,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const sendMessage = (payload: Record<string, any>): void => {
|
||||
try {
|
||||
send(payload)
|
||||
} catch(e) {
|
||||
console.log("Unable to send message: ", payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Call this to initialize session with Crimata servers.
|
||||
export const initSession = (dev: boolean) => {
|
||||
export const initSession = (cid: string): void => {
|
||||
console.log("SESS:Creating new session.")
|
||||
|
||||
// Load Json or createState.
|
||||
state = loadState("session.json")
|
||||
|
||||
console.log("SESS:State loaded: \n" +
|
||||
` key: ${state.key}\n` +
|
||||
` new: ${state.newMessages}`)
|
||||
state = loadState("session.json");
|
||||
|
||||
// Open socket connection.
|
||||
let url = "ws://crimata.com:8760";
|
||||
if (dev) url = "ws://localhost:8760";
|
||||
createSocket(url)
|
||||
|
||||
// Attack browser window init listener.
|
||||
ipcMain.removeAllListeners("app-mounted")
|
||||
ipcMain.on("app-mounted", onNewBrowserWindow);
|
||||
|
||||
// Attach listeners for frontend.
|
||||
ipcMain.removeAllListeners("client-message")
|
||||
ipcMain.on("client-message", onClientMessage);
|
||||
createSocket();
|
||||
|
||||
// Keep win up-to-date.
|
||||
backgroundMitt.on('window-active', (state: boolean) => {
|
||||
|
|
|
|||
16
src/background/store.ts
Normal file
16
src/background/store.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
const Store = require('electron-store');
|
||||
|
||||
const schema = {
|
||||
key: {
|
||||
type: 'string',
|
||||
},
|
||||
crimataId: {
|
||||
type: 'string'
|
||||
}
|
||||
};
|
||||
|
||||
export const store = new Store({
|
||||
schema,
|
||||
encryptionKey: "super user test"
|
||||
});
|
||||
88
src/background/websockets.ts
Normal file
88
src/background/websockets.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"use strict";
|
||||
|
||||
import WebSocket from 'ws';
|
||||
|
||||
let socket: WebSocket;
|
||||
|
||||
const socketUrl = process.env.PLATFORM_URL;
|
||||
|
||||
// Run every time we want to connect to backend.
|
||||
export default function useWebSockets(
|
||||
receiveCallback: (s: string) => 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<string, any>): Promise<boolean> => (
|
||||
new Promise((resolve, reject) => {
|
||||
if (socket.readyState !== 1) {
|
||||
reject(false);
|
||||
}
|
||||
socket.send(JSON.stringify(data))
|
||||
resolve(true);
|
||||
|
||||
}))
|
||||
|
||||
|
||||
const onOpen = (_event: WebSocket.OpenEvent) => {
|
||||
|
||||
console.log("WS:Connected to WS Server!");
|
||||
|
||||
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 = () => {
|
||||
if (socketUrl)
|
||||
socket = new WebSocket(socketUrl)
|
||||
|
||||
// Add listeners.
|
||||
socket.addEventListener("open", onOpen)
|
||||
socket.addEventListener("message", onServerMessage)
|
||||
socket.addEventListener("close", onClose)
|
||||
socket.addEventListener("error", onError)
|
||||
|
||||
console.log("WS:New socket created.")
|
||||
}
|
||||
|
||||
return {
|
||||
createSocket,
|
||||
sendMessage,
|
||||
send
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -57,7 +57,6 @@ const saveWindowState = () => {
|
|||
|
||||
// Do this on window mount.
|
||||
const onWindowMount = (): void => {
|
||||
console.log("BW:Adding window listeners. ")
|
||||
|
||||
// Must tell initApp that window exists.
|
||||
backgroundMitt.emit('window-active', true);
|
||||
|
|
@ -70,12 +69,10 @@ const onWindowMount = (): void => {
|
|||
backgroundMitt.removeAllListeners("ipc-renderer")
|
||||
backgroundMitt.on("ipc-renderer", renderMessage);
|
||||
|
||||
console.log("BW:Listeners created.")
|
||||
}
|
||||
|
||||
// Do this on window dismount (close).
|
||||
const onWindowDismount = (): void => {
|
||||
console.log("BW:Window closed.")
|
||||
win = null;
|
||||
backgroundMitt.emit('window-active', false);
|
||||
}
|
||||
|
|
@ -89,7 +86,6 @@ export async function createWindow(): Promise<void> {
|
|||
|
||||
// Load the saved window state.
|
||||
winState = loadWinState("window.json")
|
||||
console.log(`BW:Creating window [${winState.width}, ${winState.height}].`)
|
||||
|
||||
// Define the browser window.
|
||||
win = new BrowserWindow({
|
||||
|
|
|
|||
Loading…
Reference in a new issue