+++ /dev/null
-# 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
- }
-}
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
- "main": "background.js",
+ "main": "init.js",
"dependencies": {
"@google-cloud/speech": "^4.2.0",
"@types/animejs": "^3.1.2",
"lintOnSave": false,
"pluginOptions": {
"electronBuilder": {
- "preload": "src/preload.ts",
+ "preload": "src/renderer/preload.ts",
"builderOptions": {
"appId": "com.crimata.ElectronUpdaterApp",
"artifactName": "${productName}-${version}.${ext}",
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
- <!-- <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>
+ <div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
--- /dev/null
+
+import { postAuth, postLogin, postLogout } from "@/api/account";
+import { endSession, launchSession } from "@/session";
+import { getToken, clearToken, setToken } from "@/composables/store";
+import { parseAuthRes } from "./auth";
+
+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)
+
+ return {
+ profile: parsed.profile,
+ token: parsed.token
+ };
+
+ } catch(e) {
+ console.log('[ACCOUNT]', e);
+ clearToken();
+ 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)
+
+ // launch session
+ launchSession(parsed.token)
+
+ // return profile to renderer
+ return parsed.profile;
+
+ } catch(e) {
+ throw e;
+ }
+}
+
+// export const accountLogin = async (account: Account): Promise<Error | Profile> => {
+//
+// 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)
+//
+// // launch session
+// launchSession(parsed.token)
+//
+// // return profile to renderer
+// return parsed.profile;
+//
+// } catch(e) {
+// console.log('[ACCOUNT]', e);
+// throw (new Error('Failed to authenticate'));
+// }
+//
+// }
+
+export const accountLogout = async (): Promise<Error | void> => {
+
+ try {
+ // post logout to backend
+ await postLogout();
+
+ // remove key and crimataId
+ clearToken();
+
+ // kill crimata platform session
+ endSession();
+
+ return;
+
+ } catch(e) {
+ console.log('[ACCOUNT]', e);
+ return (new Error('Failed to logout. Please try again.'));
+ }
+
+}
+
+
import { useHttp } from "@/composables/http";
import axios from "axios";
+import {config} from "@/config";
const { post } = useHttp();
-export const usrPwdAuth = async (email: string, password: string) => {
+export const postAuth = async (token: string) => (
+ await axios({
+ url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate',
+ headers: {
+ Cookie: `jwt=${token}`
+ },
+ method: 'POST',
+ })
+).data;
- try {
- return await post('/account/login', { email, password })
- } catch (e) {
- return null;
- }
+export const postLogin = async (email: string, password: string) => (
+ await post('/account/login', { email, password })
+).data;
+
+
+export const postLogout =
+ async (): Promise<null | Error> => (await post('/account/logout'));
+
-}
-export const tokenAuth = async (cid: string, token: string) => {
- try {
- return await axios({
- url: "http://127.0.0.1:3000/api/account/profile",
- headers: {
- Cookie: `jwt=${token}`
- },
- method: 'GET',
- data: {
- cid,
- }
- })
- } catch (e) {
- return null;
- }
-}
\ No newline at end of file
--- /dev/null
+
+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
+ }
+};
+
+
+
+
import axios, { AxiosRequestConfig } from 'axios';
+import {config} from "@/config";
-const preFix = '/api';
-
-const baseURL = "http://127.0.0.1:3000" + preFix;
-
+const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX;
interface Request {
endpoint: string;
config?: Record<string, any>;
}
-
const makeQuery = (reqQuery: Record<string, any>) => {
let result = '';
--- /dev/null
+
+import { ipcMain, IpcMainInvokeEvent } from "electron";
+
+export class IpcHandler<InputType, ReturnType> implements IIpcHandler<InputType, ReturnType> {
+
+ readonly channel: string;
+
+ readonly _handlerCallback: IpcHandlerCallback<InputType, ReturnType>;
+
+ constructor(options: {
+ channel: string;
+ handlerCallback: IpcHandlerCallback<InputType, ReturnType>;
+ }) {
+ this.channel = options.channel;
+ this._handlerCallback = options.handlerCallback;
+ }
+
+ handle() {
+ ipcMain.handle(this.channel, this._onInvoke);
+ }
+
+ remove() {
+ ipcMain.removeHandler(this.channel);
+ }
+
+ private async _onInvoke(_e: IpcMainInvokeEvent, payload?: string | null): Promise<Error | ReturnType> {
+
+ return new Promise(async (resolve, reject) => {
+
+ console.log(`[IPC]:${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]:${this.channel}`, e);
+ reject(e);
+ }
+ });
+ }
+
+}
+
+
+
+
+import {config} from "@/config";
+import fs from 'fs';
+
export const saveToJson = (fileName: string, data: any) => {
- fs.writeFile(configPath + fileName, JSON.stringify(data), (err) => {
+ fs.writeFile(config.configPath + fileName, JSON.stringify(data), (err) => {
if (err) {
console.log("Error when saving to json.")
- }
+ }
})
-}
\ No newline at end of file
+}
--- /dev/null
+const Store = require('electron-store');
+
+const schema = {
+ key: {
+ type: 'string',
+ },
+};
+
+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));
+
import WebSocket from 'ws';
-export default function useWebSockets(onMessageCallback: (s: string) => void) {
- let socket: WebSocket | null = null;
+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) => {
const connect = (socketUrl: string, secret: string) => {
+ // avoid setting multiple interval;
+ if (_connectionCheckInterval) clearInterval(_connectionCheckInterval);
+
/* create a new socket */
- socket = new WebSocket(socketUrl)
+ socket = new WebSocket(socketUrl);
/* add event listeners */
-
socket.on("open", () => {
- if (socket)
+
socket.send(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) => {
- onMessageCallback(event.data.toString())
+ messageCallback(event.data.toString())
});
- socket.on("close", () => {
- return
+ 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();
- socket = null;
}
}
--- /dev/null
+
+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 || 3010;
+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')
+}
import createWindow from "./window";
import main from "./main";
-require('dotenv').config();
-
console.log('Starting Crimata electron app.');
// Scheme must be registered before the app is ready
process.on("SIGTERM", () => {
app.quit();
});
-}
\ No newline at end of file
+}
"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);
-
-}
+import { accountLogin, accountLogout } from "@/account";
+import {IpcHandler} from "@/composables/ipcHandler";
+
+const LOGIN_CHANNEL = "account-login";
+const LOGOUT_CHANNEL = "account-logout";
+
+const loginHandler = new IpcHandler({
+ channel: LOGIN_CHANNEL,
+ handlerCallback: accountLogin
+});
+
+const logoutHandler = new IpcHandler({
+ channel: LOGOUT_CHANNEL,
+ handlerCallback: accountLogout
+});
+
+const handlers = [loginHandler, logoutHandler];
+
+export default handlers;
+
+// export default function useAccountListeners(): void {
+//
+// ipcMain.removeHandler(LOGIN_HANDLER);
+// ipcMain.handle(LOGIN_HANDLER, onLogin);
+//
+// ipcMain.removeHandler(LOGOUT_HANDLER);
+// ipcMain.handle(LOGOUT_HANDLER, onLogout);
+//
+// }
"use strict";
-import useAccountListeners from "./account";
+import { IpcHandler } from "@/composables/ipcHandler";
+import handlers from "./account";
import useSessionListeners from "./session";
// import useAudioListeners from "./audio";
+interface IPCHandlers {
+ [channel: string]: IpcHandler<any, any>;
+}
+
+const ipcHandlers: IPCHandlers = {};
+
+const _initHandlers = () => {
+ handlers.forEach((h) => {
+ if (!(h.channel in ipcHandlers)) {
+ ipcHandlers[h.channel] = h;
+ h.handle();
+ }
+ });
+}
export default function useIpc(): void {
- useAccountListeners();
+ _initHandlers();
+
+ // useAccountListeners();
useSessionListeners();
// useAudioListeners();
}
+
+
}
// Login attempt, returns success or not.
-function onLogin(_event: IpcMainEvent, payload: LoginPayload) => {
- authenticate(payload.email, payload.password);
+function onLogin(_event: IpcMainEvent, payload: LoginPayload): void {
+ console.log('hello')
}
export default function useSessionListeners(): void {
ipcMain.removeAllListeners("client-message");
ipcMain.on("client-message", onSendMessage);
-}
\ No newline at end of file
+}
/**
* Where the background logic really begins, gets called by app.onReady().
- *
+ *
* Handles authentication. If profile is set, we launch a session, which consis
* of opening a connection with the platform, initializing the audio streams.
- *
+ *
* The session is primarily an interface between the frontend and the platform,
* relaying messages from one to the other.
- *
+ *
*/
-import { tokenAuth, usrPwdAuth } from "@/api/account";
-import { launchSession, endSession } from "@/session";
import useIpc from "@/ipc/index";
-import store from "@/composables/store";
+import { accountAuth } from "./account";
+import { launchSession } from "./session";
+import ipcEmit from "./composables/emitter";
+import createWindow from "./window";
-/* authenticate the user */
-export async function authenticate(email: string, password: string) {
-
- /* attempt normal login */
- const platformKey, token, crimataId = await usrPwdAuth(email, password);
-
- /* launch if successful */
- if (platformKey)
- launchSession(platformKey, crimataId);
-
- /* save the token */
- store.set("token", token);
-
-}
-
-/* logout the user, end the session */
-export function deauthenticate() {
-
- /* terminate the session */
- endSession();
-
-}
+let authState: AuthState | null;
export default async function main() {
/* launch browser window */
await createWindow();
- /* attempt to get a login token from the store */
- const token = store.get("token");
-
- /* try to login with it, returns platform secret and new token on success */
- if (token)
- const newToken, profile = await tokenAuth(token);
+ try {
+ authState = await accountAuth() as AuthState;
+ } catch(e) {
+ console.log('AUTH:', e);
+ authState = null;
+ } finally {
+ if (authState) launchSession(authState.token as string);
+ ipcEmit("set-profile", authState?.profile);
+ }
- /* if secret, we launch a session */
- if (newToken)
- launchSession(newToken, profile);
-
- /* finally, save the most recent token */
- store.set("token", newToken);
-
-}
\ No newline at end of file
+}
-import useAudio from "@/audio";
-import Canvas from "@/composables/convas";
+// import useAudio from "@/audio";
import ipcEmit from "@/composables/emitter";
+import useWebsockets from "./composables/websockets";
+import {config} from "@/config";
/* data structure of messages that's tied to the UI */
-let msgrState: UIState | null = null;
+const uiState: any | null = null;
/* start and stop audio functionality */
-const { initAudio, closeAudio } = useAudio();
+// const { initAudio, closeAudio } = useAudio();
+
+let isInitMessage: any;
+
+let deauthenticate: any;
+let isAddMessage: any
/**
* Controls for interfacing with the platform.
* Takes an onMessage callback which we define below.
*/
-const { connect, send, close } = useWebsockets((content: any) => {
+
+
+const onMessageCallback = (message: string) => {
+
+ const content = JSON.parse(message);
/* if the platform fails to authenticate, we must back down */
- if (content === "auth_error") {
+ if (content === "CLOSE_AUTH_FAIL") {
deauthenticate();
return;
}
if (isInitMessage(content)) {
uiState.set(content);
}
-
-
+
+
else if (isAddMessage(content)) {
uiState.add(content);
}
else {
uiState.update(content);
}
+}
+
+const onConnectionStatusCallback = (alive: boolean) => {
+ ipcEmit('connection-state', alive);
+}
-});
+const { connect, send, close } = useWebsockets(onMessageCallback, onConnectionStatusCallback);
-/* send a message to the platform */
+/* send a message to the platform */
export function sendMessage(message: Message) {
/* socket send */
}
+
/* launch a new session (the main process for authenticated users) */
-export function launchSession(platformKey: string, crimata_id: string) {
+export function launchSession(platformKey: string) {
/* connect to the platform */
- connect(PLATFORM_URL, platformKey);
+ connect(config.PLATFORM_URL, platformKey);
/* initialize the audio streams */
- initAudio();
-
- /* push profile to window */
- if (win)
- ipcEmit("update-auth", crimata_id);
+ // initAudio();
}
+
export function endSession() {
- closeAudio();
+ // closeAudio();
close();
+
interface Message {
text: boolean | string;
context: boolean | string;
email: string;
password: string;
}
+
+interface AuthState {
+ profile: Profile | null;
+ token: string | null;
+}
+
+interface AccountCredentials {
+ email: string;
+ password: string;
+}
+
+interface IpcHandlerCallback<I, O> {
+ (payload: I | null): Promise<Error | O>;
+}
+
+interface IIpcHandler<I, O> {
+ handle(): void;
+ remove(): void;
+ readonly _handlerCallback: IpcHandlerCallback<I, O>;
+}
+
+
"use strict";
-import { BrowserWindow, ipcMain } from "electron";
+import { BrowserWindow, ipcMain, app } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
-import { backgroundMitt } from './coposables/emitter';
-import { saveToJson } from "./coposables/json";
+import { backgroundMitt } from './composables/emitter';
+import { saveToJson } from "./composables/json";
import * as path from "path";
+import fs from 'fs';
+import { config } from "@/config";
const { autoUpdater } = require('electron-updater');
interface IpcRendererPayload {
let state: WindowState;
try {
- state = JSON.parse(fs.readFileSync(configPath + fileName).toString());
- }
+ state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString());
+ }
catch (error) {
state = {
}
return state
-
-}
+
+}
// Called when a NavBar button is pressed.
const onNavBar = (_event: any, action: string): void => {
win = new BrowserWindow({
width: winState.width,
height: winState.height,
- x: winState.x,
- y: winState.y,
+ x: winState.x as number,
+ y: winState.y as number,
resizable: true,
backgroundColor: '#EBEBEB',
frame: false,