+++ /dev/null
-<template>
- <!-- Only render when profile has been set -->
- <div id="app" v-if="(ready)">
-
- <button id="titlebar" />
-
- <!-- Minimize and exit buttons. -->
- <span class="menu">
- <button
- class="menuButton exitButton"
- @click.prevent="post('nav-bar', 'close')"
- />
- <button
- class="menuButton minimizeButton"
- @click.prevent="post('nav-bar', 'min')"
- />
- </span>
-
- <!-- Main Pages -->
- <Messenger
- v-if="profile"
- :profile="profile"
- :newMessages="newMessages"
- />
- <Login v-else />
-
- </div>
-
- <!-- Show spash screen if ready is false -->
- <Splash v-else />
-
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, onUnmounted, ref } from "vue";
-import { IpcRendererEvent } from "electron";
-import { useIpc } from "@/modules/ipc";
-import { useProfile } from "@/modules/auth"
-import { invokeProfile } from "@/ipcRend/account";
-import { postInitSession, postMount } from "@/ipcRend/session";
-
-import Splash from "@/components/splash.vue";
-import Messenger from "@/components/messenger.vue";
-import Login from "@/components/login.vue";
-
-import { Profile } from "@/types";
-
-
-export default defineComponent({
-
- components: {
- Splash,
- Messenger,
- Login
- },
-
- setup() {
-
- const { post, invoke } = useIpc();
-
- const { profile, setProfile, clearProfile } = useProfile();
-
- // Whether browser has received user info yet.
- const ready = ref(false);
-
- // New messages that browser missed while closed.
- const newMessages = ref([]);
-
- // Receive updated information about the session.
- const updateState = (_event: IpcRendererEvent, payload: any) => {
- console.log('YAYAYAYAYA');
- console.log("APP:Received updated profile and new messages: \n" +
- ` profile: ${payload.message.profile}\n` +
- ` new: ${payload.message.newMessages}`);
-
- if (payload.message.profile) {
- console.log(`APP:Logged-in, showing Messenger View.`);
- } else {
- console.log(`APP:Logged-out, showing Login View.`);
- }
-
- newMessages.value = payload.message.newMessages;
-
- ready.value = true;
-
- }
-
- const onSessionAuthFail = (_event: IpcRendererEvent, _payload: null) => {
- clearProfile();
- }
-
- onMounted(async () => {
- console.log("APP:mounted.");
-
- window.ipcRenderer.on("session-auth-fail", onSessionAuthFail)
- window.ipcRenderer.on("update-state", updateState)
- window.ipcRenderer.on("update_available", () => {
- console.log('testing auto update');
- })
-
- window.ipcRenderer.on("update_downloaded", () => {
- console.log('testing update download');
- })
-
- postMount();
-
- try {
- const profile = await invokeProfile() as Profile;
- setProfile(profile);
- } catch(e) {
- console.log('[REND] Auth:', e);
- clearProfile();
- } finally {
- ready.value = true;
- if (profile.value.crimataId) {
- // start session
- postInitSession();
- }
- }
-
- });
-
- onUnmounted(() => {
- window.ipcRenderer.removeAllListeners("update-state");
- window.ipcRenderer.removeAllListeners("session-auth-fail");
- })
-
- return {
- ready,
- post,
- newMessages,
- profile
- }
- }
-})
-
-</script>
-
-<style lang="scss">
-
-html, body {
- margin: 0;
- padding: 0;
- // Background color set in window.ts
-}
-
-#app {
- font-family: "SF Pro Text";
-
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-
- height: 100vh;
- width: 100vw;
-
- border-radius: 15px;
- }
-
-#titlebar {
- position: fixed;
-
- width: 100vw;
- height: 54px;
-
- opacity: 0.75;
-
- background-color: #EBEBEB;
-
- -webkit-app-region: drag;
-
- border: none;
- outline: none;
-
- z-index: 1;
-
-}
-
-.menu {
- position: fixed;
- margin-left: 20px;
- margin-top: 20px;
-
- z-index: 2;
-
-}
-
-.menuButton {
- border: none;
- outline:none;
- min-width: 14px;
- min-height: 14px;
- border-radius: 7px;
-}
-
-.exitButton {
- background-color: #FF6157;
-}
-
-.exitButton:active {
- background: #c14645;
-}
-
-.minimizeButton {
- background-color: #FFC12F;
- margin-left: 8px;
-}
-
-.minimizeButton:active {
- background-color: #c08e38;
-}
-
-</style>
+++ /dev/null
-
-import { store } from "@/background/store";
-
-interface PlatformAuthProtocol {
- key: string;
- crimata_id: string;
-}
-
-export const getAuthPayload = (): PlatformAuthProtocol => ({
- key: store.get('key'),
- crimata_id: store.get('crimataId')
-});
-
+++ /dev/null
-/*
- * Entry point for Crimata electron app.
- * "Look on my Works, ye Mighty, and despair!"
- */
-
-"use strict";
-
-import { initApp } from './background/init';
-import { protocol } from "electron";
-
-// Scheme must be registered before the app is ready
-protocol.registerSchemesAsPrivileged([
- { scheme: "app", privileges: { secure: true, standard: true } }
-]);
-
-// Load environment variable
-const isDev = require('electron-is-dev');
-
-// NOTE Program Begins Here
-(() => {
-
- console.log('Starting Crimata electron app.');
- initApp(isDev);
-
-})();
+++ /dev/null
-
-"use strict";
-
-import { Profile } from "@/types";
-import { submit, fetchProfile, logout } from "@/api/account";
-import { ipcMain, IpcMainInvokeEvent } from "electron";
-import { store } from "@/background/store";
-import { endSession } from "@/background/session";
-
-
-const parseAuthRes = (authRes: any) => {
- const token = authRes.headers['set-cookie'][0].split(";")[0].split("=")[1] as string;
- const profile = authRes.data as Profile;
- return {
- token,
- profile
- }
-};
-
-
-const onProfile = async (
- _event: IpcMainInvokeEvent,
- _payload: 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 {
-
- const res = await fetchProfile(
- crimataId,
- token
- );
-
- const parsed = parseAuthRes(res);
- resolve(parsed.profile);
-
- } catch(e) {
- reject(new Error('Unable to authenticate and fetch account 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 {
- const res = await submit(account.email, account.password);
- const parsed = parseAuthRes(res);
-
- // save jwt token and profile
- store.set('key', parsed.token);
- store.set('crimataId', parsed.profile.crimataId);
-
- // return profile to renderer
- resolve(parsed.profile);
-
- } catch(e) {
- console.log('[API]', e);
- 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);
-
-}
+++ /dev/null
-
-"use strict";
-
-import { initSession, emitNewMessages, sendMessage } from '@/background/session';
-import { ipcMain, IpcMainInvokeEvent, IpcMainEvent } from "electron";
-import { initAudioIO } from "@/background/audio";
-import { ClientMessage } from "@/types";
-
-
-// Instantiate socket session with crimata-platorm.
-const onSessionInit = (
- _event: IpcMainInvokeEvent,
- _payload: null
-): void => {
-
- console.log('[IPC]: init-session');
-
- initSession();
-
- initAudioIO();
-}
-
-
-const onAppMounted = (
- _event: IpcMainInvokeEvent,
- _payload: null
-): void => {
-
- console.log('[IPC]: app-mounted');
-
- emitNewMessages()
-};
-
-
-// Handle messages from window/client.
-const onClientMessage = (
- _event: IpcMainEvent,
- payload: ClientMessage
-): void => {
-
- console.log('[IPC]: client-message');
-
- sendMessage(payload);
-}
-
-
-export default function useSessionListeners(): void {
-
- console.log('[IPC]: Init session listeners.');
-
- // Attach listeners for frontend.
- ipcMain.removeAllListeners("client-message");
- ipcMain.on("client-message", onClientMessage);
-
- // Attack browser window init listener.
- ipcMain.removeAllListeners("app-mounted");
- ipcMain.on("app-mounted", onAppMounted);
-
- ipcMain.removeAllListeners("init-session");
- ipcMain.on("init-session", onSessionInit);
-
-}
+++ /dev/null
-/*
- * Creates a websocket session with Crimata Servers.
- *
- * Connects to Servers and attempts key authentication. Server will respond
- * with key and user profile. We send the profile to the browser. We also
- * resend this information on new broser window. We then serve as a
- * communication interface between the window and the servers. It will
- * automatically try to reconnect on websocket disconnect.
- */
-
-import { backgroundMitt } from "@/modules/emitter";
-import { ipcEmit, loadState } from './helpers';
-import WebSocket from 'ws';
-
-import useWebSockets from "./websockets";
-
-import { play } from "./audio";
-import { renderMessage } from "@/modules/message";
-import { SessionState } from "@/types";
-
-let win = true;
-
-// Info saved to json on quit (key, newMessages).
-let state: SessionState;
-
-let socket: WebSocket | null = null;
-
-
-// Calls appropriate endpoint for a server message.
-const onMessage = (data: string): void => {
- let message = JSON.parse(data);
-
- if (message === "CLOSE_AUTH_FAIL") {
- ipcEmit("session-auth-fail", null)
- return;
- }
-
- // Standard message.
- if (message.content) {
-
- // Convert to render message
- message = renderMessage(
- message.content.text,
- message.content.audio,
- message.context,
- message.modifier
- );
-
- if (win) {
- console.log("SESS:Emitting standard message.")
- if (message.audio) {
- play(message.audio)
- }
- ipcEmit("render-message", message)
- }
-
- else {
- console.log("SESS:No window: saving message.")
- state.newMessages.push(message);
- }
- }
-
- else {
- if (win) {
- ipcEmit("render-message", message)
- }
- }
-
-};
-
-
-// Websockets module.
-const { createSocket, send } = useWebSockets(onMessage);
-
-
-export const emitNewMessages = (): void => {
- if (state) {
- ipcEmit("update-state", {
- newMessages: state.newMessages,
- });
- }
-
-}
-
-
-export const sendMessage = (payload: Record<string, any>): void => {
- try {
- send(payload)
- } catch(e) {
- console.log("Unable to send message: ", payload);
- }
-
-}
-
-export const endSession = (): void => {
- if (socket) {
- socket.close();
- socket = null;
- }
-}
-
-
-// Call this to initialize session with Crimata servers.
-export const initSession = (): void => {
- console.log("SESS:Creating new session.")
-
- // Load Json or createState.
- state = loadState("session.json");
-
- // Open socket connection.
- if (!socket)
- socket = createSocket();
-
- // Keep win up-to-date.
- backgroundMitt.on('window-active', (state: boolean) => {
- win = state;
- });
-
-}
+++ /dev/null
-
-"use strict";
-
-import WebSocket from 'ws';
-import { getAuthPayload } from "./authPayload";
-import { ipcEmit } from './helpers';
-
-let socket: WebSocket;
-
-const socketUrl = "ws://127.0.0.1:8760"
-
-const _connectionCheckTimeout = 4000;
-const _reconnectTimeout = 1000;
-let _connectionCheckInterval: ReturnType<typeof setTimeout>;
-
-
-// Run every time we want to connect to backend.
-export default function useWebSockets(
- receiveCallback: (s: string) => void,
- openCallback?: () => void
-) {
-
- // Returns bool (sucess or fail).
- const sendMessage = (data: any) => {
- console.log("WS:Sending message: ", data)
-
- if (socket.readyState !== 1) {
- return false
- }
-
- else {
- socket.send(JSON.stringify(data))
- return true
- }
-
- }
-
- const send = async (data: Record<string, any>): Promise<boolean> => (
- new Promise((resolve, reject) => {
- if (socket.readyState !== 1) {
- reject(false);
- }
- socket.send(JSON.stringify(data))
- resolve(true);
-
- }))
-
-
- const onOpen = (_event: WebSocket.OpenEvent) => {
-
- console.log("WS:Connected to WS Server!");
- const jwt = getAuthPayload();
- socket.send(JSON.stringify(jwt));
-
- // ping server
- _connectionCheckInterval = setInterval(() => {
-
- if (socket) socket.ping(null, true, (e: Error) => {
- if (e) {
- ipcEmit('connection-alive', false);
- socket.close();
- setTimeout(createSocket, 1000);
- }
- });
-
- }, _connectionCheckTimeout);
-
- 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.", event.wasClean)
-
- clearInterval(_connectionCheckInterval);
-
- if (!event.wasClean) {
- ipcEmit('connection-alive', false);
- setTimeout(createSocket, 1000);
- }
- }
-
- // Reconnect automatically on error.
- const onError = (event: WebSocket.ErrorEvent) => {
- console.log("WS:WebSocket error: ", event.message);
- }
-
-
- const createSocket = (): WebSocket => {
-
- if (_connectionCheckInterval) clearInterval(_connectionCheckInterval);
-
- socket = new WebSocket(socketUrl);
-
- // Add listeners.
- socket.addEventListener("open", onOpen);
- socket.addEventListener("message", onServerMessage);
- socket.addEventListener("close", onClose);
- socket.addEventListener("error", onError);
- socket.addEventListener("pong", () => {
- ipcEmit('connection-alive', true);
- });
-
- return socket;
-
- }
-
- return {
- createSocket,
- sendMessage,
- send
- }
-
-}
+++ /dev/null
-
-import { useIpc } from "@/modules/ipc";
-
-import { ClientMessage } from "@/types";
-
-const { post } = useIpc();
-
-
-export const postMount = (): void => (
- post("app-mounted", null)
-);
-
-
-export const postInitSession = (): void => (
- post("init-session", null)
-);
-
-
-export const postMessage = (payload: ClientMessage): void => (
- post('client-message', payload)
-);
-
-