Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8203a90de5 |
52 changed files with 1054 additions and 1139 deletions
79
build/config.gypi
Normal file
79
build/config.gypi
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"postuninstall": "electron-builder install-app-deps"
|
"postuninstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
"main": "init.js",
|
"main": "background.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/speech": "^4.2.0",
|
"@google-cloud/speech": "^4.2.0",
|
||||||
"@types/animejs": "^3.1.2",
|
"@types/animejs": "^3.1.2",
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
"animejs": "^3.2.0",
|
"animejs": "^3.2.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
"electron-store": "^8.0.0",
|
"electron-store": "^8.0.0",
|
||||||
"electron-updater": "^4.3.8",
|
"electron-updater": "^4.3.8",
|
||||||
|
|
@ -72,9 +73,7 @@
|
||||||
"lintOnSave": false,
|
"lintOnSave": false,
|
||||||
"pluginOptions": {
|
"pluginOptions": {
|
||||||
"electronBuilder": {
|
"electronBuilder": {
|
||||||
"mainProcessFile": "./src/init.ts",
|
"preload": "src/preload.ts",
|
||||||
"rendererProcessFile": "./src/render/main.ts",
|
|
||||||
"preload": "./src/render/preload.ts",
|
|
||||||
"builderOptions": {
|
"builderOptions": {
|
||||||
"appId": "com.crimata.ElectronUpdaterApp",
|
"appId": "com.crimata.ElectronUpdaterApp",
|
||||||
"artifactName": "${productName}-${version}.${ext}",
|
"artifactName": "${productName}-${version}.${ext}",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<!-- <h1>Hello World!</h1>
|
||||||
|
We are using Node.js <span id="node-version"></span>, Chromium
|
||||||
|
<span id="chrome-version"></span>, and Electron
|
||||||
|
<span id="electron-version"></span>. -->
|
||||||
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -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<Error | AuthState> => {
|
|
||||||
|
|
||||||
/* 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<AccountCredentials, Profile> = 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<Error | void> => {
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,31 +1,23 @@
|
||||||
|
|
||||||
import useHttp from "@/composables/useHttp";
|
import { useHttp } from "@/composables/http";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {config} from "@/config";
|
|
||||||
|
|
||||||
const { post } = useHttp();
|
const { post } = useHttp();
|
||||||
|
|
||||||
export const postAuth = async (token: string) => (
|
export const submit = async (email: string, password: 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 })
|
await post('/account/login', { email, password })
|
||||||
);
|
)
|
||||||
|
|
||||||
|
|
||||||
export const postLogout =
|
|
||||||
async (): Promise<null | Error> => (await post('/account/logout'));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const fetchAccount = async (email: string, token: string) => (
|
||||||
|
await axios({
|
||||||
|
url: "http://127.0.0.1:3000/api/account/profile",
|
||||||
|
headers: {
|
||||||
|
Cookie: `jwt=${token}`
|
||||||
|
},
|
||||||
|
method: 'GET',
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
||||||
187
src/audio.ts
187
src/audio.ts
|
|
@ -1,187 +0,0 @@
|
||||||
/* eslint @typescript-eslint/no-var-requires: "off" */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// where the audio goes
|
|
||||||
let buffer: ArrayBuffer[] = [];
|
|
||||||
|
|
||||||
// place audio data in buffer
|
|
||||||
export const collect: IpcListenerCallback<ArrayBuffer> = (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<Error | string> => (
|
|
||||||
|
|
||||||
// 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<Buffer> {
|
|
||||||
// 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;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
13
src/auth.ts
13
src/auth.ts
|
|
@ -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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
187
src/composables/audio.ts
Normal file
187
src/composables/audio.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/* eslint @typescript-eslint/no-var-requires: "off" */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// where the audio goes
|
||||||
|
let buffer: ArrayBuffer[] = [];
|
||||||
|
|
||||||
|
// place audio data in buffer
|
||||||
|
export function collect (chunk: ArrayBuffer) {
|
||||||
|
buffer.push(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return audio and clear buffer
|
||||||
|
export function flush () {
|
||||||
|
const bufferCopy = buffer;
|
||||||
|
buffer = [];
|
||||||
|
return bufferCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// import { backgroundMitt } from '@/modules/emitter';
|
||||||
|
// const portAudio = require('naudiodon');
|
||||||
|
|
||||||
|
// // Audio in and out stream objects.
|
||||||
|
// let ai: typeof portAudio.AudioIO | boolean = false;
|
||||||
|
// let ao: typeof portAudio.AudioIO | boolean = false;
|
||||||
|
|
||||||
|
// // Whether activly recording.
|
||||||
|
// let record = false;
|
||||||
|
|
||||||
|
// const audioContainer = {
|
||||||
|
// input: '',
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const audioOptions = {
|
||||||
|
// channelCount: 1,
|
||||||
|
// sampleFormat: 16,
|
||||||
|
// sampleRate: 16000,
|
||||||
|
// deviceId: -1,
|
||||||
|
// closeOnError: false,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const toggleRecord = (): void => { record = !record };
|
||||||
|
|
||||||
|
|
||||||
|
// export const fetchAudioInput = (): Promise<Error | string> => (
|
||||||
|
|
||||||
|
// 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<Buffer> {
|
||||||
|
// 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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
// Backend emitter
|
// Backend emitter
|
||||||
//
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
class BackgroundMitt extends EventEmitter { }
|
class BackgroundMitt extends EventEmitter { }
|
||||||
|
|
||||||
export const backgroundMitt = new BackgroundMitt();
|
export const backgroundMitt = new BackgroundMitt();
|
||||||
|
|
||||||
export const ipcEmit = <T>(channel: string, payload: T) => {
|
export default function ipcEmit (channel: string, payload: any) {
|
||||||
backgroundMitt.emit('ipc-renderer', {
|
backgroundMitt.emit('ipc-renderer', {
|
||||||
channel,
|
endpoint: channel,
|
||||||
payload
|
message: payload
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import {config} from "@/config";
|
|
||||||
|
|
||||||
const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX;
|
const preFix = '/api';
|
||||||
|
|
||||||
|
const baseURL = "http://127.0.0.1:3000" + preFix;
|
||||||
|
|
||||||
|
|
||||||
interface Request {
|
interface Request {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
|
|
@ -10,6 +12,7 @@ interface Request {
|
||||||
config?: Record<string, any>;
|
config?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const makeQuery = (reqQuery: Record<string, any>) => {
|
const makeQuery = (reqQuery: Record<string, any>) => {
|
||||||
|
|
||||||
let result = '';
|
let result = '';
|
||||||
|
|
@ -22,7 +25,7 @@ const makeQuery = (reqQuery: Record<string, any>) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function useHttp() {
|
export const useHttp = () => {
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
9
src/composables/json.ts
Normal file
9
src/composables/json.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const saveToJson = (fileName: string, data: any) => {
|
||||||
|
|
||||||
|
fs.writeFile(configPath + fileName, JSON.stringify(data), (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log("Error when saving to json.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
51
src/composables/store.ts
Normal file
51
src/composables/store.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
const Store = require('electron-store');
|
||||||
|
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
// should be separate, used to authenticate against business and platform
|
||||||
|
key: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
|
||||||
|
profile: {
|
||||||
|
type:
|
||||||
|
},
|
||||||
|
|
||||||
|
messages: {
|
||||||
|
new: Message[],
|
||||||
|
saved: ViewMessages[]
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export const store = new Store({
|
||||||
|
schema,
|
||||||
|
encryptionKey: "super user test"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const emitInitialState = () => {
|
||||||
|
|
||||||
|
const profile = store.get(profile);
|
||||||
|
const messages = store.get(messages);
|
||||||
|
|
||||||
|
emit("initial-state", {
|
||||||
|
messages,
|
||||||
|
profile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* add a message to state.messages */
|
||||||
|
|
||||||
|
export function addMessage(message: Message) {
|
||||||
|
|
||||||
|
// update state
|
||||||
|
// TODO: add logic to handle new vs saved
|
||||||
|
state.messages.new.push(message);
|
||||||
|
state.messages.saved.push(message);
|
||||||
|
|
||||||
|
saveState();
|
||||||
|
// emit new message
|
||||||
|
}
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
|
|
||||||
import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
|
|
||||||
|
|
||||||
export class IpcHandler<InputParam, ReturnType> implements IIpcHandler<InputParam, ReturnType> {
|
|
||||||
|
|
||||||
readonly channel: string;
|
|
||||||
|
|
||||||
readonly _handlerCallback: IpcHandlerCallback<InputParam, ReturnType>;
|
|
||||||
|
|
||||||
constructor(options: {
|
|
||||||
channel: string;
|
|
||||||
handlerCallback: IpcHandlerCallback<InputParam, ReturnType>;
|
|
||||||
}) {
|
|
||||||
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<Error | ReturnType> => {
|
|
||||||
|
|
||||||
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<InputParam> implements IIpcListener<InputParam> {
|
|
||||||
|
|
||||||
readonly channel: string;
|
|
||||||
|
|
||||||
readonly _listenerCallback: IpcListenerCallback<InputParam>;
|
|
||||||
|
|
||||||
constructor(options: {
|
|
||||||
channel: string;
|
|
||||||
listenerCallback: IpcListenerCallback<InputParam>;
|
|
||||||
}) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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.")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import WebSocket from 'ws';
|
|
||||||
|
|
||||||
|
|
||||||
const _connectionCheckTimeout = 4000;
|
|
||||||
const _reconnectTimeout = 1000;
|
|
||||||
let _connectionCheckInterval: ReturnType<typeof setTimeout>;
|
|
||||||
|
|
||||||
|
|
||||||
export default function useWebSockets(
|
|
||||||
messageCallback: (message: string) => void,
|
|
||||||
connectionStatusCallback: (alive: boolean) => void,
|
|
||||||
) {
|
|
||||||
|
|
||||||
let socket: WebSocket;
|
|
||||||
|
|
||||||
const send = async (data: Record<string, any>): Promise<boolean> => {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
73
src/composables/websockets.ts
Normal file
73
src/composables/websockets.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
export default function useWebSockets(receiveCallback: (s: string) => void, openCallback?: () => void) {
|
||||||
|
|
||||||
|
let socket: WebSocket | null = null;
|
||||||
|
|
||||||
|
const send = async (data: Record<string, any>): Promise<boolean> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (socket) {
|
||||||
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.send(JSON.stringify(data));
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reject(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOpen = (_event: WebSocket.OpenEvent) => {
|
||||||
|
console.log("WS:Connected to WS Server!");
|
||||||
|
if (openCallback) openCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onServerMessage = (event: WebSocket.MessageEvent) => {
|
||||||
|
console.log("WS:Message received: ", event.data);
|
||||||
|
receiveCallback(event.data.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = (event: WebSocket.CloseEvent) => {
|
||||||
|
console.log("WS:Socket closed normally.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconnect automatically on error.
|
||||||
|
const onError = (event: WebSocket.ErrorEvent) => {
|
||||||
|
console.log("WS:WebSocket error: ", event.message);
|
||||||
|
console.log("Attempting reconnect in 1s.")
|
||||||
|
setTimeout(createSocket, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSocket = (socketUrl: string) => {
|
||||||
|
|
||||||
|
socket = new WebSocket(socketUrl)
|
||||||
|
|
||||||
|
// Add listeners.
|
||||||
|
socket.addEventListener("open", onOpen);
|
||||||
|
socket.addEventListener("message", onServerMessage);
|
||||||
|
socket.addEventListener("close", onClose);
|
||||||
|
socket.addEventListener("error", onError);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
if (socket) {
|
||||||
|
socket.close();
|
||||||
|
socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkConnection = () => {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createSocket,
|
||||||
|
send,
|
||||||
|
close,
|
||||||
|
checkConnection
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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')
|
|
||||||
}
|
|
||||||
12
src/init.ts
12
src/init.ts
|
|
@ -8,7 +8,8 @@
|
||||||
import { app, protocol } from "electron";
|
import { app, protocol } from "electron";
|
||||||
import createWindow from "./window";
|
import createWindow from "./window";
|
||||||
import main from "./main";
|
import main from "./main";
|
||||||
import { backgroundMitt } from '@/composables/useEmitter';
|
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
console.log('Starting Crimata electron app.');
|
console.log('Starting Crimata electron app.');
|
||||||
|
|
||||||
|
|
@ -19,13 +20,6 @@ protocol.registerSchemesAsPrivileged([
|
||||||
|
|
||||||
const isDev = require('electron-is-dev');
|
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 */
|
/* Start main process on ready */
|
||||||
app.on("ready", async () => {
|
app.on("ready", async () => {
|
||||||
await main();
|
await main();
|
||||||
|
|
@ -49,4 +43,4 @@ if (isDev) {
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
126
src/ipc/account.ts
Normal file
126
src/ipc/account.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import { submit, fetchProfile, logout } from "../api/account";
|
||||||
|
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
||||||
|
import { store } from "@/composables/store";
|
||||||
|
|
||||||
|
|
||||||
|
const parseAuthRes = (authRes: any) => {
|
||||||
|
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
|
||||||
|
const profile = authRes.data as Profile;
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
profile
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user profile from store and try to login with it.
|
||||||
|
*/
|
||||||
|
const onTokenLogin = async (
|
||||||
|
_event: IpcMainInvokeEvent,
|
||||||
|
_payload: null
|
||||||
|
): Promise<Profile | Error> => (
|
||||||
|
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
console.log('[IPC]: user-profile');
|
||||||
|
|
||||||
|
// get jwt token and crimataId from store
|
||||||
|
const token = store.get('key');
|
||||||
|
const crimataId = store.get('crimataId');
|
||||||
|
|
||||||
|
// authenticate and fetch profile
|
||||||
|
try {
|
||||||
|
|
||||||
|
// attempt login with email token
|
||||||
|
const res = await fetchProfile(crimataId, token);
|
||||||
|
const parsed = parseAuthRes(res);
|
||||||
|
|
||||||
|
// return profile to renderer
|
||||||
|
resolve(parsed.profile);
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
reject(new Error('Failed to fetch profile.'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const onLogin = async (
|
||||||
|
_event: IpcMainInvokeEvent,
|
||||||
|
payload: string
|
||||||
|
): Promise<Profile | Error> => (
|
||||||
|
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
console.log('[IPC]: user-login');
|
||||||
|
|
||||||
|
const account = JSON.parse(payload);
|
||||||
|
|
||||||
|
if ( account.password && account.email ) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// attempt login with email password
|
||||||
|
const res = await submit(account.email, account.password);
|
||||||
|
const parsed = parseAuthRes(res);
|
||||||
|
|
||||||
|
// save jwt token and profile
|
||||||
|
store.set('key', parsed.token);
|
||||||
|
store.set('crimataId', parsed.profile.crimataId);
|
||||||
|
|
||||||
|
// init session
|
||||||
|
|
||||||
|
// return profile to renderer
|
||||||
|
resolve(parsed.profile);
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
console.log('[API]', e);
|
||||||
|
reject(new Error('Failed to authenticate'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const onLogout = async (
|
||||||
|
_event: IpcMainInvokeEvent,
|
||||||
|
_payload: null
|
||||||
|
): Promise<void> => (
|
||||||
|
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
console.log('[IPC]: user-logout');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// post logout to backend
|
||||||
|
await logout();
|
||||||
|
|
||||||
|
// remove key and crimataId
|
||||||
|
store.delete('key');
|
||||||
|
store.delete('crimataId');
|
||||||
|
|
||||||
|
// TODO: kill crimata platform session
|
||||||
|
// endSession();
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} catch(e) {
|
||||||
|
reject(new Error('Failed to logout. Please try again.'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export default function useAccountListeners(): void {
|
||||||
|
|
||||||
|
ipcMain.removeHandler("user-profile");
|
||||||
|
ipcMain.handle("user-profile", onProfile);
|
||||||
|
|
||||||
|
ipcMain.removeHandler("user-login");
|
||||||
|
ipcMain.handle("user-login", onLogin);
|
||||||
|
|
||||||
|
ipcMain.removeHandler("user-logout");
|
||||||
|
ipcMain.handle("user-logout", onLogout);
|
||||||
|
|
||||||
|
}
|
||||||
34
src/ipc/audio.ts
Normal file
34
src/ipc/audio.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
|
||||||
|
import { collect, flush } from "@/composbales/audio";
|
||||||
|
|
||||||
|
// handle the new audio data
|
||||||
|
const onAudioChunk = (
|
||||||
|
_e: IpcMainEvent,
|
||||||
|
payload: ArrayBuffer
|
||||||
|
) => {
|
||||||
|
console.log('[IPC]: audio-buffer');
|
||||||
|
collect(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns recorded audio to frontend and sets record to false.
|
||||||
|
const onGetAudio = async (
|
||||||
|
_event: IpcMainInvokeEvent,
|
||||||
|
_payload: null
|
||||||
|
): Promise<Error | ArrayBuffer[]> => {
|
||||||
|
console.log('[IPC]: stop-recording');
|
||||||
|
return await flush()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default function useAudioListeners(): void {
|
||||||
|
|
||||||
|
ipcMain.removeAllListeners("audio-chunk");
|
||||||
|
ipcMain.on("audio-chunk", onAudioChunk);
|
||||||
|
|
||||||
|
ipcMain.removeHandler("get-audio");
|
||||||
|
ipcMain.handle("get-audio", onGetAudio);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,33 +1,17 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import * as handlers from "./handlers";
|
import useAccountListeners from "./account";
|
||||||
import * as listeners from "./listeners";
|
import useSessionListeners from "./session";
|
||||||
|
// import useAudioListeners from "./audio";
|
||||||
|
|
||||||
const ipcHandlers: IPCHandlers = {};
|
|
||||||
const ipcListeners: IPCListeners = {};
|
|
||||||
|
|
||||||
const _initHandlers = (): void => {
|
export default function useIpc(): void {
|
||||||
for (const [key, handler] of Object.entries(handlers)) {
|
|
||||||
if (!(key in ipcHandlers)) {
|
|
||||||
ipcHandlers[key] = handler;
|
|
||||||
handler.handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _initListeners = (): void => {
|
useAccountListeners();
|
||||||
for (const [key, listener] of Object.entries(listeners)) {
|
|
||||||
if (!(key in ipcListeners)) {
|
useSessionListeners();
|
||||||
ipcListeners[key] = listener;
|
|
||||||
listener.listen();
|
// useAudioListeners();
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function initIpcMain(): void {
|
|
||||||
_initHandlers();
|
|
||||||
_initListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<Message>({
|
|
||||||
channel: CLIENT_MESSAGE_CHANNEL,
|
|
||||||
listenerCallback: sendMessage
|
|
||||||
});
|
|
||||||
|
|
||||||
export const audioChunkListener = new IpcListener<ArrayBuffer>({
|
|
||||||
channel: GET_AUDIO_CHANNEL,
|
|
||||||
listenerCallback: collect
|
|
||||||
});
|
|
||||||
|
|
||||||
export const appMountListener = new IpcListener<null>({
|
|
||||||
channel: APP_MOUNT_CHANNEL,
|
|
||||||
listenerCallback: updateAppState
|
|
||||||
});
|
|
||||||
20
src/ipc/session.ts
Normal file
20
src/ipc/session.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent } from "electron";
|
||||||
|
import { sendMessage } from '@/session';
|
||||||
|
|
||||||
|
// Handle messages from window/client.
|
||||||
|
function onSendMessage(_event: IpcMainEvent, payload: Message): void {
|
||||||
|
sendMessage(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login attempt, returns success or not.
|
||||||
|
function onLogin(_event: IpcMainEvent, payload: LoginPayload) => {
|
||||||
|
authenticate(payload.email, payload.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useSessionListeners(): void {
|
||||||
|
ipcMain.removeAllListeners("client-message");
|
||||||
|
ipcMain.on("client-message", onSendMessage);
|
||||||
|
}
|
||||||
81
src/main.ts
81
src/main.ts
|
|
@ -1,39 +1,72 @@
|
||||||
/**
|
/**
|
||||||
* Where the background logic really begins, gets called by app.onReady().
|
* Where the background logic really begins, gets called by app.onReady().
|
||||||
*
|
*
|
||||||
* Handles authentication. If profile is set, we launch a session, which consis
|
* Handles authentication. If profile is set, we launch a session, which consis
|
||||||
* of opening a connection with the platform, initializing the audio streams.
|
* of opening a connection with the platform, initializing the audio streams.
|
||||||
*
|
*
|
||||||
* The session is primarily an interface between the frontend and the platform,
|
* The session is primarily an interface between the frontend and the platform,
|
||||||
* relaying messages from one to the other.
|
* relaying messages from one to the other.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import initIpcMain from "@/ipc/index";
|
import { fetchAccount, submit } from "@/api/account";
|
||||||
import { accountAuth, updateAppState } from "./account";
|
import { launchSession, endSession } from "@/session";
|
||||||
import { launchSession } from "./session";
|
import useIpc from "@/ipc/index";
|
||||||
import createWindow from "./window";
|
import store from "@/composables/store";
|
||||||
|
|
||||||
let authState: AuthState | null;
|
/* user profile, signals whether user is logged in */
|
||||||
|
let auth: Profile | null = null;
|
||||||
|
|
||||||
export default async function main() {
|
/* authenticate the user */
|
||||||
|
export async function authenticate(email: string, password: string) {
|
||||||
/* initiate controls for frontend to use when needed */
|
|
||||||
initIpcMain();
|
|
||||||
|
|
||||||
/* launch browser window */
|
|
||||||
await createWindow();
|
|
||||||
|
|
||||||
|
/* attempt normal login */
|
||||||
try {
|
try {
|
||||||
authState = await accountAuth() as AuthState;
|
auth = await submit(email, password);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.log('AUTH:', e);
|
console.log(e);
|
||||||
authState = null;
|
}
|
||||||
} finally {
|
|
||||||
if (authState) {
|
/* launch if profile */
|
||||||
launchSession(authState.token as string);
|
if (auth) {
|
||||||
}
|
launchSession(auth);
|
||||||
updateAppState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* logout the user, end the session */
|
||||||
|
export function deauthenticate() {
|
||||||
|
|
||||||
|
/* set profile back to null */
|
||||||
|
auth = null;
|
||||||
|
|
||||||
|
/* terminate the session */
|
||||||
|
endSession();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function main() {
|
||||||
|
|
||||||
|
/* launch browser window */
|
||||||
|
// await createWindow();
|
||||||
|
|
||||||
|
/* attempt key-based authentication with business api */
|
||||||
|
const token = store.get('key', null);
|
||||||
|
const crimataId = store.get('crimataId', null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetchAccount(crimataId, token);
|
||||||
|
auth = parseAuthRes(res);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[MAIN]', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* connect to Crimata, or listen for manual login req */
|
||||||
|
if (auth) {
|
||||||
|
launchSession(auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initiate controls for frontend to use when needed */
|
||||||
|
useIpc();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- Only render when profile has been set -->
|
<!-- Only render when profile has been set -->
|
||||||
<main id="app" v-if="authComplete">
|
<main id="app" v-if="typeof profile !== 'undefined'">
|
||||||
|
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
|
<!-- Main Components -->
|
||||||
<Messenger
|
<Messenger
|
||||||
v-if="profile"
|
v-if="profile"
|
||||||
|
:profile="profile"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Main Components
|
|
||||||
-->
|
|
||||||
<Login v-else />
|
<Login v-else />
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -21,17 +20,14 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, onMounted } from "vue";
|
import { defineComponent, onMounted, onUnmounted, Ref } from "vue";
|
||||||
import { IpcRendererEvent } from "electron";
|
import { invokeProfile } from "@/ipc/account";
|
||||||
|
|
||||||
import Splash from "@/render/components/splash.vue";
|
import Splash from "@/render/components/splash.vue";
|
||||||
import Messenger from "@/render/components/messenger.vue";
|
import Messenger from "@/render/components/messenger.vue";
|
||||||
import Login from "@/render/components/login.vue";
|
import Login from "@/render/components/login.vue";
|
||||||
import Header from "@/render/components/header.vue";
|
import Header from "@/render/components/header.vue";
|
||||||
|
|
||||||
import { profile, authComplete } from "@/render/composables/useProfile";
|
|
||||||
import { postAppMount } from "@/render/ipc";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -43,10 +39,23 @@ export default defineComponent({
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
|
||||||
onMounted(() => postAppMount());
|
const state: Ref;
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log("[APP]:mounted.");
|
||||||
|
|
||||||
|
/* listen for auth related messages */
|
||||||
|
window.addEventListener("update-state", (event: any) => {
|
||||||
|
state.value = event.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.ipcRenderer.removeAllListeners("update-state");
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authComplete,
|
|
||||||
profile
|
profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +68,7 @@ export default defineComponent({
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
// Background color set in window.ts
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<!-- message context -->
|
<!-- message context -->
|
||||||
<span class="context">
|
<span class="context">
|
||||||
<div :class="`${type}-icon`"/>
|
<div :class="`${type}-icon`">
|
||||||
{{ context }}
|
{{ context }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
@ -22,24 +22,23 @@
|
||||||
import { defineComponent, ref, onMounted } from 'vue';
|
import { defineComponent, ref, onMounted } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Bubble",
|
name: "MessageItem",
|
||||||
|
|
||||||
props: ["text", "context", "type", "position"],
|
props: ["text", "context", "type", "position"],
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
|
||||||
const seen = ref(false);
|
|
||||||
/* initialize seen state */
|
/* initialize seen state */
|
||||||
if (document.visibilityState === "visible") {
|
const seen = ref(() => {
|
||||||
seen.value = true;
|
if (document.visibilityState === "visible") {
|
||||||
} else seen.value = false;
|
return true;
|
||||||
|
} else return false;
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
if(seen.value)
|
if (!seen)
|
||||||
document.addEventListener("visibilitychange", () => {
|
document.addEventListener("visibilitychange", () => seen = true);
|
||||||
seen.value = true
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ export function newMessage ({
|
||||||
audio=false,
|
audio=false,
|
||||||
context=false,
|
context=false,
|
||||||
uid=uuidv4()
|
uid=uuidv4()
|
||||||
}) {
|
}): Message {
|
||||||
return {
|
return {
|
||||||
text: text,
|
text: text,
|
||||||
audio: audio,
|
audio: audio,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import anime from "animejs";
|
||||||
|
import { useIpc } from '@/modules/ipc';
|
||||||
import { onMounted, onUnmounted, ref, Ref } from "vue";
|
import { onMounted, onUnmounted, ref, Ref } from "vue";
|
||||||
import { postMessage } from "@/render/ipc";
|
import { postMessage } from "@/ipc/session";
|
||||||
import { newMessage, animateAudioInput } from "./helpers";
|
import { newMessage, animateAudioInput } from "./helpers";
|
||||||
import { invokeReturnAudio, postAudioChunk } from "@/render/ipc";
|
import { invokeStopRecord, postAudioChunk } from "@/ipc/audio";
|
||||||
|
|
||||||
export default function useAudioInputController (typing: Ref) {
|
export default function useAudioInputController (typing: Ref) {
|
||||||
|
|
||||||
|
|
@ -26,7 +28,7 @@ export default function useAudioInputController (typing: Ref) {
|
||||||
|
|
||||||
// get audio and post new message to backend
|
// get audio and post new message to backend
|
||||||
mediaRecorder.addEventListener('stop', (_e: Event) => {
|
mediaRecorder.addEventListener('stop', (_e: Event) => {
|
||||||
invokeReturnAudio().then((audio: ArrayBuffer[] | Error) => {
|
invokeStopRecord().then((audio: ArrayBuffer[] | Error) => {
|
||||||
console.log(audio);
|
console.log(audio);
|
||||||
// postMessage(newMessage({audio: audio}));
|
// postMessage(newMessage({audio: audio}));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
|
import { Ref, ref, watch, onMounted, onUnmounted } from "vue";
|
||||||
import { postMessage } from "@/render/ipc";
|
import { postMessage } from "@/ipc/session";
|
||||||
import { newMessage, animateTextInput } from "./helpers";
|
import { newMessage, animateTextInput } from "./helpers";
|
||||||
|
|
||||||
|
|
||||||
export default function useTextInputController(elementX: Ref) {
|
export default function useTextInputController(elementX: Ref) {
|
||||||
|
|
||||||
let textInput: HTMLInputElement | null;
|
let textInput: HTMLInputElement | null;
|
||||||
|
|
||||||
const { side, show, hide, switchSide } = animateTextInput();
|
const { side, show, hide, switchSide } = animateTextInput();
|
||||||
|
|
@ -37,10 +37,10 @@ export default function useTextInputController(elementX: Ref) {
|
||||||
|
|
||||||
// Send it to the backend for processing.
|
// Send it to the backend for processing.
|
||||||
const message = newMessage({
|
const message = newMessage({
|
||||||
text: false
|
text: textInput.value
|
||||||
});
|
});
|
||||||
|
|
||||||
// postMessage(message);
|
postMessage(message);
|
||||||
|
|
||||||
clearInput()
|
clearInput()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,143 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import useScroll from "@/render/composables/useScroll";
|
import invokeSavedMessages from "@/render/ipc";
|
||||||
|
import useScroll from "@/render/composables/scroll";
|
||||||
|
|
||||||
const messagesRef = ref();
|
const messages = ref(new Map());
|
||||||
|
|
||||||
/* seed the canvas with messages */
|
function getTimeStamp(): number {
|
||||||
const seedCanvas = (messages: Message[]) => {
|
const currentdate = new Date();
|
||||||
messagesRef.value = messages;
|
return currentdate.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMessage = (message: Message) => {
|
const newViewMessage = (message: Message): ViewMessage => {
|
||||||
messagesRef.value.push(message);
|
return {
|
||||||
|
text: message.text,
|
||||||
|
context: message.context,
|
||||||
|
audio: message.audio,
|
||||||
|
from: message.from,
|
||||||
|
uid: message.uid,
|
||||||
|
time: getTimeStamp(),
|
||||||
|
isChild: "none",
|
||||||
|
seen: false,
|
||||||
|
newMessage: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMessage = (message: Message, newMessage=false) => {
|
||||||
|
const viewMessage = newViewMessage(message);
|
||||||
|
if (newMessage) viewMessage.newMessage = true;
|
||||||
|
messages.value.set(viewMessage.uid, viewMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateMessage = (message: Message) => {
|
const updateMessage = (message: Message) => {
|
||||||
|
const viewMessage = messages.value.get(message.uid);
|
||||||
|
viewMessage.context = message.context;
|
||||||
|
viewMessage.text = message.text;
|
||||||
|
}
|
||||||
|
|
||||||
let target_message = messagesRef.value.filter((m: Message) => {
|
const loadSavedMessages = async () => {
|
||||||
return m.uid = message.uid;
|
const messageData = await invokeSavedMessages();
|
||||||
})[0];
|
messages.value = new Map(Object.entries(messageData));
|
||||||
|
}
|
||||||
|
|
||||||
if (target_message) {
|
const saveMessages = () => {
|
||||||
target_message = message;
|
const messageData = Object.fromEntries(messages.value);
|
||||||
|
// must save to json.
|
||||||
|
}
|
||||||
|
|
||||||
|
const pruneMessages = (limit=200) => {
|
||||||
|
if (messages.value.size >= limit) {
|
||||||
|
const oldest = Array.from(messages.value.keys()).shift();
|
||||||
|
messages.value.delete(oldest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateGrouping = () => {
|
||||||
|
|
||||||
|
const isSimmilar = (messageA: ViewMessage, messageB: ViewMessage) => {
|
||||||
|
if ((Math.abs(messageA.time - messageB.time) < 20000) && (messageA.from == messageB.from) && (messageA.context == messageB.context)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
export default function useMessages() {
|
||||||
|
|
||||||
const { updateScrollRef, adjustScroll } = useScroll("messenger");
|
const { updateScrollRef, adjustScroll } = useScroll("messenger");
|
||||||
|
|
||||||
|
/* Given new message object, update the view accordingly */
|
||||||
|
const updateMessageView = (newMessages: Message[]) => {
|
||||||
|
|
||||||
|
const bottom = updateScrollRef(); // see if the user is scrolled down
|
||||||
|
|
||||||
|
// take each message and apply view
|
||||||
|
newMessages.forEach((message: Message) => {
|
||||||
|
|
||||||
|
// add or update message depending
|
||||||
|
if (messages.value.has(message.uid)) {
|
||||||
|
updateMessage(message);
|
||||||
|
} else addMessage(message);
|
||||||
|
|
||||||
|
pruneMessages(); // pop off old messages from view
|
||||||
|
updateGrouping(); // group like message together
|
||||||
|
|
||||||
|
if (bottom) adjustScroll(); // only scroll if user was at bottom
|
||||||
|
|
||||||
|
saveMessages();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messagesRef,
|
messages,
|
||||||
seedCanvas,
|
updateMessageView,
|
||||||
addMessage,
|
loadSavedMessages
|
||||||
updateMessage
|
}
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // Seed message view with message history.
|
||||||
|
// const prepMessageView = async (newMessages: Message[]) => {
|
||||||
|
// console.log("MSGR:Prepping messenger view.")
|
||||||
|
|
||||||
|
// // Load and render saved messages and immediately scroll to bottom.
|
||||||
|
// await 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 => {
|
||||||
|
// addMessage(message, true);
|
||||||
|
// })
|
||||||
|
|
||||||
|
// setTimeout(setScroll.bind(true), 1000);
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -14,22 +14,14 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
// import { postNavBarExit, postNavBarMin } from "@/render/ipc";
|
import { postNavBarExit, postNavBarMin } from "@/render/ipc";
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
setup() {
|
||||||
export default defineComponent({
|
|
||||||
name: "Header",
|
|
||||||
setup() {
|
|
||||||
const postNavBarExit = () => {};
|
|
||||||
const postNavBarMin = () => {};
|
|
||||||
return {
|
return {
|
||||||
postNavBarExit,
|
postNavBarExit,
|
||||||
postNavBarMin
|
postNavBarMin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -86,4 +78,4 @@
|
||||||
.minimizeButton:active {
|
.minimizeButton:active {
|
||||||
background-color: #c08e38;
|
background-color: #c08e38;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -6,16 +6,16 @@
|
||||||
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
|
:style="{ top: `${elementY}px`, left: `${elementX}px` }"
|
||||||
>
|
>
|
||||||
<div>{{ initials }}</div>
|
<div>{{ initials }}</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Recording animation on space bar -->
|
<!-- Recording animation on space bar -->
|
||||||
<span v-if="recording" class="play"></span>
|
<span v-if="recording" class="play"></span>
|
||||||
<span v-if="recording" class="pause"></span>
|
<span v-if="recording" class="pause"></span>
|
||||||
|
|
||||||
<!-- Show text input on key-down -->
|
<!-- Show text input on key-down -->
|
||||||
<input
|
<input
|
||||||
id="textInput"
|
id="textInput"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Show suggestions menu on click -->
|
<!-- Show suggestions menu on click -->
|
||||||
|
|
@ -27,19 +27,24 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import draggify from "@/render/composables/useDraggify";
|
import draggify from "@/modules/draggify";
|
||||||
|
|
||||||
|
import TextInput from "@/components/textInput.vue";
|
||||||
|
|
||||||
import useTextInputController from
|
import useTextInputController from
|
||||||
"@/render/components/controllers/inputItem.control.text";
|
"@/components/controllers/inputItem.control.audio";
|
||||||
import useAudioInputController from
|
import useAudioInputController from
|
||||||
"@/render/components/controllers/inputItem.control.audio";
|
"@/components/controllers/inputItem.control.text";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "InputItem",
|
name: "InputItem",
|
||||||
|
|
||||||
props: ["initials"],
|
props: ["initials"],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
TextInput
|
||||||
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
|
||||||
// Default values for position.
|
// Default values for position.
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, ref } from "vue";
|
import { defineComponent, ref } from "vue";
|
||||||
import { setProfile } from '@/render/composables/useProfile';
|
import { useIpc } from "@/modules/ipc";
|
||||||
import { invokeLogin } from "@/render/ipc";
|
import { authRequest } from '@/modules/message';
|
||||||
|
import { useProfile } from '@/modules/auth';
|
||||||
|
import { invokeLogin } from "@/ipcRend/account";
|
||||||
|
import { postInitSession } from "@/ipcRend/session";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Login",
|
name: "Login",
|
||||||
|
|
@ -54,14 +57,12 @@ export default defineComponent({
|
||||||
const profile = await invokeLogin({
|
const profile = await invokeLogin({
|
||||||
email: usr.value,
|
email: usr.value,
|
||||||
password: pwd.value
|
password: pwd.value
|
||||||
}) as Profile;
|
});
|
||||||
|
|
||||||
setProfile(profile)
|
/* emit event to app.vue */
|
||||||
|
window.postMessage(profile);
|
||||||
|
|
||||||
|
} catch (e) console.log(e);
|
||||||
} catch(e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<div id="messenger">
|
<div id="messenger">
|
||||||
<Bubble
|
<Bubble
|
||||||
v-for="message in messages"
|
v-for="message in messages"
|
||||||
:text="message.content.text"
|
:text="message.text"
|
||||||
:context="message.context"
|
:context="message.context"
|
||||||
:key="message[0]"
|
:key="message[0]"
|
||||||
/>
|
/>
|
||||||
|
|
@ -20,26 +20,45 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, onMounted, onUnmounted } from "vue";
|
import { defineComponent, onMounted, onUnmounted } from "vue";
|
||||||
|
import Message from "@/render/components/message.vue";
|
||||||
import InputItem from "@/render/components/inputItem.vue";
|
import InputItem from "@/render/components/inputItem.vue";
|
||||||
import Settings from "@/render/components/settings.vue";
|
import Settings from "@/render/components/settings.vue";
|
||||||
import Bubble from "@/render/components/bubble.vue";
|
import useMessages from "@/render/composables/messages";
|
||||||
|
|
||||||
import { profile } from "@/render/composables/useProfile";
|
|
||||||
import { messages } from "@/render/composables/useMessages";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Messenger",
|
name: "Messenger",
|
||||||
|
|
||||||
|
props: ["state"],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
Message,
|
||||||
InputItem,
|
InputItem,
|
||||||
Settings,
|
Settings
|
||||||
Bubble
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup(props) {
|
||||||
|
|
||||||
|
// Handle messages in view.
|
||||||
|
const { messages, updateMessageView } = useMessages();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
/* populate the message view with existing messages */
|
||||||
|
updateMessageView(state.savedMessages, state.newMessages);
|
||||||
|
|
||||||
|
/* wait and listen for new messages to come in */
|
||||||
|
window.ipcRenderer.on("new-message", (_e: any, payload: any) => {
|
||||||
|
updateMessageView(payload.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.ipcRenderer.removeAllListeners("new-message");
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages
|
||||||
profile
|
|
||||||
};
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, ref } from "vue";
|
import { defineComponent, ref } from "vue";
|
||||||
import { clearProfile } from "@/render/composables/useProfile"
|
import { useIpc } from "@/modules/ipc";
|
||||||
import { invokeLogout } from "@/render/ipc";
|
import { logoutRequest } from '@/modules/message';
|
||||||
|
import { useProfile } from "@/modules/auth"
|
||||||
|
import { invokeLogout } from "@/ipcRend/account";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
|
|
@ -37,6 +39,10 @@
|
||||||
setup() {
|
setup() {
|
||||||
const toggleSettings = ref(false);
|
const toggleSettings = ref(false);
|
||||||
|
|
||||||
|
const { post, invoke } = useIpc();
|
||||||
|
|
||||||
|
const { clearProfile } = useProfile();
|
||||||
|
|
||||||
// Listen for escape key to close settings.
|
// Listen for escape key to close settings.
|
||||||
const onEscape = (e: any) => {
|
const onEscape = (e: any) => {
|
||||||
if(e.key === "Escape") {
|
if(e.key === "Escape") {
|
||||||
|
|
@ -57,8 +63,8 @@
|
||||||
console.log("Submitting logout request.");
|
console.log("Submitting logout request.");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invokeLogout();
|
|
||||||
clearProfile();
|
clearProfile();
|
||||||
|
await invokeLogout();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log('error')
|
console.log('error')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/render/composables/auth.ts
Normal file
21
src/render/composables/auth.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const profile = ref();
|
||||||
|
|
||||||
|
export const useProfile = () => {
|
||||||
|
|
||||||
|
const setProfile = (payload: Profile) => {
|
||||||
|
profile.value = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearProfile = () => {
|
||||||
|
profile.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setProfile,
|
||||||
|
clearProfile,
|
||||||
|
profile
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/render/composables/ipc.ts
Normal file
21
src/render/composables/ipc.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
export default function 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
|
|
||||||
import { IpcRendererEvent } from "electron";
|
|
||||||
|
|
||||||
export class IpcRendererListener<InputParam> implements IIpcListener<InputParam> {
|
|
||||||
|
|
||||||
readonly channel: string;
|
|
||||||
|
|
||||||
readonly _listenerCallback: IpcListenerCallback<InputParam>;
|
|
||||||
|
|
||||||
constructor(options: {
|
|
||||||
channel: string;
|
|
||||||
listenerCallback: IpcListenerCallback<InputParam>;
|
|
||||||
}) {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// shared
|
|
||||||
import { ref, Ref } from "vue";
|
|
||||||
import useScroll from "@/render/composables/useScroll";
|
|
||||||
|
|
||||||
export const messages: Ref<Array<Message>> = ref([]);
|
|
||||||
|
|
||||||
export const setMessages: IpcListenerCallback<Array<Message>> = (payload) => {
|
|
||||||
messages.value = payload as Array<Message>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addMessage: IpcListenerCallback<Message> = (payload) => {
|
|
||||||
messages.value.push(payload as Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateMessage: IpcListenerCallback<Message> = (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
|
|
||||||
};
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// shared
|
|
||||||
import { ref } from "vue";
|
|
||||||
|
|
||||||
export const profile = ref();
|
|
||||||
|
|
||||||
export const authComplete = ref(false);
|
|
||||||
|
|
||||||
export const setProfile: IpcListenerCallback<Profile | null> = (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,
|
|
||||||
};
|
|
||||||
|
|
@ -1,44 +1,46 @@
|
||||||
|
|
||||||
import useIpc from "@/render/composables/useIpcRend";
|
import useIpc from "@/render/composables/ipc";
|
||||||
import * as rendererListeners from "./listeners";
|
|
||||||
|
|
||||||
const { post, invoke } = useIpc();
|
const { post, invoke } = useIpc();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Account and auth related endpoints
|
* Account and auth related endpoints
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export const invokeProfile = async (): Promise<Profile | Error> => (
|
||||||
|
await invoke('user-profile', null)
|
||||||
|
);
|
||||||
|
|
||||||
export const invokeLogin = async (
|
export const invokeLogin = async (
|
||||||
payload: LoginPayload
|
payload: LoginPayload
|
||||||
): Promise<Profile | Error> => (
|
): Promise<Profile | Error> => (
|
||||||
await invoke('invoke-account-login', JSON.stringify(payload))
|
await invoke('user-login', JSON.stringify(payload))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const invokeLogout = async (): Promise<void> => (
|
export const invokeLogout = async (): Promise<void> => (
|
||||||
await invoke("invoke-account-logout", null)
|
await invoke("user-logout", null)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Audio endpoints
|
* Audio endpoints
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const postAudioChunk = (chunk: ArrayBuffer): void => (
|
export const postAudioChunk = (chunk: ArrayBuffer): void => (
|
||||||
post("post-audio-collect", chunk)
|
post("audio-chunk", chunk)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
|
export const invokeReturnAudio = async (): Promise<ArrayBuffer[] | Error> => (
|
||||||
await invoke("invoke-audio-flush", null)
|
await invoke("get-audio", null)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Crimata Platform (session) endpoints
|
* Crimata Platform (session) endpoints
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const invokeSession = async (cid: string): Promise<Profile | Error> => (
|
export const invokeSession = async (cid: string): Promise<Profile | Error> => (
|
||||||
|
|
@ -46,35 +48,5 @@ export const invokeSession = async (cid: string): Promise<Profile | Error> => (
|
||||||
);
|
);
|
||||||
|
|
||||||
export const postMessage = (payload: Message): void => (
|
export const postMessage = (payload: Message): void => (
|
||||||
post('post-session-send', payload)
|
post('client-message', 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 = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -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");
|
|
||||||
|
|
@ -2,100 +2,63 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// import useAudio from "@/audio";
|
import useAudio from "@/audio";
|
||||||
import { ipcEmit } from "@/composables/useEmitter";
|
import { loadState, saveState, emitState } from "@/state";
|
||||||
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 */
|
/* start and stop audio functionality */
|
||||||
// const { initAudio, closeAudio } = useAudio();
|
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.
|
* Controls for interfacing with the platform.
|
||||||
* Takes an onMessage callback which we define below.
|
* Takes an onMessage callback which we define below.
|
||||||
*/
|
*/
|
||||||
|
const { connect, send, close } = usePlatform((message: Message) => {
|
||||||
|
|
||||||
|
/* add the message to the state */
|
||||||
|
addMessage(message);
|
||||||
|
|
||||||
const onMessageCallback = (payload: string) => {
|
/* push the message to the browser */
|
||||||
|
if (win) emit("new-message", message);
|
||||||
|
|
||||||
const message = JSON.parse(payload);
|
});
|
||||||
console.log(typeof message);
|
|
||||||
|
|
||||||
/* if the platform fails to authenticate, we must back down */
|
/* send a message to the platform */
|
||||||
if (message === "CLOSE_AUTH_FAIL") {
|
export function sendMessage(message: Message) {
|
||||||
deauthenticate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcEmit("add-message", message)
|
/* add the message to the state */
|
||||||
return
|
addMessage(message);
|
||||||
|
|
||||||
/* on init, platform sends state, used to init canvas */
|
/* push the message to the browser */
|
||||||
if (isInitMessage(message)) {
|
if (win) emit("new-message", 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: Message): void {
|
|
||||||
|
|
||||||
/* socket send */
|
/* socket send */
|
||||||
send(message);
|
send(message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* launch a new session (the main process for authenticated users) */
|
/* launch a new session (the main process for authenticated users) */
|
||||||
export function launchSession(platformKey: string) {
|
export function launchSession(profile: Profile) {
|
||||||
|
|
||||||
|
/* load any previously saved state for that user */
|
||||||
|
loadState(profile);
|
||||||
|
|
||||||
/* connect to the platform */
|
/* connect to the platform */
|
||||||
connect(config.PLATFORM_URL, platformKey);
|
connect(profile);
|
||||||
|
|
||||||
/* initialize the audio streams */
|
/* initialize the audio streams */
|
||||||
// initAudio();
|
// initAudio();
|
||||||
|
|
||||||
}
|
/* finally we can push state to browser */
|
||||||
|
if (win) emitState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export function endSession() {
|
export function endSession() {
|
||||||
|
|
||||||
// closeAudio();
|
closeAudioStreams();
|
||||||
|
|
||||||
close();
|
closeSocket();
|
||||||
|
|
||||||
}
|
state.clear();
|
||||||
|
|
||||||
|
}
|
||||||
29
src/state.ts
Normal file
29
src/state.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
const Store = require('electron-store');
|
||||||
|
|
||||||
|
/* simple data persistance */
|
||||||
|
const store = new Store;
|
||||||
|
|
||||||
|
/* state of the session (e.g. profile and messages for now) */
|
||||||
|
let state: State | null = null;
|
||||||
|
|
||||||
|
/* load saved state in electron store for given user */
|
||||||
|
export function loadState(profile: Profile) {
|
||||||
|
state = store.get("state", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add a message to state.messages */
|
||||||
|
export function addMessage(message: Message) {
|
||||||
|
if (state) {
|
||||||
|
state.messages.push(message);
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveState() {
|
||||||
|
store.set("state", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emitState() {
|
||||||
|
emit("update-state", state);
|
||||||
|
}
|
||||||
|
|
||||||
31
src/store.ts
31
src/store.ts
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
56
src/types.ts
56
src/types.ts
|
|
@ -1,14 +1,16 @@
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
text: boolean | string;
|
text: boolean | string;
|
||||||
context: boolean | string;
|
context: boolean | string;
|
||||||
audio: boolean | string;
|
audio: boolean | string;
|
||||||
type: 1 | 2 | 3;
|
type: 1 | 2 | 3;
|
||||||
|
time: number;
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ViewMessage extends Message {
|
interface ViewMessage extends Message {
|
||||||
child: string;
|
child: string;
|
||||||
|
seen: boolean;
|
||||||
|
newMessage: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WindowState {
|
interface WindowState {
|
||||||
|
|
@ -24,54 +26,12 @@ interface Profile {
|
||||||
initials: string;
|
initials: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
profile: Profile | null;
|
||||||
|
messages
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginPayload {
|
interface LoginPayload {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthState {
|
|
||||||
profile: Profile | null;
|
|
||||||
token: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AccountCredentials {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Electron Ipc
|
|
||||||
*/
|
|
||||||
interface IpcHandlerCallback<I, O> {
|
|
||||||
(payload: I | null): Promise<Error | O>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IpcListenerCallback<T> {
|
|
||||||
(payload: T | null): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IIpcHandler<I, O> {
|
|
||||||
handle(): void;
|
|
||||||
remove(): void;
|
|
||||||
readonly _handlerCallback: IpcHandlerCallback<I, O>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IIpcListener<I> {
|
|
||||||
listen(): void;
|
|
||||||
remove(): void;
|
|
||||||
readonly _listenerCallback: IpcListenerCallback<I>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPCHandlers {
|
|
||||||
[handler: string]: IIpcHandler<any, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPCListeners {
|
|
||||||
[listener: string]: IIpcListener<any> | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IpcRendererEvent<T> {
|
|
||||||
channel: string;
|
|
||||||
payload: T | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { BrowserWindow, ipcMain, app } from "electron";
|
import { BrowserWindow, ipcMain } from "electron";
|
||||||
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
||||||
import { backgroundMitt } from './composables/useEmitter';
|
import { backgroundMitt } from './coposables/emitter';
|
||||||
import { saveToJson } from "./composables/useSaveToJSON";
|
import { saveToJson } from "./coposables/json";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import fs from 'fs';
|
|
||||||
import { config } from "@/config";
|
|
||||||
const { autoUpdater } = require('electron-updater');
|
const { autoUpdater } = require('electron-updater');
|
||||||
|
|
||||||
|
interface IpcRendererPayload {
|
||||||
|
endpoint: string;
|
||||||
|
message: Message | null;
|
||||||
|
}
|
||||||
|
|
||||||
let win: BrowserWindow | null;
|
let win: BrowserWindow | null;
|
||||||
let winState: WindowState;
|
let winState: WindowState;
|
||||||
|
|
||||||
|
|
@ -16,8 +19,8 @@ const loadWinState = (fileName: string): WindowState => {
|
||||||
let state: WindowState;
|
let state: WindowState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString());
|
state = JSON.parse(fs.readFileSync(configPath + fileName).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (error) {
|
catch (error) {
|
||||||
state = {
|
state = {
|
||||||
|
|
@ -29,8 +32,8 @@ const loadWinState = (fileName: string): WindowState => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when a NavBar button is pressed.
|
// Called when a NavBar button is pressed.
|
||||||
const onNavBar = (_event: any, action: string): void => {
|
const onNavBar = (_event: any, action: string): void => {
|
||||||
|
|
@ -44,9 +47,11 @@ const onNavBar = (_event: any, action: string): void => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util function to render message on ipc-renderer event.
|
// Util function to render message on ipc-renderer event.
|
||||||
const postToWindow = <T>(event: IpcRendererEvent<T>): void => {
|
const renderMessage = (payload: IpcRendererPayload): void => {
|
||||||
if (win) {
|
if (win) {
|
||||||
win.webContents.send(event.channel, event.payload);
|
win.webContents.send(payload.endpoint, {
|
||||||
|
message: payload.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +86,7 @@ const onWindowMount = (): void => {
|
||||||
|
|
||||||
// Gateway for messages to the frontend.
|
// Gateway for messages to the frontend.
|
||||||
backgroundMitt.removeAllListeners("ipc-renderer")
|
backgroundMitt.removeAllListeners("ipc-renderer")
|
||||||
backgroundMitt.on("ipc-renderer", postToWindow);
|
backgroundMitt.on("ipc-renderer", renderMessage);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,8 +110,8 @@ export default async function createWindow(): Promise<void> {
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
width: winState.width,
|
width: winState.width,
|
||||||
height: winState.height,
|
height: winState.height,
|
||||||
x: winState.x as number,
|
x: winState.x,
|
||||||
y: winState.y as number,
|
y: winState.y,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
backgroundColor: '#EBEBEB',
|
backgroundColor: '#EBEBEB',
|
||||||
frame: false,
|
frame: false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue