diff --git a/build/config.gypi b/build/config.gypi new file mode 100644 index 0000000..6f84ed7 --- /dev/null +++ b/build/config.gypi @@ -0,0 +1,79 @@ +# 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 24d13b1..38c1348 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "postinstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps" }, - "main": "init.js", + "main": "background.js", "dependencies": { "@google-cloud/speech": "^4.2.0", "@types/animejs": "^3.1.2", @@ -24,6 +24,7 @@ "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", @@ -72,9 +73,7 @@ "lintOnSave": false, "pluginOptions": { "electronBuilder": { - "mainProcessFile": "./src/init.ts", - "rendererProcessFile": "./src/render/main.ts", - "preload": "./src/render/preload.ts", + "preload": "src/preload.ts", "builderOptions": { "appId": "com.crimata.ElectronUpdaterApp", "artifactName": "${productName}-${version}.${ext}", diff --git a/public/index.html b/public/index.html index 8f79d27..48809d8 100644 --- a/public/index.html +++ b/public/index.html @@ -11,7 +11,11 @@ -
+ +
diff --git a/src/account.ts b/src/account.ts deleted file mode 100644 index 581da0e..0000000 --- a/src/account.ts +++ /dev/null @@ -1,95 +0,0 @@ - -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 45baca2..de1335b 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -1,31 +1,23 @@ -import useHttp from "@/composables/useHttp"; +import { useHttp } from "@/composables/http"; import axios from "axios"; -import {config} from "@/config"; const { post } = useHttp(); -export const postAuth = async (token: string) => ( - await axios({ - url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate', - headers: { - Cookie: `crimataCookie=${token}` - }, - method: 'POST', - }) -); - - -export const postLogin = async (email: string, password: string) => ( +export const submit = async (email: string, password: string) => ( await post('/account/login', { email, password }) -); - - -export const postLogout = - async (): Promise => (await post('/account/logout')); - - - - +) +export const fetchAccount = async (email: string, token: string) => ( + await axios({ + url: "http://127.0.0.1:3000/api/account/profile", + headers: { + Cookie: `jwt=${token}` + }, + method: 'GET', + data: { + email, + } + }) +) diff --git a/src/audio.ts b/src/audio.ts index 7ae2562..e69de29 100644 --- a/src/audio.ts +++ b/src/audio.ts @@ -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 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 deleted file mode 100644 index 14ed36a..0000000 --- a/src/auth.ts +++ /dev/null @@ -1,13 +0,0 @@ - -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 new file mode 100644 index 0000000..844e791 --- /dev/null +++ b/src/composables/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/useEmitter.ts b/src/composables/emitter.ts similarity index 59% rename from src/composables/useEmitter.ts rename to src/composables/emitter.ts index f95f328..ee6bb5e 100644 --- a/src/composables/useEmitter.ts +++ b/src/composables/emitter.ts @@ -1,16 +1,15 @@ +/* eslint-disable */ // Backend emitter -// + const EventEmitter = require('events'); class BackgroundMitt extends EventEmitter { } export const backgroundMitt = new BackgroundMitt(); -export const ipcEmit = (channel: string, payload: T) => { +export default function ipcEmit (channel: string, payload: any) { backgroundMitt.emit('ipc-renderer', { - channel, - payload + endpoint: channel, + message: payload }); -}; - - +} diff --git a/src/composables/useHttp.ts b/src/composables/http.ts similarity index 87% rename from src/composables/useHttp.ts rename to src/composables/http.ts index 6b9ee56..a9519a8 100644 --- a/src/composables/useHttp.ts +++ b/src/composables/http.ts @@ -1,8 +1,10 @@ import axios, { AxiosRequestConfig } from 'axios'; -import {config} from "@/config"; -const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX; +const preFix = '/api'; + +const baseURL = "http://127.0.0.1:3000" + preFix; + interface Request { endpoint: string; @@ -10,6 +12,7 @@ interface Request { config?: Record; } + const makeQuery = (reqQuery: Record) => { let result = ''; @@ -22,7 +25,7 @@ const makeQuery = (reqQuery: Record) => { }; -export default function useHttp() { +export const useHttp = () => { const api = axios.create({ baseURL, diff --git a/src/composables/json.ts b/src/composables/json.ts new file mode 100644 index 0000000..43b3804 --- /dev/null +++ b/src/composables/json.ts @@ -0,0 +1,9 @@ +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 new file mode 100644 index 0000000..a74d209 --- /dev/null +++ b/src/composables/store.ts @@ -0,0 +1,51 @@ + +const Store = require('electron-store'); + + +const schema = { + // should be separate, used to authenticate against business and platform + key: { + 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 +} diff --git a/src/composables/useIpcMain.ts b/src/composables/useIpcMain.ts deleted file mode 100644 index ebfc4bc..0000000 --- a/src/composables/useIpcMain.ts +++ /dev/null @@ -1,82 +0,0 @@ - -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 deleted file mode 100644 index c85f7ed..0000000 --- a/src/composables/useMessageCanvas.ts +++ /dev/null @@ -1,36 +0,0 @@ - - - -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 deleted file mode 100644 index 06ab4b9..0000000 --- a/src/composables/useSaveToJSON.ts +++ /dev/null @@ -1,13 +0,0 @@ - -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 deleted file mode 100644 index d1cfc02..0000000 --- a/src/composables/useWebsockets.ts +++ /dev/null @@ -1,89 +0,0 @@ - -"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 new file mode 100644 index 0000000..779f00b --- /dev/null +++ b/src/composables/websockets.ts @@ -0,0 +1,73 @@ + +"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 deleted file mode 100644 index 855b1b2..0000000 --- a/src/config.ts +++ /dev/null @@ -1,17 +0,0 @@ - -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 9a0fdf6..3d0b721 100644 --- a/src/init.ts +++ b/src/init.ts @@ -8,7 +8,8 @@ import { app, protocol } from "electron"; import createWindow from "./window"; import main from "./main"; -import { backgroundMitt } from '@/composables/useEmitter'; + +require('dotenv').config(); console.log('Starting Crimata electron app.'); @@ -19,13 +20,6 @@ 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(); @@ -49,4 +43,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 new file mode 100644 index 0000000..3860019 --- /dev/null +++ b/src/ipc/account.ts @@ -0,0 +1,126 @@ + +"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 new file mode 100644 index 0000000..31c16ce --- /dev/null +++ b/src/ipc/audio.ts @@ -0,0 +1,34 @@ + +"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 deleted file mode 100644 index 5529662..0000000 --- a/src/ipc/handlers.ts +++ /dev/null @@ -1,28 +0,0 @@ - -"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 e6eb90a..9152ef2 100644 --- a/src/ipc/index.ts +++ b/src/ipc/index.ts @@ -1,33 +1,17 @@ "use strict"; -import * as handlers from "./handlers"; -import * as listeners from "./listeners"; +import useAccountListeners from "./account"; +import useSessionListeners from "./session"; +// import useAudioListeners from "./audio"; -const ipcHandlers: IPCHandlers = {}; -const ipcListeners: IPCListeners = {}; -const _initHandlers = (): void => { - for (const [key, handler] of Object.entries(handlers)) { - if (!(key in ipcHandlers)) { - ipcHandlers[key] = handler; - handler.handle(); - } - } -}; +export default function useIpc(): void { -const _initListeners = (): void => { - for (const [key, listener] of Object.entries(listeners)) { - if (!(key in ipcListeners)) { - ipcListeners[key] = listener; - listener.listen(); - } - } -}; + useAccountListeners(); + + useSessionListeners(); + + // useAudioListeners(); -export default function initIpcMain(): void { - _initHandlers(); - _initListeners(); } - - diff --git a/src/ipc/listeners.ts b/src/ipc/listeners.ts deleted file mode 100644 index 8ef5d45..0000000 --- a/src/ipc/listeners.ts +++ /dev/null @@ -1,24 +0,0 @@ - -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 new file mode 100644 index 0000000..fa50aa1 --- /dev/null +++ b/src/ipc/session.ts @@ -0,0 +1,20 @@ + +"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 a360835..744643d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,39 +1,72 @@ /** * 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 initIpcMain from "@/ipc/index"; -import { accountAuth, updateAppState } from "./account"; -import { launchSession } from "./session"; -import createWindow from "./window"; +import { fetchAccount, submit } from "@/api/account"; +import { launchSession, endSession } from "@/session"; +import useIpc from "@/ipc/index"; +import store from "@/composables/store"; -let authState: AuthState | null; +/* user profile, signals whether user is logged in */ +let auth: Profile | null = null; -export default async function main() { - - /* initiate controls for frontend to use when needed */ - initIpcMain(); - - /* launch browser window */ - await createWindow(); +/* authenticate the user */ +export async function authenticate(email: string, password: string) { + /* attempt normal login */ try { - authState = await accountAuth() as AuthState; - } catch(e) { - console.log('AUTH:', e); - authState = null; - } finally { - if (authState) { - launchSession(authState.token as string); - } - updateAppState(); + 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(); + +} + +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(); + +} \ No newline at end of file diff --git a/src/render/App.vue b/src/render/App.vue index 686591b..e5c477d 100644 --- a/src/render/App.vue +++ b/src/render/App.vue @@ -1,15 +1,14 @@