diff --git a/.gitignore b/.gitignore index 20128fc..46762a2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ crash.log # local env files .env.local .env.*.local -.env # Log files npm-debug.log* 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..961035f 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", @@ -22,10 +22,8 @@ "@types/uuid": "^8.3.0", "@types/ws": "^7.2.7", "animejs": "^3.2.0", - "axios": "^0.21.1", "core-js": "^3.6.5", "electron-is-dev": "^2.0.0", - "electron-store": "^8.0.0", "electron-updater": "^4.3.8", "mitt": "^2.1.0", "naudiodon": "^2.3.2", @@ -38,7 +36,6 @@ "ws": "^7.3.1" }, "devDependencies": { - "@types/axios": "^0.14.0", "@types/electron-devtools-installer": "^2.2.0", "@types/jest": "^24.0.19", "@typescript-eslint/eslint-plugin": "^2.33.0", @@ -72,9 +69,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..320b62f --- /dev/null +++ b/src/App.vue @@ -0,0 +1,183 @@ + + + + + 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 deleted file mode 100644 index 45baca2..0000000 --- a/src/api/account.ts +++ /dev/null @@ -1,31 +0,0 @@ - -import useHttp from "@/composables/useHttp"; -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) => ( - 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..0a98206 --- /dev/null +++ b/src/background/audio.ts @@ -0,0 +1,188 @@ +/* eslint @typescript-eslint/no-var-requires: "off" */ + +"use strict"; + +import { ipcMain } from "electron"; +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, +} + +// Toggles record to true to begin capturing chunks. +const onRecordingStart = (_event: any, _payload: any) => { + console.log("AUDIO: Beginning audio capture.") + record = true; +} + +// Returns recorded audio to frontend and sets record to false. +const onRecordingEnd = async (_event: any, payload: any) => { + console.log("AUDIO:Sending audio to browser.") + + return new Promise((resolve, reject) => { + + try { + resolve(audioContainer.input); + record = false; + } catch (e) { + reject() + } + + }); +}; + + +// 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(); + + } + + // Listen to record. + console.log("AUDIO:Adding recording listeners.") + + ipcMain.removeAllListeners("start-recording"); + ipcMain.on("start-recording", onRecordingStart); + + ipcMain.removeHandler("stop-recording"); + ipcMain.handle("stop-recording", onRecordingEnd); +} + + +// ---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..9cc5df9 --- /dev/null +++ b/src/background/init.ts @@ -0,0 +1,95 @@ + +"use strict"; + +import { app, dialog, ipcMain } from "electron"; +import { createWindow } from './window'; +import { initSession } from './session'; +import { initAudioIO, stopStream } from './audio'; +import { backgroundMitt } from '@/modules/emitter'; + +let win: boolean; + +// Listen for window creation. +backgroundMitt.on('window-active', (state: boolean) => { + win = state; +}); + +// Auto updating. +const { autoUpdater } = require('electron-updater') +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() { + console.log("MAIN:Initializing Electron App.") + + ipcMain.removeHandler('version'); + ipcMain.handle('version', (_event: any, _payload: any) => { + return app.getVersion(); + }) + + // Must wait til window is created. + await createWindow(); + + // Instantiate socket session with crimata-platorm. + initSession(); + + // Begin audio stream. + initAudioIO(); + +} + +// 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/session.ts b/src/background/session.ts new file mode 100644 index 0000000..35773c8 --- /dev/null +++ b/src/background/session.ts @@ -0,0 +1,166 @@ +/* + * 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, saveToJson } from './helpers'; +import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron"; + +import useWebSockets from "@/modules/websockets"; + +import { play } from "./audio"; +import { renderMessage } from "@/modules/message"; +import { AuthProtocol, SessionState, Profile } from "@/types"; + +let win = true; + +// Info saved to json on quit (key, newMessages). +let state: SessionState; + +// Profile of current user. +let profile: Profile | boolean; + +// Called when server sends auth message. +const updateState = (res: AuthProtocol) => { + console.log("SESS:Auth message received: \n" + + ` key: ${res.key}\n` + + ` alias: ${res.profile}`) + + if (state) { + + // Update key. + state.key = res.key; + + // Update the user profile. + profile = res.profile; + + // Send upated profile to frontend. + console.log("SESS:Sending updated user profile to browser.") + ipcEmit("update-state", { + profile: profile, + newMessages: state.newMessages + }) + + // Save the updated state to json. + console.log("SESS:Saving session state.") + saveToJson("session.json", state) + + } + +} + +// Send state on new window. +const onNewBrowserWindow = (_event: IpcMainInvokeEvent, _payload: any) => { + if (typeof profile !== 'undefined') { + console.log("SESS:Sending user profile to browser.") + ipcEmit("update-state", { + profile: profile, + newMessages: state.newMessages + }) + } +} + +// Calls appropriate endpoint for a server message. +const onMessage = (data: string) => { + let message = JSON.parse(data) + + // AuthProtocol message. + if (message.hasOwnProperty("key")) { + updateState(message) + } + + // Standard message. + else 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) + } + } + +}; + +// When socket connects, we update state. +const onOpen = () => { + console.log(`SESS:Sending key: ${state.key}`) + + if (state) { + sendMessage({ + "key": state.key, + "usr": false, + "pwd": false + }) + } +} + +// Websockets module. +const { createSocket, sendMessage } = useWebSockets(onMessage, onOpen); + +// Handle messages from window/client. +const onClientMessage = (_event: IpcMainEvent, payload: any) => { + console.log("New client message") + + const success = sendMessage(payload) + + if (!success) { + console.log("Unable to send message: ", payload) + } + +} + +// Call this to initialize session with Crimata servers. +export const initSession = () => { + console.log("SESS:Creating new session.") + + // Load Json or createState. + state = loadState("session.json") + + console.log("SESS:State loaded: \n" + + ` key: ${state.key}\n` + + ` new: ${state.newMessages}`) + + // Open socket connection. + createSocket() + + // Attack browser window init listener. + ipcMain.removeAllListeners("app-mounted") + ipcMain.on("app-mounted", onNewBrowserWindow); + + // Attach listeners for frontend. + ipcMain.removeAllListeners("client-message") + ipcMain.on("client-message", onClientMessage); + + // Keep win up-to-date. + backgroundMitt.on('window-active', (state: boolean) => { + win = state; + }); + +} 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..e312572 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 + }); } } @@ -71,6 +57,7 @@ const saveWindowState = () => { // Do this on window mount. const onWindowMount = (): void => { + console.log("BW:Adding window listeners. ") // Must tell initApp that window exists. backgroundMitt.emit('window-active', true); @@ -81,18 +68,20 @@ const onWindowMount = (): void => { // Gateway for messages to the frontend. backgroundMitt.removeAllListeners("ipc-renderer") - backgroundMitt.on("ipc-renderer", postToWindow); + backgroundMitt.on("ipc-renderer", renderMessage); + console.log("BW:Listeners created.") } // Do this on window dismount (close). const onWindowDismount = (): void => { + console.log("BW:Window closed.") win = null; backgroundMitt.emit('window-active', false); } // 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. @@ -100,13 +89,14 @@ export default async function createWindow(): Promise { // Load the saved window state. winState = loadWinState("window.json") + console.log(`BW:Creating window [${winState.width}, ${winState.height}].`) // Define the browser window. win = new BrowserWindow({ 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..edc50a1 --- /dev/null +++ b/src/components/messenger.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/render/components/settings.vue b/src/components/settings.vue similarity index 84% rename from src/render/components/settings.vue rename to src/components/settings.vue index cf881c3..3144874 100644 --- a/src/render/components/settings.vue +++ b/src/components/settings.vue @@ -27,9 +27,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/useHttp.ts b/src/composables/useHttp.ts deleted file mode 100644 index 6b9ee56..0000000 --- a/src/composables/useHttp.ts +++ /dev/null @@ -1,51 +0,0 @@ - -import axios, { AxiosRequestConfig } from 'axios'; -import {config} from "@/config"; - -const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX; - -interface Request { - endpoint: string; - query?: Record; - config?: Record; -} - -const makeQuery = (reqQuery: Record) => { - - let result = ''; - - result = '?' + Object.entries(reqQuery) - .map(([ key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&') - - return result; -}; - - -export default function useHttp() { - - const api = axios.create({ - baseURL, - withCredentials: true, - }); - - - const post = async (endpoint: string, payload?: Record): Promise => ( - await api.post(endpoint, payload) - ) - - - const get = async (req: Request) => { - - if (req.query) { - req.endpoint += makeQuery(req.query); - } - - const res = await api.get(req.endpoint, req.config); - return res; - }; - - return { - get, post - } -} 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/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/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/modules/websockets.ts b/src/modules/websockets.ts new file mode 100644 index 0000000..9af0448 --- /dev/null +++ b/src/modules/websockets.ts @@ -0,0 +1,72 @@ +"use strict"; + +import WebSocket from 'ws'; +import { ipcMain } from "electron"; + +let socket: WebSocket; + + +// Run every time we want to connect to backend. +export default function useWebSockets(receiveCallback: (s: string) => any, openCallback: () => any) { + + // 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 onOpen = (event: WebSocket.OpenEvent) => { + + console.log("WS:Connected to WS Server!"); + + 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 + } + +} \ No newline at end of file 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..37b18ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,77 +1,74 @@ +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 { + key: boolean | string; + profile: boolean | Profile; } -interface AuthState { - profile: Profile | null; - token: string | null; +export interface LogoutRequest { + logout: boolean; } -interface AccountCredentials { - email: string; - password: string; -} - -/* - * Electron Ipc - */ -interface IpcHandlerCallback { - (payload: I | null): Promise; -} - -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; +export interface Annotation { + text: string; + context: string; + uid: string; } +export interface StandardMessage { + content: { + text: boolean | string; + audio: boolean | string; + }; + context: string; + modifier: string; +} \ No newline at end of file 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", diff --git a/yarn.lock b/yarn.lock index 465adc9..8dc3fba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1279,13 +1279,6 @@ resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== -"@types/axios@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" - integrity sha1-7CMA++fX3d1+udOr+HmZlkyvzkY= - dependencies: - axios "*" - "@types/babel__core@^7.1.0": version "7.1.12" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d" @@ -2355,13 +2348,6 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv-formats@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.0.tgz#96eaf83e38d32108b66d82a9cb0cfa24886cdfeb" - integrity sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q== - dependencies: - ajv "^8.0.0" - ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -2377,16 +2363,6 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.3, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.1.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b" - integrity sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -2734,11 +2710,6 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -atomically@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe" - integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== - autoprefixer@^9.8.6: version "9.8.6" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" @@ -2762,13 +2733,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@*, axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== - dependencies: - follow-redirects "^1.10.0" - babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -3982,22 +3946,6 @@ condense-newlines@^0.2.1: is-whitespace "^0.3.0" kind-of "^3.0.2" -conf@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/conf/-/conf-10.0.1.tgz#038093e5cbddc0e59bc14f63382c4ce732a4781d" - integrity sha512-QClEoNcruwBL84QgMEPHibL3ERxWIrRKhbjJKG1VsFBadm5QpS0jsu4QjY/maxUvhyAKXeyrs+ws+lC6PajnEg== - dependencies: - ajv "^8.1.0" - ajv-formats "^2.0.2" - atomically "^1.7.0" - debounce-fn "^4.0.0" - dot-prop "^6.0.1" - env-paths "^2.2.1" - json-schema-typed "^7.0.3" - onetime "^5.1.2" - pkg-up "^3.1.0" - semver "^7.3.5" - config-chain@^1.1.11, config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -4520,13 +4468,6 @@ deasync@^0.1.15: bindings "^1.5.0" node-addon-api "^1.7.1" -debounce-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7" - integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ== - dependencies: - mimic-fn "^3.0.0" - debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4886,23 +4827,11 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dot-prop@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" - integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== - dependencies: - is-obj "^2.0.0" - dotenv-expand@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== - dotenv@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" @@ -5056,14 +4985,6 @@ electron-publish@22.9.1: lazy-val "^1.0.4" mime "^2.4.6" -electron-store@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-8.0.0.tgz#81a4e687958e2dae1c5c84cc099a8148be776337" - integrity sha512-ZgRPUZkfrrjWSqxZeaxu7lEvmYf6tgl49dLMqxXGnEmliSiwv3u4rJPG+mH3fBQP9PBqgSh4TCuxHZImMMUgWg== - dependencies: - conf "^10.0.0" - type-fest "^1.0.2" - electron-to-chromium@^1.3.585: version "1.3.589" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.589.tgz#bd26183ed8697dde6ac19acbc16a3bf33b1f8220" @@ -5170,11 +5091,6 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== -env-paths@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -5955,11 +5871,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== -follow-redirects@^1.10.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" - integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== - for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -8132,16 +8043,6 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-schema-typed@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9" - integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A== - json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -8857,11 +8758,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" - integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== - mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -9926,13 +9822,6 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - please-upgrade-node@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -10876,11 +10765,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -11232,7 +11116,7 @@ semver@^7.2.1, semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@^7.3.4, semver@^7.3.5: +semver@^7.3.4: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -12504,11 +12388,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.0.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.1.3.tgz#ea1a602e98e5a968a56a289886a52f04c686fc81" - integrity sha512-CsiQeFMR1jZEq8R+H59qe+bBevnjoV5N2WZTTdlyqxeoODQOOepN2+msQOywcieDq5sBjabKzTn3U+sfHZlMdw== - type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"