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..04ce2ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Crimata", - "version": "0.9.9", + "version": "0.9.8", "private": true, "description": "Cross-platform messenger application built with electron, vue3, and TS.", "author": { @@ -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/App.vue b/src/App.vue new file mode 100644 index 0000000..71eb7c3 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,209 @@ + + + + + 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..0fd1497 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -1,31 +1,36 @@ -import useHttp from "@/composables/useHttp"; +import { useHttp } from "@/modules/http"; import axios from "axios"; -import {config} from "@/config"; const { post } = useHttp(); -export const postAuth = async (token: string) => ( +export const submit = + async (email: string, password: string) => ( + + await post('/account/login', { + email, + password + }) + +) + + +export const logout = + async () : Promise => (await post('/account/logout')); + + + +export const fetchProfile = async(email: string, token: string) => ( + await axios({ - url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate', - headers: { - Cookie: `crimataCookie=${token}` - }, - method: 'POST', + url: "http://127.0.0.1:3000/api/account/profile", + headers: { + Cookie: `jwt=${token}` + }, + method: 'GET', + data: { + email, + } }) -); - - -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/assets/crimata.svg b/src/assets/crimata.svg new file mode 100644 index 0000000..8bb28dc --- /dev/null +++ b/src/assets/crimata.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/audio.ts b/src/audio.ts deleted file mode 100644 index 7ae2562..0000000 --- a/src/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 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/background.ts b/src/background.ts new file mode 100644 index 0000000..8fafb4f --- /dev/null +++ b/src/background.ts @@ -0,0 +1,25 @@ +/* + * Entry point for Crimata electron app. + * "Look on my Works, ye Mighty, and despair!" + */ + +"use strict"; + +import { initApp } from './background/init'; +import { protocol } from "electron"; + +// Scheme must be registered before the app is ready +protocol.registerSchemesAsPrivileged([ + { scheme: "app", privileges: { secure: true, standard: true } } +]); + +// Load environment variable +const isDev = require('electron-is-dev'); + +// NOTE Program Begins Here +(async () => { + + console.log('Starting Crimata electron app.'); + await initApp(isDev); + +})(); diff --git a/src/background/audio.ts b/src/background/audio.ts new file mode 100644 index 0000000..fa6efb2 --- /dev/null +++ b/src/background/audio.ts @@ -0,0 +1,172 @@ +/* eslint @typescript-eslint/no-var-requires: "off" */ + +"use strict"; + +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/background/helpers.ts b/src/background/helpers.ts new file mode 100644 index 0000000..f0074fb --- /dev/null +++ b/src/background/helpers.ts @@ -0,0 +1,62 @@ +import fs from 'fs'; +import { backgroundMitt } from "@/modules/emitter"; +import { SessionState, WindowState } from "@/types"; +import { app } from "electron"; + +const configPath = app.getPath("userData"); + +export const ipcEmit = (channel: string, payload: any) => { + backgroundMitt.emit('ipc-renderer', { + endpoint: channel, + message: payload + }); +} + +export const loadState = (fileName: string): SessionState => { + let state: SessionState; + + try { + state = JSON.parse(fs.readFileSync(configPath + fileName).toString()); + } + + catch (error) { + state = { + key: false, + newMessages: [] + } + } + + return state + +} + +export const loadWinState = (fileName: string): WindowState => { + let state: WindowState; + + try { + state = JSON.parse(fs.readFileSync(configPath + fileName).toString()); + } + + catch (error) { + state = { + width: 600, + height: 500, + x: null, + y: null, + } + } + + return state + +} + +// Save session or window state. +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/background/init.ts b/src/background/init.ts new file mode 100644 index 0000000..0936e81 --- /dev/null +++ b/src/background/init.ts @@ -0,0 +1,89 @@ + +"use strict"; + +import { app, dialog } from "electron"; +import { createWindow } from './window'; +import { stopStream } from './audio'; +import { backgroundMitt } from '@/modules/emitter'; +import useIpc from "@/background/ipc/index"; +const { autoUpdater } = require('electron-updater'); + +let win: boolean; + +// Listen for window creation. +backgroundMitt.on('window-active', (state: boolean) => { + win = state; +}); + +// Auto updating. +autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' } + +autoUpdater.on('update-available', (info: any) => { + console.log(`Update available: ${info.version}`) +}) + +autoUpdater.on('update-downloaded', (info: any) => { + + const updateDialog = { + type: 'info', + buttons: ['Restart', 'Later'], + title: 'Application Update', + message: info.version, + detail: 'A new version has been downloaded. Restart the application to apply the updates.' + } + + dialog.showMessageBox(updateDialog).then((returnValue) => { + if (returnValue.response === 0) autoUpdater.quitAndInstall() + }) + +}) + + + +// Run when electron app is initialized. +async function main(): Promise { + + console.log("MAIN:Initializing Electron App."); + + useIpc(); + + // Must wait til window is created. + await createWindow(); + +} + +// Root function of app. +export function initApp(dev: boolean): void { + + // On initial startup. + app.on("ready", () => { + // autoUpdater.checkForUpdates() + main() + }); + + // Must keep to ensure app doesn't quit on close. + app.on("before-quit", async () => { + await stopStream(); + }); + + // Must keep to ensure app doesn't quit on close. + app.on("window-all-closed", () => { + }); + + // When user clicks app icon (re-open) + app.on("activate", () => { + + if (!win) { + createWindow(); + } + + }); + + // Exit cleanly on request from parent process in development mode. + if (dev) { + process.on("SIGTERM", () => { + app.quit(); + }); + } +} + diff --git a/src/background/ipc/account.ts b/src/background/ipc/account.ts new file mode 100644 index 0000000..e6e9795 --- /dev/null +++ b/src/background/ipc/account.ts @@ -0,0 +1,108 @@ + +"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: string) => ( + + 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) => ( + + 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: string) => ( + + 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(null); + } 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/background/ipc/audio.ts b/src/background/ipc/audio.ts new file mode 100644 index 0000000..e6a63d0 --- /dev/null +++ b/src/background/ipc/audio.ts @@ -0,0 +1,39 @@ + +"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 => { + + 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); + +}; diff --git a/src/background/ipc/index.ts b/src/background/ipc/index.ts new file mode 100644 index 0000000..933bda1 --- /dev/null +++ b/src/background/ipc/index.ts @@ -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(); + +} diff --git a/src/background/ipc/session.ts b/src/background/ipc/session.ts new file mode 100644 index 0000000..3eaacbf --- /dev/null +++ b/src/background/ipc/session.ts @@ -0,0 +1,61 @@ + +"use strict"; + +import { initSession, emitNewMessages, sendMessage } from '@/background/session'; +import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron"; +import { initAudioIO } from "@/background/audio"; + + +// 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: any +): void => { + + console.log('[IPC]: app-mounted'); + + emitNewMessages() +}; + + +// Handle messages from window/client. +const onClientMessage = async ( + _event: IpcMainEvent, + payload: Record +): Promise => { + + 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); + +} diff --git a/src/background/session.ts b/src/background/session.ts new file mode 100644 index 0000000..fc3e4fd --- /dev/null +++ b/src/background/session.ts @@ -0,0 +1,104 @@ +/* + * 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 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; + + +// Calls appropriate endpoint for a server message. +const onMessage = (data: string): void => { + let message = JSON.parse(data); + console.log('received new message', message); + + // 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): void => { + try { + send(payload) + } catch(e) { + console.log("Unable to send message: ", payload); + } + +} + + +// Call this to initialize session with Crimata servers. +export const initSession = (cid: string): void => { + console.log("SESS:Creating new session.") + + // Load Json or createState. + state = loadState("session.json"); + + // Open socket connection. + createSocket(); + + // Keep win up-to-date. + backgroundMitt.on('window-active', (state: boolean) => { + win = state; + }); + +} diff --git a/src/background/store.ts b/src/background/store.ts new file mode 100644 index 0000000..16f8409 --- /dev/null +++ b/src/background/store.ts @@ -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" +}); diff --git a/src/background/websockets.ts b/src/background/websockets.ts new file mode 100644 index 0000000..dfbd9cb --- /dev/null +++ b/src/background/websockets.ts @@ -0,0 +1,87 @@ +"use strict"; + +import WebSocket from 'ws'; + +let socket: WebSocket; + + + +// 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): Promise => ( + 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 = () => { + socket = new WebSocket(`ws://127.0.0.1:8760`) + + // 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 + } + +} diff --git a/src/window.ts b/src/background/window.ts similarity index 77% rename from src/window.ts rename to src/background/window.ts index 21ebbca..162b3e1 100644 --- a/src/window.ts +++ b/src/background/window.ts @@ -1,37 +1,21 @@ "use strict"; -import { BrowserWindow, ipcMain, app } from "electron"; +import { BrowserWindow, ipcMain } from "electron"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; -import { backgroundMitt } from './composables/useEmitter'; -import { saveToJson } from "./composables/useSaveToJSON"; +import { backgroundMitt } from '@/modules/emitter'; +import { RenderMessage, WindowState } from "@/types"; +import { loadWinState, saveToJson } from "./helpers"; import * as path from "path"; -import fs from 'fs'; -import { config } from "@/config"; const { autoUpdater } = require('electron-updater'); +interface IpcRendererPayload { + endpoint: string; + message: RenderMessage | null; +} + let win: BrowserWindow | null; let winState: WindowState; -const loadWinState = (fileName: string): WindowState => { - let state: WindowState; - - try { - state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString()); - } - - catch (error) { - state = { - width: 600, - height: 500, - x: null, - y: null, - } - } - - return state - -} - // Called when a NavBar button is pressed. const onNavBar = (_event: any, action: string): void => { if (win) { @@ -44,9 +28,11 @@ const onNavBar = (_event: any, action: string): void => { } // Util function to render message on ipc-renderer event. -const postToWindow = (event: IpcRendererEvent): void => { +const renderMessage = (payload: IpcRendererPayload): void => { if (win) { - win.webContents.send(event.channel, event.payload); + win.webContents.send(payload.endpoint, { + message: payload.message + }); } } @@ -81,7 +67,7 @@ const onWindowMount = (): void => { // Gateway for messages to the frontend. backgroundMitt.removeAllListeners("ipc-renderer") - backgroundMitt.on("ipc-renderer", postToWindow); + backgroundMitt.on("ipc-renderer", renderMessage); } @@ -92,7 +78,7 @@ const onWindowDismount = (): void => { } // function used by run.ts to create the main window. -export default async function createWindow(): Promise { +export async function createWindow(): Promise { return new Promise((resolve, _reject) => { // avoid creating duplicate windows. @@ -105,8 +91,8 @@ export default async function createWindow(): Promise { win = new BrowserWindow({ width: winState.width, height: winState.height, - x: winState.x as number, - y: winState.y as number, + x: winState.x, + y: winState.y, resizable: true, backgroundColor: '#EBEBEB', frame: false, diff --git a/src/components/controllers/audioCtrl.ts b/src/components/controllers/audioCtrl.ts new file mode 100644 index 0000000..c9efaa9 --- /dev/null +++ b/src/components/controllers/audioCtrl.ts @@ -0,0 +1,111 @@ +import anime from "animejs"; +import useMitt from "@/modules/mitt"; +import { useIpc } from '@/modules/ipc'; +import { onMounted, onUnmounted, ref, Ref } from "vue"; +import keyboardNameMap from "../keyBoardMaps/keyboardNameMap"; +import { renderMessage, clientMessage } from '@/modules/message'; + + +function showRecIcon () { + + anime({ + targets: '#recIcon', + opacity: [0, 0.75], + scale: [0.0, 1], + duration: 250, + easing: 'linear', + }) + +} + +function hideRecIcon () { + + anime({ + targets: '#recIcon', + opacity: [0.75, 0], + scale: [1, 0], + duration: 250, + easing: 'linear', + }) + +} + + +export default function useAudioInputController (typing: Ref) { + + // For sending messages. + const { post, invoke } = useIpc(); + const { emitter } = useMitt(); + + // Keepp track of when we are recording. + const recording = ref(false); + + //---Callbacks----------------------------------------------- + + const onKeyDown = (e: KeyboardEvent) => { + const cmd = keyboardNameMap[e.keyCode]; + // console.log(cmd) + + // Start recording on space bar. + if (cmd == "SPACE" && !recording.value && !typing.value) { + + console.log("INPT:Starting record.") + post("start-recording", ""); + + showRecIcon() + recording.value = true; + + } + + } + + const onKeyUp = async (e: KeyboardEvent) => { + const cmd = keyboardNameMap[e.keyCode]; + + // Stop recording on space up. + if (cmd == "SPACE" && recording.value) { + + // Create a message. + const message = renderMessage( + "", + "", + "", + "sf" + ) + + // Render it immediately. + emitter.emit("self-message", message); + + // Stop recording and get audio from recorder. + console.log("INPT:Stopping record.") + const audio = await invoke("stop-recording", ""); + + // Send message to the backend for processing. + const clientM = clientMessage("", audio, message.uid); + post('client-message', clientM); + + hideRecIcon() + recording.value = false; + + } + + } + + //----------------------------------------------------------- + + onMounted(() => { + window.addEventListener("keydown", onKeyDown); + window.addEventListener("keyup", onKeyUp); + }) + + onUnmounted(() => { + window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("keyup", onKeyUp); + }); + + + return { + recording + } + +} diff --git a/src/render/components/controllers/inputItem.control.text.ts b/src/components/controllers/textCtrl.ts similarity index 52% rename from src/render/components/controllers/inputItem.control.text.ts rename to src/components/controllers/textCtrl.ts index 941ae89..7a2e2e8 100644 --- a/src/render/components/controllers/inputItem.control.text.ts +++ b/src/components/controllers/textCtrl.ts @@ -1,20 +1,70 @@ +import anime from "animejs"; +import useMitt from "@/modules/mitt"; +import { useIpc } from '@/modules/ipc'; import { Ref, ref, watch, onMounted, onUnmounted } from "vue"; -import { postMessage } from "@/render/ipc"; -import { newMessage, animateTextInput } from "./helpers"; +import keyboardNameMap from "../keyBoardMaps/keyboardNameMap"; +import { clientMessage, renderMessage } from '@/modules/message'; + +//---Animations----------------------------------------------- + +let side = "right"; // Side of parent we are on. + +function showTextInput () { + const t1 = (side === "right") ? 50 : -70; + const t2 = (side === "right") ? 110 : -130; + + anime({ + targets: '#textInput', + opacity: [0, 1], + translateX: [t1, t2], + scale: [0.3, 1], + duration: 500, + easing: 'easeOutExpo', + }) + +} + +function hideTextInput() { + const t = (side === "right") ? 50 : -80; + + anime({ + targets: '#textInput', + opacity: [1, 0], + translateX: t, + scale: 0.3, + duration: 500, + easing: 'easeOutExpo', + }) + +} + +function switchSide(currentSide: string) { + const t = (currentSide === "right") ? -130 : 110; + + anime({ + targets: '#textInput', + translateX: t, + duration: 500, + easing: 'easeOutExpo', + }) + +} + +//------------------------------------------------------------ export default function useTextInputController(elementX: Ref) { - let textInput: HTMLInputElement | null; - const { side, show, hide, switchSide } = animateTextInput(); + const { post } = useIpc(); + const { emitter } = useMitt(); let firstKey = true; const typing = ref(false); // Prep inputItem for typing. const prepInput = () => { - show() + showTextInput() typing.value = true } @@ -25,8 +75,8 @@ export default function useTextInputController(elementX: Ref) { textInput.value = ""; textInput.blur(); } - - hide() + + hideTextInput() firstKey = true; typing.value = false; } @@ -35,20 +85,28 @@ export default function useTextInputController(elementX: Ref) { const sendMessage = () => { if (textInput) { - // Send it to the backend for processing. - const message = newMessage({ - text: false - }); + // Create the message. + const message = renderMessage( + textInput.value, + false, + "", + "sf" + ) - // postMessage(message); + emitter.emit("self-message", message); + + // Send it to the backend for processing. + const clientM = clientMessage(textInput.value, false, message.uid); + post('client-message', clientM); clearInput() } } // Keys that are capable of opening the text input (numbers and letters). - const isHotKey = (key: number) => { - if (key >= 47 && key <= 91) { // a letter + const hotKeyRange = keyboardNameMap.slice(47, 91) + const isHotKey = (key: string) => { + if (hotKeyRange.includes(key)) { return true } } @@ -56,10 +114,10 @@ export default function useTextInputController(elementX: Ref) { //---Callbacks----------------------------------------------- const onKeyDown = (e: KeyboardEvent) => { - const key = e.keyCode; - + const key = keyboardNameMap[e.keyCode] + if (textInput) { - + // Only runs on firstKey. if (firstKey) { @@ -73,18 +131,18 @@ export default function useTextInputController(elementX: Ref) { textInput.focus(); // Close input when no text or on ESC. - if ((textInput.value == "") && (!firstKey) && (key === 8)) { // backspace + if ((textInput.value == "") && (!firstKey) && (key === "BACK_SPACE")) { clearInput() return } - if (key === 27) { // escape + if (key === "ESCAPE") { clearInput() return } // Close and send on enter. - if (key === 13) { + if (key === "ENTER") { if (textInput.value) { sendMessage() return @@ -102,20 +160,20 @@ export default function useTextInputController(elementX: Ref) { const winW = window.innerWidth // Logic depends on the side we are on. - if (side.value === "right") { + if (side === "right") { if (winW - elementX < 230) { - switchSide() - side.value = "left" + switchSide(side) + side = "left" } - } + } else { if (winW - elementX > 230) { - switchSide() - side.value = "right" + switchSide(side) + side = "right" } } - + }); onMounted(() => { diff --git a/src/render/components/inputItem.vue b/src/components/inputItem.vue similarity index 84% rename from src/render/components/inputItem.vue rename to src/components/inputItem.vue index edd844f..861d7c1 100644 --- a/src/render/components/inputItem.vue +++ b/src/components/inputItem.vue @@ -6,17 +6,14 @@ :style="{ top: `${elementY}px`, left: `${elementX}px` }" >
{{ initials }}
- + - + @@ -27,19 +24,24 @@ + + diff --git a/src/components/messenger.vue b/src/components/messenger.vue new file mode 100644 index 0000000..f5512a9 --- /dev/null +++ b/src/components/messenger.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/render/components/settings.vue b/src/components/settings.vue similarity index 90% rename from src/render/components/settings.vue rename to src/components/settings.vue index cf881c3..9632b97 100644 --- a/src/render/components/settings.vue +++ b/src/components/settings.vue @@ -28,8 +28,9 @@ + + \ No newline at end of file diff --git a/src/composables/autoUpdate.ts b/src/composables/autoUpdate.ts deleted file mode 100644 index 1a6b780..0000000 --- a/src/composables/autoUpdate.ts +++ /dev/null @@ -1,31 +0,0 @@ -const { autoUpdater } = require('electron-updater'); - -let win: boolean; - -// Listen for window creation. -backgroundMitt.on('window-active', (state: boolean) => { - win = state; -}); - -// Auto updating. -autoUpdater.requestHeaders = { 'PRIVATE-TOKEN': 'mvvgWYwWnot4bisiQMh_' } - -autoUpdater.on('update-available', (info: any) => { - console.log(`Update available: ${info.version}`) -}) - -autoUpdater.on('update-downloaded', (info: any) => { - - const updateDialog = { - type: 'info', - buttons: ['Restart', 'Later'], - title: 'Application Update', - message: info.version, - detail: 'A new version has been downloaded. Restart the application to apply the updates.' - } - - dialog.showMessageBox(updateDialog).then((returnValue) => { - if (returnValue.response === 0) autoUpdater.quitAndInstall() - }) - -}) \ No newline at end of file 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/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 deleted file mode 100644 index 9a0fdf6..0000000 --- a/src/init.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Entry point for Crimata electron app. - * "Look on my Works, ye Mighty, and despair!" - */ - -"use strict"; - -import { app, protocol } from "electron"; -import createWindow from "./window"; -import main from "./main"; -import { backgroundMitt } from '@/composables/useEmitter'; - -console.log('Starting Crimata electron app.'); - -// Scheme must be registered before the app is ready -protocol.registerSchemesAsPrivileged([ - { scheme: "app", privileges: { secure: true, standard: true } } -]); - -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(); -}); - -// Must keep to ensure app doesn't quit on close. -app.on("before-quit", async () => { -}); - -// Must keep to ensure app doesn't quit on close. -app.on("window-all-closed", () => { -}); - -// When user clicks app icon (re-open) -app.on("activate", () => { - if (!win) createWindow(); -}); - -// Exit cleanly on request from parent process in development mode. -if (isDev) { - process.on("SIGTERM", () => { - app.quit(); - }); -} 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 deleted file mode 100644 index e6eb90a..0000000 --- a/src/ipc/index.ts +++ /dev/null @@ -1,33 +0,0 @@ - -"use strict"; - -import * as handlers from "./handlers"; -import * as listeners from "./listeners"; - -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(); - } - } -}; - -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 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/main.ts b/src/main.ts index a360835..35774f7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,39 +1,15 @@ -/** - * 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. - * - */ +// src/main.ts -import initIpcMain from "@/ipc/index"; -import { accountAuth, updateAppState } from "./account"; -import { launchSession } from "./session"; -import createWindow from "./window"; +import App from "./App.vue"; -let authState: AuthState | null; +import mitt from "mitt"; +import { createApp } from "vue"; -export default async function main() { - /* initiate controls for frontend to use when needed */ - initIpcMain(); +// Handle events. +const emitter = mitt(); - /* launch browser window */ - await createWindow(); +const app = createApp(App) - try { - authState = await accountAuth() as AuthState; - } catch(e) { - console.log('AUTH:', e); - authState = null; - } finally { - if (authState) { - launchSession(authState.token as string); - } - updateAppState(); - } - -} +app.provide("mitt", emitter) +app.mount("#app"); diff --git a/src/modules/auth.ts b/src/modules/auth.ts new file mode 100644 index 0000000..5fb40c1 --- /dev/null +++ b/src/modules/auth.ts @@ -0,0 +1,23 @@ +import { ref } from "vue"; +import { Profile } from "@/types"; + + +const profile = ref(); + +export const useProfile = () => { + + const setProfile = (payload: Profile) => { + profile.value = payload; + } + + const clearProfile = () => { + profile.value = null; + } + + return { + setProfile, + clearProfile, + profile + } + +} diff --git a/src/render/composables/useDraggify.ts b/src/modules/draggify.ts similarity index 100% rename from src/render/composables/useDraggify.ts rename to src/modules/draggify.ts diff --git a/src/composables/useEmitter.ts b/src/modules/emitter.ts similarity index 51% rename from src/composables/useEmitter.ts rename to src/modules/emitter.ts index f95f328..bc91693 100644 --- a/src/composables/useEmitter.ts +++ b/src/modules/emitter.ts @@ -1,16 +1,13 @@ +/* eslint-disable */ + +import { Emitter } from "mitt"; // Backend emitter -// + +type Mitt = Emitter; + const EventEmitter = require('events'); + class BackgroundMitt extends EventEmitter { } export const backgroundMitt = new BackgroundMitt(); - -export const ipcEmit = (channel: string, payload: T) => { - backgroundMitt.emit('ipc-renderer', { - channel, - payload - }); -}; - - diff --git a/src/composables/useHttp.ts b/src/modules/http.ts similarity index 87% rename from src/composables/useHttp.ts rename to src/modules/http.ts index 6b9ee56..326b675 100644 --- a/src/composables/useHttp.ts +++ b/src/modules/http.ts @@ -1,8 +1,8 @@ import axios, { AxiosRequestConfig } from 'axios'; -import {config} from "@/config"; -const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX; +const baseURL = 'http://127.0.0.1:3000/api'; + interface Request { endpoint: string; @@ -10,6 +10,7 @@ interface Request { config?: Record; } + const makeQuery = (reqQuery: Record) => { let result = ''; @@ -22,7 +23,7 @@ const makeQuery = (reqQuery: Record) => { }; -export default function useHttp() { +export const useHttp = () => { const api = axios.create({ baseURL, diff --git a/src/modules/ipc.ts b/src/modules/ipc.ts new file mode 100644 index 0000000..36658de --- /dev/null +++ b/src/modules/ipc.ts @@ -0,0 +1,21 @@ + +export const useIpc = () => { + + const invoke = async (endpoint: string, payload: any) => { + try { + const res = await window.ipcRenderer.invoke(endpoint, payload); + return res; + } catch (e) { + throw e; + } + } + + const post = (endpoint: string, payload: any) => { + window.ipcRenderer.send(endpoint, payload); + }; + + return { + invoke, + post + } +} diff --git a/src/modules/message.ts b/src/modules/message.ts new file mode 100644 index 0000000..71beb70 --- /dev/null +++ b/src/modules/message.ts @@ -0,0 +1,62 @@ +import { + RenderMessage, + ClientMessage, + ClientRequest, + AuthRequest, + LogoutRequest +} from "@/types"; + +import { v4 as uuidv4 } from 'uuid'; + +function getTimeStamp(): number { + const currentdate = new Date(); + return currentdate.getTime(); +} + +// Create a RenderMessage object. +export const renderMessage = (text: boolean | string, audio: boolean | string, context: string, modifier: string): RenderMessage => ( + { + content: { + text: text, + audio: audio + }, + context: context, + modifier: modifier, + time: getTimeStamp(), + uid: uuidv4(), + isChild: "none", + seen: false, + newMessage: false + } +) + +export const clientMessage = (text: string, audio: string | boolean, uid: string): ClientMessage => ( + { + audio, + text, + uid + } +) + +export const clientRequest = (intent: string, params: object, epic: string | boolean): ClientRequest => ( + { + intent: intent, + params: params, + epic: epic, + confidence: 1.0 + } +) + +export const authRequest = (key: boolean | string, usr: boolean | string, pwd: boolean | string): AuthRequest => ( + { + key, + usr, + pwd + } +) + +export const logoutRequest = (): LogoutRequest => ( + { + logout: true + } +) \ No newline at end of file diff --git a/src/modules/messages.ts b/src/modules/messages.ts new file mode 100644 index 0000000..1d2c4b4 --- /dev/null +++ b/src/modules/messages.ts @@ -0,0 +1,125 @@ +import { ref } from 'vue'; +import useScroll from "@/modules/scroll"; +import { RenderMessage, Annotation } from "@/types"; + +const messages = ref(new Map()); + +const addMessage = (message: RenderMessage) => { + messages.value.set(message.uid, message) +} + +const updateMessage = (annotation: Annotation) => { + const message = messages.value.get(annotation.uid) + message.context = annotation.context + message.content.text = annotation.text +} + +const loadSavedMessages = () => { + const rawData = window.localStorage.getItem("crimata_messages"); + if (rawData) { + const messageData = JSON.parse(rawData) + messages.value = new Map(Object.entries(messageData)); + } +} + +const saveMessages = () => { + const messageData = Object.fromEntries(messages.value); + window.localStorage.setItem("crimata_messages", JSON.stringify(messageData)); +} + +const isSimmilar = (messageA: RenderMessage, messageB: RenderMessage) => { + if ((Math.abs(messageA.time - messageB.time) < 20000) && (messageA.modifier == messageB.modifier) && (messageA.context == messageB.context)) { + return true + } + return false +} + +const updateGrouping = () => { + console.log("updating grouping") + const refs = Array.from(messages.value.keys()) + + // Get the last three messages. + const first = messages.value.get(refs[refs.length - 1]) + const second = messages.value.get(refs[refs.length - 2]) + const third = messages.value.get(refs[refs.length - 3]) + + // If messages are simmilar, update the classes. + if ((first) && (second)) { + if (isSimmilar(first, second)) { + first.isChild = "last" // i.e. last in group. + second.isChild = "first" + + if (third) { + if ((third.isChild == "first") || (third.isChild == "middle")) { + second.isChild = "middle" + } + } + } + } +} + + +export default function useMessages() { + + // Scroll controller. + const { setScroll, updateScrollRef, adjustScroll } = useScroll("messenger"); + + // Main function for updating the message view. + const updateMessageView = (message: RenderMessage | Annotation) => { + + // Step 1: See if user is scrolled down. + updateScrollRef() + + // Step 2: Add the new content to the view. + if ("content" in message) { + addMessage(message) + } else { + updateMessage(message) + } + + // Step 3: Pop off oldest message (if > 200). + if (messages.value.size >= 200) { + const oldest = Array.from(messages.value.keys()).shift(); + messages.value.delete(oldest); + } + + // Step 4: Update grouping. + updateGrouping() + + // Setp 5: Scroll the view (if scrolled down). + setTimeout(adjustScroll, 20); + + // Step 6: Save the view data. + saveMessages() + + } + + // Seed message view with message history. + const prepMessageView = (newMessages: RenderMessage[]) => { + console.log("MSGR:Prepping messenger view.") + + // Load and render saved messages and immediately scroll to bottom. + loadSavedMessages() + setTimeout(setScroll.bind(false), 10); + + // Render new messages, then wait 1s to scroll. + if (newMessages.length) { + + console.log("MSGR:Adding new messages") + + newMessages.forEach(message => { + message.newMessage = true; + addMessage(message) + }) + + setTimeout(setScroll.bind(true), 1000); + + } + } + + return { + messages, + prepMessageView, + updateMessageView + } +} \ No newline at end of file diff --git a/src/modules/mitt.ts b/src/modules/mitt.ts new file mode 100644 index 0000000..4f3f448 --- /dev/null +++ b/src/modules/mitt.ts @@ -0,0 +1,19 @@ +import { inject } from "vue"; +import { Emitter } from "mitt"; + +// Frontend emitter + +type Mitt = Emitter; + +let emitter: Mitt; + +export default function useMitt() { + + const emitterInject: Mitt | undefined = inject("mitt"); + if (emitterInject) { + emitter = emitterInject; + } + return { + emitter + } +} \ No newline at end of file diff --git a/src/modules/scroll.ts b/src/modules/scroll.ts new file mode 100644 index 0000000..58370f7 --- /dev/null +++ b/src/modules/scroll.ts @@ -0,0 +1,50 @@ + + +export default function useScroll(element: string) { + + let isScrolledToBottom: boolean; + + // Set the initial scroll position. + const setScroll = (smooth: boolean) => { + const view = document.getElementById(element) + + if (view) { + view.scrollTo({ + top: view.scrollHeight - view.clientHeight, + behavior: (smooth) ? 'smooth' : 'auto' + }); + } + } + + // Update isScrolledToBottom + const updateScrollRef = () => { + const view = document.getElementById(element) + + if (view) { + isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1 + } + + } + + // Adjust scroll after we add content to the messenger. + const adjustScroll = () => { + const view = document.getElementById(element) + + if (view) { + if (isScrolledToBottom) { + view.scrollTo({ + top: view.scrollHeight - view.clientHeight, + behavior: 'smooth' + }); + } + } + + } + + return { + updateScrollRef, + adjustScroll, + setScroll, + } + +} diff --git a/src/render/preload.ts b/src/preload.ts similarity index 100% rename from src/render/preload.ts rename to src/preload.ts diff --git a/src/render/App.vue b/src/render/App.vue deleted file mode 100644 index 686591b..0000000 --- a/src/render/App.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - diff --git a/src/render/components/bubble.vue b/src/render/components/bubble.vue deleted file mode 100644 index 982cef3..0000000 --- a/src/render/components/bubble.vue +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - - diff --git a/src/render/components/controllers/bubble.control.ts b/src/render/components/controllers/bubble.control.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/render/components/controllers/helpers.ts b/src/render/components/controllers/helpers.ts deleted file mode 100644 index 23976a8..0000000 --- a/src/render/components/controllers/helpers.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { ref } from "vue"; -import anime from "animejs"; -import { v4 as uuidv4 } from 'uuid'; - - -export function animateTextInput () { - - const side = ref("right"); - - function show () { - const t1 = (side.value === "right") ? 50 : -70; - const t2 = (side.value === "right") ? 110 : -130; - - anime({ - targets: '#textInput', - opacity: [0, 1], - translateX: [t1, t2], - scale: [0.3, 1], - duration: 500, - easing: 'easeOutExpo', - }) - - } - - function hide () { - const t = (side.value === "right") ? 50 : -80; - - anime({ - targets: '#textInput', - opacity: [1, 0], - translateX: t, - scale: 0.3, - duration: 500, - easing: 'easeOutExpo', - }) - - } - - function switchSide () { - const t = (side.value === "right") ? -130 : 110; - - anime({ - targets: '#textInput', - translateX: t, - duration: 500, - easing: 'easeOutExpo', - }) - - } - - return { - side, - show, - hide, - switchSide - }; - -} - -export function animateAudioInput () { - - function show () { - anime({ - targets: '#recIcon', - opacity: [0, 0.75], - scale: [0.0, 1], - duration: 250, - easing: 'linear', - }) - } - - function hide () { - anime({ - targets: '#recIcon', - opacity: [0.75, 0], - scale: [1, 0], - duration: 250, - easing: 'linear', - }) - } - - return { - show, - hide - }; - -} - - -export function newMessage ({ - text=false, - audio=false, - context=false, - uid=uuidv4() -}) { - return { - text: text, - audio: audio, - context: context, - uid: uid - }; -} diff --git a/src/render/components/controllers/inputItem.control.audio.ts b/src/render/components/controllers/inputItem.control.audio.ts deleted file mode 100644 index facf30c..0000000 --- a/src/render/components/controllers/inputItem.control.audio.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { onMounted, onUnmounted, ref, Ref } from "vue"; -import { postMessage } from "@/render/ipc"; -import { newMessage, animateAudioInput } from "./helpers"; -import { invokeReturnAudio, postAudioChunk } from "@/render/ipc"; - -export default function useAudioInputController (typing: Ref) { - - const recording = ref(false); - let mediaRecorder: MediaRecorder; - - const { show, hide } = animateAudioInput(); - - // initialize audio - const conf = {audio: true, video: false} - navigator.mediaDevices.getUserMedia(conf).then((stream: MediaStream) => { - - const options = {mimeType: 'audio/webm'}; - mediaRecorder = new MediaRecorder(stream, options); - - // post any mew audio to backend - mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => { - e.data.arrayBuffer().then((buff: ArrayBuffer) => { - postAudioChunk(buff); - }); - }); - - // get audio and post new message to backend - mediaRecorder.addEventListener('stop', (_e: Event) => { - invokeReturnAudio().then((audio: ArrayBuffer[] | Error) => { - console.log(audio); - // postMessage(newMessage({audio: audio})); - }); - }); - - }); - - // start recording on space bar - const record = () => { - console.log("INPT:Capturing audio...") - mediaRecorder.start(); - recording.value = true; - show() - } - - // stop recording and send on release - const stop = () => { - console.log("INPT:Stopping record.") - mediaRecorder.stop(); - recording.value = false; - hide(); - } - - const onKeyDown = (e: KeyboardEvent) => { - if (e.keyCode == 32 && !typing.value) record(); - } - - const onKeyUp = (e: KeyboardEvent) => { - if (e.keyCode == 32 && recording.value) stop(); - } - - //----------------------------------------------------------- - - onMounted(() => { - window.addEventListener("keydown", onKeyDown); - window.addEventListener("keyup", onKeyUp); - }); - - onUnmounted(() => { - window.removeEventListener("keydown", onKeyDown); - window.removeEventListener("keyup", onKeyUp); - }) - - return { - recording - } - -} diff --git a/src/render/components/controllers/messenger.control.ts b/src/render/components/controllers/messenger.control.ts deleted file mode 100644 index b9c3a96..0000000 --- a/src/render/components/controllers/messenger.control.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ref } from 'vue'; -import useScroll from "@/render/composables/useScroll"; - -const messagesRef = ref(); - -/* seed the canvas with messages */ -const seedCanvas = (messages: Message[]) => { - messagesRef.value = messages; -} - -const addMessage = (message: Message) => { - messagesRef.value.push(message); -} - -const updateMessage = (message: Message) => { - - let target_message = messagesRef.value.filter((m: Message) => { - return m.uid = message.uid; - })[0]; - - if (target_message) { - target_message = message; - } - -} - -export default function useMessages() { - - const { updateScrollRef, adjustScroll } = useScroll("messenger"); - - return { - messagesRef, - seedCanvas, - addMessage, - updateMessage - }; - -} diff --git a/src/render/components/header.vue b/src/render/components/header.vue deleted file mode 100644 index 63716c8..0000000 --- a/src/render/components/header.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/src/render/components/messenger.vue b/src/render/components/messenger.vue deleted file mode 100644 index ee56549..0000000 --- a/src/render/components/messenger.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - - diff --git a/src/render/composables/useIpcRend.ts b/src/render/composables/useIpcRend.ts deleted file mode 100644 index 9ba7f73..0000000 --- a/src/render/composables/useIpcRend.ts +++ /dev/null @@ -1,54 +0,0 @@ - -import { IpcRendererEvent } from "electron"; - -export class IpcRendererListener 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(); - window.ipcRenderer.on(this.channel, this._onPost); - } - - remove() { - window.ipcRenderer.removeAllListeners(this.channel); - } - - private _onPost = (_e: IpcRendererEvent, payload: InputParam): void => { - console.log(`[IPC] Post: ${this.channel}`); - this._listenerCallback(payload); - } - -} - - -export default function useIpcRenderer () { - - const invoke = async (endpoint: string, payload: any) => { - try { - const res = await window.ipcRenderer.invoke(endpoint, payload); - return res; - } catch (e) { - throw e; - } - } - - const post = (endpoint: string, payload: any) => { - window.ipcRenderer.send(endpoint, payload); - }; - - return { - invoke, - post, - } -} diff --git a/src/render/composables/useMessages.ts b/src/render/composables/useMessages.ts deleted file mode 100644 index f731584..0000000 --- a/src/render/composables/useMessages.ts +++ /dev/null @@ -1,33 +0,0 @@ -// shared -import { ref, Ref } from "vue"; -import useScroll from "@/render/composables/useScroll"; - -export const messages: Ref> = ref([]); - -export const setMessages: IpcListenerCallback> = (payload) => { - messages.value = payload as Array; -} - -export const addMessage: IpcListenerCallback = (payload) => { - messages.value.push(payload as Message); -} - -export const updateMessage: IpcListenerCallback = (payload) => { - const message = payload as Message; - - let targetMessage = messages.value.filter((m: Message) => { - return m.uid = message.uid; - })[0]; - - if (targetMessage) { - targetMessage = message; - } - -} - -export default { - messages, - setMessages, - addMessage, - updateMessage -}; diff --git a/src/render/composables/useProfile.ts b/src/render/composables/useProfile.ts deleted file mode 100644 index 3575de7..0000000 --- a/src/render/composables/useProfile.ts +++ /dev/null @@ -1,27 +0,0 @@ -// shared -import { ref } from "vue"; - -export const profile = ref(); - -export const authComplete = ref(false); - -export const setProfile: IpcListenerCallback = (payload) => { - payload ? profile.value = payload : clearProfile(); - showRender(); -}; - -export const clearProfile = () => { - profile.value = null; -}; - -export const showRender = () => { - authComplete.value = true; -}; - -export default { - setProfile, - clearProfile, - profile, - showRender, - authComplete, -}; diff --git a/src/render/composables/useScroll.ts b/src/render/composables/useScroll.ts deleted file mode 100644 index 811e7fc..0000000 --- a/src/render/composables/useScroll.ts +++ /dev/null @@ -1,29 +0,0 @@ - - -export default function useScroll(element: string) { - - let isScrolledToBottom: boolean; - const view = document.getElementById(element) - - // Update isScrolledToBottom - const updateScrollRef = () => { - if (view) isScrolledToBottom = view.scrollHeight - view.clientHeight <= view.scrollTop + 1; - return isScrolledToBottom; - } - - // Adjust scroll after we add content to the messenger. - const adjustScroll = () => { - if (view) { - view.scrollTo({ - top: view.scrollHeight - view.clientHeight, - behavior: 'smooth' - }); - } - } - - return { - updateScrollRef, - adjustScroll, - }; - -} \ No newline at end of file diff --git a/src/render/ipc.ts b/src/render/ipc.ts deleted file mode 100644 index bc68049..0000000 --- a/src/render/ipc.ts +++ /dev/null @@ -1,80 +0,0 @@ - -import useIpc from "@/render/composables/useIpcRend"; -import * as rendererListeners from "./listeners"; - -const { post, invoke } = useIpc(); - -/** - * - * Account and auth related endpoints - * - */ - - -export const invokeLogin = async ( - payload: LoginPayload -): Promise => ( - await invoke('invoke-account-login', JSON.stringify(payload)) -); - -export const invokeLogout = async (): Promise => ( - await invoke("invoke-account-logout", null) -); - -/** - * - * Audio endpoints - * - */ - -export const postAudioChunk = (chunk: ArrayBuffer): void => ( - post("post-audio-collect", chunk) -); - -export const invokeReturnAudio = async (): Promise => ( - await invoke("invoke-audio-flush", null) -); - -/** - * - * Crimata Platform (session) endpoints - * - */ - -export const invokeSession = async (cid: string): Promise => ( - await invoke("messenger-init", cid) -); - -export const postMessage = (payload: Message): void => ( - post('post-session-send', payload) -); - -export const postAppMount = (): void => ( - post('post-app-mount', null) -); - - -/** - * - * Ipc Renderer Listeners - * - */ - -let ipcListeners: IPCListeners = {}; - -export const initIpcRendererListeners = () => { - for (const [key, listener] of Object.entries(rendererListeners)) { - if (!(key in ipcListeners)) { - ipcListeners[key] = listener; - listener.listen(); - } - } -}; - -export const removeListeners = () => { - for (const [key, listener] of Object.entries(rendererListeners)) { - listener.remove(); - } - ipcListeners = {}; -}; - diff --git a/src/render/listeners.ts b/src/render/listeners.ts deleted file mode 100644 index 876d206..0000000 --- a/src/render/listeners.ts +++ /dev/null @@ -1,32 +0,0 @@ - -import { IpcRendererListener } from "./composables/useIpcRend" -import { setProfile } from "./composables/useProfile"; -import { setMessages, addMessage, updateMessage } from "./composables/useMessages"; - -const SET_PROFILE_CHANNEL = "set-profile"; - -const INIT_MESSAGES_CHANNEL = "init-messages"; -const ADD_MESSAGE_CHANNEL = "add-message"; -const UPDATE_MESSAGE_CHANNEL = "update-message"; - -export const setProfileListener = new IpcRendererListener({ - channel: SET_PROFILE_CHANNEL, - listenerCallback: setProfile -}); - -export const initMessagesListener = new IpcRendererListener({ - channel: INIT_MESSAGES_CHANNEL, - listenerCallback: setMessages -}); - - -export const addMessagesListener = new IpcRendererListener({ - channel: ADD_MESSAGE_CHANNEL, - listenerCallback: addMessage -}); - -export const updateMessagesListener = new IpcRendererListener({ - channel: UPDATE_MESSAGE_CHANNEL, - listenerCallback: updateMessage -}); - diff --git a/src/render/main.ts b/src/render/main.ts deleted file mode 100644 index 5375889..0000000 --- a/src/render/main.ts +++ /dev/null @@ -1,21 +0,0 @@ - -// src/main.ts - -import App from "./App.vue"; - -import mitt from "mitt"; -import { createApp } from "vue"; - -import { initIpcRendererListeners } from "./ipc" - - -// Handle ipcMain events. -initIpcRendererListeners(); - -// Handle events. -const emitter = mitt(); - -const app = createApp(App); - -app.provide("mitt", emitter); -app.mount("#app"); diff --git a/src/session.ts b/src/session.ts deleted file mode 100644 index cde1df3..0000000 --- a/src/session.ts +++ /dev/null @@ -1,101 +0,0 @@ - - - - -// import useAudio from "@/audio"; -import { ipcEmit } from "@/composables/useEmitter"; -import useWebsockets from "./composables/useWebsockets"; -import {config} from "@/config"; - -/* data structure of messages that's tied to the UI */ -const uiState: any | null = null; - -/* start and stop audio functionality */ -// const { initAudio, closeAudio } = useAudio(); - -const isInitMessage = (message: any): boolean => { - return true; -}; - -const deauthenticate = (): void => { - console.log('deauthenticating') -}; - -const isAddMessage = (message: any): boolean => { - return true; -}; - -/** - * Controls for interfacing with the platform. - * Takes an onMessage callback which we define below. - */ - - -const onMessageCallback = (payload: string) => { - - const message = JSON.parse(payload); - console.log(typeof message); - - /* if the platform fails to authenticate, we must back down */ - if (message === "CLOSE_AUTH_FAIL") { - deauthenticate(); - return; - } - - ipcEmit("add-message", message) - return - - /* on init, platform sends state, used to init canvas */ - if (isInitMessage(message)) { - ipcEmit("init-messages", message) - } - - - else if (isAddMessage(message)) { - ipcEmit("add-messages", message) - } - - else { - ipcEmit("update-messages", message) - } - -} - -const onConnectionStatusCallback = (alive: boolean) => { - // console.log('[Session]: Connection Alive: ', alive); - // ipcEmit('connection-state', alive); -} - -const { connect, send, close } = useWebsockets( - onMessageCallback, - onConnectionStatusCallback -); - -/* send a message to the platform */ -export function sendMessage(message: Message): void { - - /* socket send */ - send(message); - -} - - -/* launch a new session (the main process for authenticated users) */ -export function launchSession(platformKey: string) { - - /* connect to the platform */ - connect(config.PLATFORM_URL, platformKey); - - /* initialize the audio streams */ - // initAudio(); - -} - - -export function endSession() { - - // closeAudio(); - - close(); - -} diff --git a/src/render/shims-vue.d.ts b/src/shims-vue.d.ts similarity index 100% rename from src/render/shims-vue.d.ts rename to src/shims-vue.d.ts diff --git a/src/store.ts b/src/store.ts deleted file mode 100644 index e532625..0000000 --- a/src/store.ts +++ /dev/null @@ -1,31 +0,0 @@ -const Store = require('electron-store'); - -const schema = { - token: { - type: 'string', - }, - profile: {} -}; - -const store = new Store({ - schema, - encryptionKey: "super user test" -}); - -export const getToken = (): string | undefined => (store.get("token")); - -export const clearToken = (): void => (store.delete("token")); - -export const setToken = (token: string): void => (store.set('token', token)); - -export const setProfile = (profile: Profile): Profile => (store.set('profile', profile)); - -export const getProfile = (): Profile => (store.get('profile')); - -export const clearProfile = (): void => (store.delete('profile')); - -export const clearStore = (): void => { - clearToken(); - clearProfile(); -} - diff --git a/src/types.ts b/src/types.ts index 450cb77..2e35fc5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,77 +1,76 @@ +export interface RenderMessage { + content: { + text: boolean | string; + audio: boolean | string; + }; + context: string; + modifier: string; + time: number; + uid: string; + isChild: string; + seen: boolean; + newMessage: boolean; +} -interface Message { - text: boolean | string; - context: boolean | string; +export interface ClientMessage { + text: string; audio: boolean | string; - type: 1 | 2 | 3; uid: string; } -interface ViewMessage extends Message { - child: string; +export interface ClientRequest { + intent: string; + params: object; + epic: string | boolean; + confidence: number; } -interface WindowState { +export interface SessionState { + key: string | boolean; + newMessages: RenderMessage[]; +} + +export interface WindowState { width: number; height: number; x: number | null; y: number | null; } -interface Profile { +export interface AuthRequest { + key: boolean | string; + usr: boolean | string; + pwd: boolean | string; +} + +export interface Profile { crimataId: string; alias: string; initials: string; } -interface LoginPayload { - email: string; - password: string; +export interface AuthProtocol { + token: null | string; + profile: null | Profile; + password?: string; + email?: string; } -interface AuthState { - profile: Profile | null; - token: string | null; +export interface LogoutRequest { + logout: boolean; } -interface AccountCredentials { - email: string; - password: string; +export interface Annotation { + text: string; + context: string; + uid: string; } -/* - * Electron Ipc - */ -interface IpcHandlerCallback { - (payload: I | null): Promise; +export interface StandardMessage { + content: { + text: boolean | string; + audio: boolean | string; + }; + context: string; + modifier: string; } - -interface IpcListenerCallback { - (payload: T | null): void; -} - -interface IIpcHandler { - handle(): void; - remove(): void; - readonly _handlerCallback: IpcHandlerCallback; -} - -interface IIpcListener { - listen(): void; - remove(): void; - readonly _listenerCallback: IpcListenerCallback; -} - -interface IPCHandlers { - [handler: string]: IIpcHandler; -} - -interface IPCListeners { - [listener: string]: IIpcListener | null; -} - -interface IpcRendererEvent { - channel: string; - payload: T | null; -} - diff --git a/tests/server.js b/tests/server.js index 9616e8a..6731be9 100644 --- a/tests/server.js +++ b/tests/server.js @@ -6,7 +6,6 @@ const wss = new WebSocket.Server({ let auth = false; wss.on("connection", function connection(ws, req) { - ws.on("message", function incoming(message) { console.log(message) @@ -20,13 +19,13 @@ wss.on("connection", function connection(ws, req) { auth = true; } else { const parsed = JSON.parse(message); - console.log('got a message', message); + console.log('got a message', message) } } else { const parsed = JSON.parse(message); - console.log('parsed', parsed); + console.log('parsed', parsed) } }); diff --git a/tsconfig.json b/tsconfig.json index 5a01f4d..307539b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,7 +32,6 @@ ] }, "include": [ - "**/*.ts", "src/*.ts", "src/**/*.ts", "src/**/*.tsx",