Merge remote-tracking branch 'origin/main' into new_architecture
This commit is contained in:
commit
ab487e4f5e
9 changed files with 695 additions and 1 deletions
119
src/background/ipc/account.ts
Normal file
119
src/background/ipc/account.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
"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<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('Unable to authenticate and fetch account 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);
|
||||
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
|
||||
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);
|
||||
|
||||
}
|
||||
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,
|
||||
_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);
|
||||
|
||||
}
|
||||
119
src/background/session.ts
Normal file
119
src/background/session.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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<string, any>): 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;
|
||||
});
|
||||
|
||||
}
|
||||
122
src/background/websockets.ts
Normal file
122
src/background/websockets.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
|
||||
"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<typeof setTimeout>;
|
||||
|
||||
|
||||
// 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!");
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue