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..24d13b1 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", @@ -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", @@ -73,7 +72,9 @@ "lintOnSave": false, "pluginOptions": { "electronBuilder": { - "preload": "src/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/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..581da0e --- /dev/null +++ b/src/account.ts @@ -0,0 +1,95 @@ + +import { postAuth, postLogin, postLogout } from "@/api/account"; +import { endSession, launchSession } from "@/session"; +import { getToken, setToken, setProfile, getProfile, clearStore } from "./store"; +import { parseAuthRes } from "./auth"; +import { ipcEmit } from "@/composables/useEmitter"; + +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) + setProfile(parsed.profile); + + return { + profile: parsed.profile, + token: parsed.token + }; + + } catch(e) { + console.log('[ACCOUNT]', e); + clearStore(); + 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); + setProfile(parsed.profile); + + // launch session + launchSession(parsed.token); + + // return profile to renderer + return parsed.profile; + + } catch(e) { + clearStore(); + throw e; + } +} + + +export const accountLogout = async (): Promise => { + + try { + // post logout to backend + await postLogout(); + + // remove key and crimataId + clearStore(); + + // kill crimata platform session + endSession(); + + return; + + } catch(e) { + console.log('[ACCOUNT]', e); + return (new Error('Failed to logout. Please try again.')); + } + +} + +export const updateAppState = (): void => { + + const profile = getProfile(); + + ipcEmit("set-profile", profile); + + // ipcEmit('messages') etc + +} + diff --git a/src/api/account.ts b/src/api/account.ts index de1335b..45baca2 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -1,23 +1,31 @@ -import { useHttp } from "@/composables/http"; +import useHttp from "@/composables/useHttp"; import axios from "axios"; +import {config} from "@/config"; const { post } = useHttp(); -export const submit = async (email: string, password: string) => ( - await post('/account/login', { email, password }) -) - -export const fetchAccount = async (email: string, token: string) => ( +export const postAuth = async (token: string) => ( await axios({ - url: "http://127.0.0.1:3000/api/account/profile", - headers: { - Cookie: `jwt=${token}` - }, - method: 'GET', - data: { - email, - } + url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate', + headers: { + Cookie: `crimataCookie=${token}` + }, + method: 'POST', }) -) +); + + +export const postLogin = async (email: string, password: string) => ( + await post('/account/login', { email, password }) +); + + +export const postLogout = + async (): Promise => (await post('/account/logout')); + + + + + diff --git a/src/audio.ts b/src/audio.ts index e69de29..7ae2562 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 const collect: IpcListenerCallback = (chunk) => { + if (chunk) buffer.push(chunk); +} + +// return audio and clear buffer +export const flush = async () => { + 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/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/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/json.ts b/src/composables/json.ts deleted file mode 100644 index 43b3804..0000000 --- a/src/composables/json.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const saveToJson = (fileName: string, data: any) => { - - fs.writeFile(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 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/emitter.ts b/src/composables/useEmitter.ts similarity index 59% rename from src/composables/emitter.ts rename to src/composables/useEmitter.ts index ee6bb5e..f95f328 100644 --- a/src/composables/emitter.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: T) => { backgroundMitt.emit('ipc-renderer', { - endpoint: channel, - message: payload + channel, + payload }); -} +}; + + diff --git a/src/composables/http.ts b/src/composables/useHttp.ts similarity index 87% rename from src/composables/http.ts rename to src/composables/useHttp.ts index a9519a8..6b9ee56 100644 --- a/src/composables/http.ts +++ b/src/composables/useHttp.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 = ''; @@ -25,7 +22,7 @@ const makeQuery = (reqQuery: Record) => { }; -export const useHttp = () => { +export default function useHttp() { const api = axios.create({ baseURL, diff --git a/src/composables/useIpcMain.ts b/src/composables/useIpcMain.ts new file mode 100644 index 0000000..ebfc4bc --- /dev/null +++ b/src/composables/useIpcMain.ts @@ -0,0 +1,82 @@ + +import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } 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() { + this.remove(); + ipcMain.handle(this.channel, this._onInvoke); + } + + remove() { + ipcMain.removeHandler(this.channel); + } + + private _onInvoke = (_e: IpcMainInvokeEvent, payload?: string | null): Promise => { + + return new Promise(async (resolve, reject) => { + + console.log(`[IPC] Handle:${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] Error:${this.channel}`); + reject(e); + } + }); + } + +} + + +export class IpcListener implements IIpcListener { + + readonly channel: string; + + readonly _listenerCallback: IpcListenerCallback; + + constructor(options: { + channel: string; + listenerCallback: IpcListenerCallback; + }) { + this.channel = options.channel; + this._listenerCallback = options.listenerCallback; + } + + listen() { + this.remove(); + ipcMain.on(this.channel, this._onPost); + } + + remove() { + ipcMain.removeAllListeners(this.channel); + } + + private _onPost = (_e: IpcMainEvent, payload?: string | null): void => { + + console.log(`[IPC] Post: ${this.channel}`); + + const params = payload ? JSON.parse(payload) : null; + this._listenerCallback(params); + } + +} diff --git a/src/composables/useMessageCanvas.ts b/src/composables/useMessageCanvas.ts new file mode 100644 index 0000000..c85f7ed --- /dev/null +++ b/src/composables/useMessageCanvas.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/useSaveToJSON.ts b/src/composables/useSaveToJSON.ts new file mode 100644 index 0000000..06ab4b9 --- /dev/null +++ b/src/composables/useSaveToJSON.ts @@ -0,0 +1,13 @@ + +import {config} from "@/config"; +import fs from 'fs'; + +export const saveToJson = (fileName: string, data: any) => { + + fs.writeFile(config.configPath + fileName, JSON.stringify(data), (err) => { + if (err) { + console.log("Error when saving to json.") + } + }) + +} diff --git a/src/composables/useWebsockets.ts b/src/composables/useWebsockets.ts new file mode 100644 index 0000000..d1cfc02 --- /dev/null +++ b/src/composables/useWebsockets.ts @@ -0,0 +1,89 @@ + +"use strict"; + +import WebSocket from 'ws'; + + +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) => { + if (socket) { + if (socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify(data)); + resolve(true); + } + } + reject(false); + }); + } + + const connect = (socketUrl: string, secret: string) => { + + // avoid setting multiple interval; + if (_connectionCheckInterval) clearInterval(_connectionCheckInterval); + + /* create a new socket */ + socket = new WebSocket(socketUrl); + + /* add event listeners */ + socket.on("open", () => { + + socket.send(JSON.stringify({key: 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) => { + console.log("message received", event); + messageCallback(event.toString()) + }); + + 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(); + } + } + + return { + connect, + send, + close + }; + +} diff --git a/src/composables/websockets.ts b/src/composables/websockets.ts deleted file mode 100644 index 779f00b..0000000 --- a/src/composables/websockets.ts +++ /dev/null @@ -1,73 +0,0 @@ - -"use strict"; - -import WebSocket from 'ws'; - -export default function useWebSockets(receiveCallback: (s: string) => void, openCallback?: () => void) { - - let socket: WebSocket | null = null; - - const send = async (data: Record): Promise => { - return new Promise((resolve, reject) => { - if (socket) { - if (socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify(data)); - resolve(true); - } - } - reject(false); - }); - } - - 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) => { - - socket = new WebSocket(socketUrl) - - // Add listeners. - socket.addEventListener("open", onOpen); - socket.addEventListener("message", onServerMessage); - socket.addEventListener("close", onClose); - socket.addEventListener("error", onError); - - } - - const close = () => { - if (socket) { - socket.close(); - socket = null; - } - } - - const checkConnection = () => { - return true; - } - - return { - createSocket, - send, - close, - checkConnection - }; - -} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..855b1b2 --- /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 || 3000; +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..9a0fdf6 100644 --- a/src/init.ts +++ b/src/init.ts @@ -8,8 +8,7 @@ import { app, protocol } from "electron"; import createWindow from "./window"; import main from "./main"; - -require('dotenv').config(); +import { backgroundMitt } from '@/composables/useEmitter'; console.log('Starting Crimata electron app.'); @@ -20,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(); @@ -43,4 +49,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 deleted file mode 100644 index 3860019..0000000 --- a/src/ipc/account.ts +++ /dev/null @@ -1,126 +0,0 @@ - -"use strict"; - -import { submit, fetchProfile, logout } from "../api/account"; -import { ipcMain, IpcMainInvokeEvent } from "electron"; -import { store } from "@/composables/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 - } -}; - - - - - -/** - * 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); - -} diff --git a/src/ipc/audio.ts b/src/ipc/audio.ts deleted file mode 100644 index 31c16ce..0000000 --- a/src/ipc/audio.ts +++ /dev/null @@ -1,34 +0,0 @@ - -"use strict"; - -import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron"; -import { collect, flush } from "@/composbales/audio"; - -// handle the new audio data -const onAudioChunk = ( - _e: IpcMainEvent, - payload: ArrayBuffer -) => { - console.log('[IPC]: audio-buffer'); - collect(payload); -} - -// Returns recorded audio to frontend and sets record to false. -const onGetAudio = async ( - _event: IpcMainInvokeEvent, - _payload: null -): Promise => { - console.log('[IPC]: stop-recording'); - return await flush() -}; - - -export default function useAudioListeners(): void { - - ipcMain.removeAllListeners("audio-chunk"); - ipcMain.on("audio-chunk", onAudioChunk); - - ipcMain.removeHandler("get-audio"); - ipcMain.handle("get-audio", onGetAudio); - -} diff --git a/src/ipc/handlers.ts b/src/ipc/handlers.ts new file mode 100644 index 0000000..5529662 --- /dev/null +++ b/src/ipc/handlers.ts @@ -0,0 +1,28 @@ + +"use strict"; + +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"; + +export const loginHandler = new IpcHandler({ + channel: LOGIN_CHANNEL, + handlerCallback: accountLogin +}); + +export const logoutHandler = new IpcHandler({ + channel: LOGOUT_CHANNEL, + handlerCallback: accountLogout +}); + +export const getAudioHandler = new IpcHandler({ + channel: GET_AUDIO_CHANNEL, + handlerCallback: flush +}); + + diff --git a/src/ipc/index.ts b/src/ipc/index.ts index 9152ef2..e6eb90a 100644 --- a/src/ipc/index.ts +++ b/src/ipc/index.ts @@ -1,17 +1,33 @@ "use strict"; -import useAccountListeners from "./account"; -import useSessionListeners from "./session"; -// import useAudioListeners from "./audio"; +import * as handlers from "./handlers"; +import * as listeners from "./listeners"; +const ipcHandlers: IPCHandlers = {}; +const ipcListeners: IPCListeners = {}; -export default function useIpc(): void { +const _initHandlers = (): void => { + for (const [key, handler] of Object.entries(handlers)) { + if (!(key in ipcHandlers)) { + ipcHandlers[key] = handler; + handler.handle(); + } + } +}; - useAccountListeners(); - - useSessionListeners(); - - // useAudioListeners(); +const _initListeners = (): void => { + for (const [key, listener] of Object.entries(listeners)) { + if (!(key in ipcListeners)) { + ipcListeners[key] = listener; + listener.listen(); + } + } +}; +export default function initIpcMain(): void { + _initHandlers(); + _initListeners(); } + + diff --git a/src/ipc/listeners.ts b/src/ipc/listeners.ts new file mode 100644 index 0000000..8ef5d45 --- /dev/null +++ b/src/ipc/listeners.ts @@ -0,0 +1,24 @@ + +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, + listenerCallback: sendMessage +}); + +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/ipc/session.ts b/src/ipc/session.ts deleted file mode 100644 index fa50aa1..0000000 --- a/src/ipc/session.ts +++ /dev/null @@ -1,20 +0,0 @@ - -"use strict"; - -import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron"; -import { sendMessage } from '@/session'; - -// Handle messages from window/client. -function onSendMessage(_event: IpcMainEvent, payload: Message): void { - sendMessage(payload); -} - -// Login attempt, returns success or not. -function onLogin(_event: IpcMainEvent, payload: LoginPayload) => { - authenticate(payload.email, payload.password); -} - -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 744643d..a360835 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,72 +1,39 @@ /** * 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 { fetchAccount, submit } from "@/api/account"; -import { launchSession, endSession } from "@/session"; -import useIpc from "@/ipc/index"; -import store from "@/composables/store"; +import initIpcMain from "@/ipc/index"; +import { accountAuth, updateAppState } from "./account"; +import { launchSession } from "./session"; +import createWindow from "./window"; -/* 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); - } - - /* launch if profile */ - if (auth) { - launchSession(auth); - } - -} - -/* logout the user, end the session */ -export function deauthenticate() { - - /* set profile back to null */ - auth = null; - - /* terminate the session */ - endSession(); - -} +let authState: AuthState | null; export default async function main() { - /* launch browser window */ - // await createWindow(); + /* initiate controls for frontend to use when needed */ + initIpcMain(); - /* attempt key-based authentication with business api */ - const token = store.get('key', null); - const crimataId = store.get('crimataId', null); + /* launch browser window */ + await createWindow(); try { - const res = await fetchAccount(crimataId, token); - auth = parseAuthRes(res); - } catch (e) { - console.log('[MAIN]', e); + authState = await accountAuth() as AuthState; + } catch(e) { + console.log('AUTH:', e); + authState = null; + } finally { + if (authState) { + launchSession(authState.token as string); + } + updateAppState(); } - /* connect to Crimata, or listen for manual login req */ - if (auth) { - launchSession(auth); - } - - /* initiate controls for frontend to use when needed */ - useIpc(); - -} \ No newline at end of file +} diff --git a/src/render/App.vue b/src/render/App.vue index e5c477d..686591b 100644 --- a/src/render/App.vue +++ b/src/render/App.vue @@ -1,14 +1,15 @@