update sockets, account, auth
This commit is contained in:
parent
9e8641b4de
commit
d851db2fcc
20 changed files with 411 additions and 326 deletions
|
|
@ -1,79 +0,0 @@
|
||||||
# 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": "background.js",
|
"main": "init.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",
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
"lintOnSave": false,
|
"lintOnSave": false,
|
||||||
"pluginOptions": {
|
"pluginOptions": {
|
||||||
"electronBuilder": {
|
"electronBuilder": {
|
||||||
"preload": "src/preload.ts",
|
"preload": "src/renderer/preload.ts",
|
||||||
"builderOptions": {
|
"builderOptions": {
|
||||||
"appId": "com.crimata.ElectronUpdaterApp",
|
"appId": "com.crimata.ElectronUpdaterApp",
|
||||||
"artifactName": "${productName}-${version}.${ext}",
|
"artifactName": "${productName}-${version}.${ext}",
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,7 @@
|
||||||
<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>
|
||||||
<!-- <h1>Hello World!</h1>
|
<div id="app"></div>
|
||||||
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>
|
||||||
|
|
|
||||||
105
src/account.ts
Normal file
105
src/account.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
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.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,36 +1,31 @@
|
||||||
|
|
||||||
import { useHttp } from "@/composables/http";
|
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 usrPwdAuth = async (email: string, password: string) => {
|
export const postAuth = async (token: string) => (
|
||||||
|
await axios({
|
||||||
try {
|
url: config.BUSINESS_URL + config.BUSINESS_PREFIX + '/account/authenticate',
|
||||||
return await post('/account/login', { email, password })
|
headers: {
|
||||||
|
Cookie: `jwt=${token}`
|
||||||
} catch (e) {
|
},
|
||||||
return null;
|
method: 'POST',
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
|
||||||
|
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'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
13
src/auth.ts
Normal file
13
src/auth.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
|
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
import {config} from "@/config";
|
||||||
|
|
||||||
const preFix = '/api';
|
const baseURL = config.BUSINESS_URL + config.BUSINESS_PREFIX;
|
||||||
|
|
||||||
const baseURL = "http://127.0.0.1:3000" + preFix;
|
|
||||||
|
|
||||||
|
|
||||||
interface Request {
|
interface Request {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
|
|
@ -12,7 +10,6 @@ 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 = '';
|
||||||
|
|
|
||||||
50
src/composables/ipcHandler.ts
Normal file
50
src/composables/ipcHandler.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
|
|
||||||
|
import {config} from "@/config";
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
export const saveToJson = (fileName: string, data: any) => {
|
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) {
|
if (err) {
|
||||||
console.log("Error when saving to json.")
|
console.log("Error when saving to json.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/composables/store.ts
Normal file
19
src/composables/store.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
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));
|
||||||
|
|
||||||
|
|
@ -3,9 +3,18 @@
|
||||||
|
|
||||||
import WebSocket from 'ws';
|
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> => {
|
const send = async (data: Record<string, any>): Promise<boolean> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
@ -21,30 +30,52 @@ export default function useWebSockets(onMessageCallback: (s: string) => void) {
|
||||||
|
|
||||||
const connect = (socketUrl: string, secret: string) => {
|
const connect = (socketUrl: string, secret: string) => {
|
||||||
|
|
||||||
|
// avoid setting multiple interval;
|
||||||
|
if (_connectionCheckInterval) clearInterval(_connectionCheckInterval);
|
||||||
|
|
||||||
/* create a new socket */
|
/* create a new socket */
|
||||||
socket = new WebSocket(socketUrl)
|
socket = new WebSocket(socketUrl);
|
||||||
|
|
||||||
/* add event listeners */
|
/* add event listeners */
|
||||||
|
|
||||||
socket.on("open", () => {
|
socket.on("open", () => {
|
||||||
if (socket)
|
|
||||||
socket.send(secret);
|
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) => {
|
socket.on("message", (event: WebSocket.MessageEvent) => {
|
||||||
onMessageCallback(event.data.toString())
|
messageCallback(event.data.toString())
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("close", () => {
|
socket.on("close", (event: WebSocket.CloseEvent) => {
|
||||||
return
|
connectionStatusCallback(false);
|
||||||
|
clearInterval(_connectionCheckInterval);
|
||||||
|
if (!event.wasClean) {
|
||||||
|
setTimeout(() => connect(socketUrl, secret), _reconnectTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("pong", () => connectionStatusCallback(true));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.close();
|
socket.close();
|
||||||
socket = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
17
src/config.ts
Normal file
17
src/config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,6 @@ import { app, protocol } from "electron";
|
||||||
import createWindow from "./window";
|
import createWindow from "./window";
|
||||||
import main from "./main";
|
import main from "./main";
|
||||||
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
console.log('Starting Crimata electron app.');
|
console.log('Starting Crimata electron app.');
|
||||||
|
|
||||||
// Scheme must be registered before the app is ready
|
// Scheme must be registered before the app is ready
|
||||||
|
|
@ -43,4 +41,4 @@ if (isDev) {
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,126 +1,32 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { submit, fetchProfile, logout } from "../api/account";
|
import { accountLogin, accountLogout } from "@/account";
|
||||||
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
import {IpcHandler} from "@/composables/ipcHandler";
|
||||||
import { store } from "@/composables/store";
|
|
||||||
|
|
||||||
|
const LOGIN_CHANNEL = "account-login";
|
||||||
|
const LOGOUT_CHANNEL = "account-logout";
|
||||||
|
|
||||||
const parseAuthRes = (authRes: any) => {
|
const loginHandler = new IpcHandler({
|
||||||
const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
|
channel: LOGIN_CHANNEL,
|
||||||
const profile = authRes.data as Profile;
|
handlerCallback: accountLogin
|
||||||
return {
|
});
|
||||||
token,
|
|
||||||
profile
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const logoutHandler = new IpcHandler({
|
||||||
|
channel: LOGOUT_CHANNEL,
|
||||||
|
handlerCallback: accountLogout
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlers = [loginHandler, logoutHandler];
|
||||||
|
|
||||||
|
export default handlers;
|
||||||
|
|
||||||
|
// export default function useAccountListeners(): void {
|
||||||
/**
|
//
|
||||||
* Get user profile from store and try to login with it.
|
// ipcMain.removeHandler(LOGIN_HANDLER);
|
||||||
*/
|
// ipcMain.handle(LOGIN_HANDLER, onLogin);
|
||||||
const onTokenLogin = async (
|
//
|
||||||
_event: IpcMainInvokeEvent,
|
// ipcMain.removeHandler(LOGOUT_HANDLER);
|
||||||
_payload: null
|
// ipcMain.handle(LOGOUT_HANDLER, onLogout);
|
||||||
): 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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,36 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import useAccountListeners from "./account";
|
import { IpcHandler } from "@/composables/ipcHandler";
|
||||||
|
import handlers from "./account";
|
||||||
import useSessionListeners from "./session";
|
import useSessionListeners from "./session";
|
||||||
// import useAudioListeners from "./audio";
|
// 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 {
|
export default function useIpc(): void {
|
||||||
|
|
||||||
useAccountListeners();
|
_initHandlers();
|
||||||
|
|
||||||
|
// useAccountListeners();
|
||||||
|
|
||||||
useSessionListeners();
|
useSessionListeners();
|
||||||
|
|
||||||
// useAudioListeners();
|
// useAudioListeners();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ function onSendMessage(_event: IpcMainEvent, payload: Message): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login attempt, returns success or not.
|
// Login attempt, returns success or not.
|
||||||
function onLogin(_event: IpcMainEvent, payload: LoginPayload) => {
|
function onLogin(_event: IpcMainEvent, payload: LoginPayload): void {
|
||||||
authenticate(payload.email, payload.password);
|
console.log('hello')
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useSessionListeners(): void {
|
export default function useSessionListeners(): void {
|
||||||
ipcMain.removeAllListeners("client-message");
|
ipcMain.removeAllListeners("client-message");
|
||||||
ipcMain.on("client-message", onSendMessage);
|
ipcMain.on("client-message", onSendMessage);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
60
src/main.ts
60
src/main.ts
|
|
@ -1,41 +1,21 @@
|
||||||
/**
|
/**
|
||||||
* 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 { tokenAuth, usrPwdAuth } from "@/api/account";
|
|
||||||
import { launchSession, endSession } from "@/session";
|
|
||||||
import useIpc from "@/ipc/index";
|
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 */
|
let authState: AuthState | null;
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function main() {
|
export default async function main() {
|
||||||
|
|
||||||
|
|
@ -45,18 +25,14 @@ export default async function main() {
|
||||||
/* launch browser window */
|
/* launch browser window */
|
||||||
await createWindow();
|
await createWindow();
|
||||||
|
|
||||||
/* attempt to get a login token from the store */
|
try {
|
||||||
const token = store.get("token");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* try to login with it, returns platform secret and new token on success */
|
}
|
||||||
if (token)
|
|
||||||
const newToken, profile = await tokenAuth(token);
|
|
||||||
|
|
||||||
/* if secret, we launch a session */
|
|
||||||
if (newToken)
|
|
||||||
launchSession(newToken, profile);
|
|
||||||
|
|
||||||
/* finally, save the most recent token */
|
|
||||||
store.set("token", newToken);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,34 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import useAudio from "@/audio";
|
// import useAudio from "@/audio";
|
||||||
import Canvas from "@/composables/convas";
|
|
||||||
import ipcEmit from "@/composables/emitter";
|
import ipcEmit from "@/composables/emitter";
|
||||||
|
import useWebsockets from "./composables/websockets";
|
||||||
|
import {config} from "@/config";
|
||||||
|
|
||||||
/* data structure of messages that's tied to the UI */
|
/* 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 */
|
/* 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.
|
* 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 } = useWebsockets((content: any) => {
|
|
||||||
|
|
||||||
|
const onMessageCallback = (message: string) => {
|
||||||
|
|
||||||
|
const content = JSON.parse(message);
|
||||||
|
|
||||||
/* if the platform fails to authenticate, we must back down */
|
/* if the platform fails to authenticate, we must back down */
|
||||||
if (content === "auth_error") {
|
if (content === "CLOSE_AUTH_FAIL") {
|
||||||
deauthenticate();
|
deauthenticate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -28,8 +38,8 @@ const { connect, send, close } = useWebsockets((content: any) => {
|
||||||
if (isInitMessage(content)) {
|
if (isInitMessage(content)) {
|
||||||
uiState.set(content);
|
uiState.set(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
else if (isAddMessage(content)) {
|
else if (isAddMessage(content)) {
|
||||||
uiState.add(content);
|
uiState.add(content);
|
||||||
}
|
}
|
||||||
|
|
@ -37,10 +47,15 @@ const { connect, send, close } = useWebsockets((content: any) => {
|
||||||
else {
|
else {
|
||||||
uiState.update(content);
|
uiState.update(content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
const onConnectionStatusCallback = (alive: boolean) => {
|
||||||
|
ipcEmit('connection-state', alive);
|
||||||
|
}
|
||||||
|
|
||||||
/* send a message to the platform */
|
const { connect, send, close } = useWebsockets(onMessageCallback, onConnectionStatusCallback);
|
||||||
|
|
||||||
|
/* send a message to the platform */
|
||||||
export function sendMessage(message: Message) {
|
export function sendMessage(message: Message) {
|
||||||
|
|
||||||
/* socket send */
|
/* socket send */
|
||||||
|
|
@ -48,24 +63,22 @@ export function sendMessage(message: 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, crimata_id: string) {
|
export function launchSession(platformKey: string) {
|
||||||
|
|
||||||
/* connect to the platform */
|
/* connect to the platform */
|
||||||
connect(PLATFORM_URL, platformKey);
|
connect(config.PLATFORM_URL, platformKey);
|
||||||
|
|
||||||
/* initialize the audio streams */
|
/* initialize the audio streams */
|
||||||
initAudio();
|
// initAudio();
|
||||||
|
|
||||||
/* push profile to window */
|
|
||||||
if (win)
|
|
||||||
ipcEmit("update-auth", crimata_id);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function endSession() {
|
export function endSession() {
|
||||||
|
|
||||||
closeAudio();
|
// closeAudio();
|
||||||
|
|
||||||
close();
|
close();
|
||||||
|
|
||||||
|
|
|
||||||
23
src/types.ts
23
src/types.ts
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
text: boolean | string;
|
text: boolean | string;
|
||||||
context: boolean | string;
|
context: boolean | string;
|
||||||
|
|
@ -28,3 +29,25 @@ interface LoginPayload {
|
||||||
email: string;
|
email: string;
|
||||||
password: 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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { BrowserWindow, ipcMain } from "electron";
|
import { BrowserWindow, ipcMain, app } from "electron";
|
||||||
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
|
||||||
import { backgroundMitt } from './coposables/emitter';
|
import { backgroundMitt } from './composables/emitter';
|
||||||
import { saveToJson } from "./coposables/json";
|
import { saveToJson } from "./composables/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 {
|
interface IpcRendererPayload {
|
||||||
|
|
@ -19,8 +21,8 @@ const loadWinState = (fileName: string): WindowState => {
|
||||||
let state: WindowState;
|
let state: WindowState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state = JSON.parse(fs.readFileSync(configPath + fileName).toString());
|
state = JSON.parse(fs.readFileSync(config.configPath + fileName).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (error) {
|
catch (error) {
|
||||||
state = {
|
state = {
|
||||||
|
|
@ -32,8 +34,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 => {
|
||||||
|
|
@ -110,8 +112,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,
|
x: winState.x as number,
|
||||||
y: winState.y,
|
y: winState.y as number,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
backgroundColor: '#EBEBEB',
|
backgroundColor: '#EBEBEB',
|
||||||
frame: false,
|
frame: false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue