const { post } = useHttp();
-export const submit = async (email: string, password: string) => (
- await post('/account/login', { email, password })
-)
+export const usrPwdAuth = async (email: string, password: string) => {
-export const fetchAccount = async (email: string, token: string) => (
- await axios({
+ try {
+ return await post('/account/login', { email, password })
+
+ } catch (e) {
+ return null;
+ }
+
+}
+
+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: {
- email,
+ cid,
}
})
-)
+ } catch (e) {
+ return null;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+
+
+
+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);
+ }
+
+ }
+
+}
+++ /dev/null
-
-const Store = require('electron-store');
-
-const schema = {
- key: {
- type: 'string',
- },
- crimataId: {
- type: 'string'
- }
-};
-
-export const store = new Store({
- schema,
- encryptionKey: "super user test"
-});
import WebSocket from 'ws';
-export default function useWebSockets(receiveCallback: (s: string) => void, openCallback?: () => void) {
+export default function useWebSockets(onMessageCallback: (s: string) => void) {
let socket: WebSocket | null = null;
});
}
- 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 connect = (socketUrl: string, secret: string) => {
- const onClose = (event: WebSocket.CloseEvent) => {
- console.log("WS:Socket closed normally.")
- }
+ /* create a new socket */
+ socket = new WebSocket(socketUrl)
- // 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);
- }
+ /* add event listeners */
- const createSocket = (socketUrl: string) => {
+ socket.on("open", () => {
+ if (socket)
+ socket.send(secret);
+ });
- socket = new WebSocket(socketUrl)
+ socket.on("message", (event: WebSocket.MessageEvent) => {
+ onMessageCallback(event.data.toString())
+ });
- // Add listeners.
- socket.addEventListener("open", onOpen);
- socket.addEventListener("message", onServerMessage);
- socket.addEventListener("close", onClose);
- socket.addEventListener("error", onError);
+ socket.on("close", () => {
+ return
+ });
}
}
}
- const checkConnection = () => {
- return true;
- }
-
return {
- createSocket,
+ connect,
send,
- close,
- checkConnection
+ close
};
}
*
*/
-import { fetchAccount, submit } from "@/api/account";
+import { tokenAuth, usrPwdAuth } from "@/api/account";
import { launchSession, endSession } from "@/session";
import useIpc from "@/ipc/index";
import store from "@/composables/store";
-/* user profile, signals whether user is logged in */
-let auth: Profile | null = null;
-
/* authenticate the user */
export async function authenticate(email: string, password: string) {
/* attempt normal login */
- try {
- auth = await submit(email, password);
- } catch (e) {
- console.log(e);
- }
+ const platformKey, token, crimataId = await usrPwdAuth(email, password);
+
+ /* launch if successful */
+ if (platformKey)
+ launchSession(platformKey, crimataId);
- /* launch if profile */
- if (auth) {
- launchSession(auth);
- }
+ /* save the token */
+ store.set("token", token);
}
/* 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() {
+ /* initiate controls for frontend to use when needed */
+ useIpc();
+
/* launch browser window */
- // await createWindow();
+ await createWindow();
- /* attempt key-based authentication with business api */
- const token = store.get('key', null);
- const crimataId = store.get('crimataId', null);
+ /* attempt to get a login token from the store */
+ const token = store.get("token");
- try {
- const res = await fetchAccount(crimataId, token);
- auth = parseAuthRes(res);
- } catch (e) {
- console.log('[MAIN]', e);
- }
+ /* try to login with it, returns platform secret and new token on success */
+ if (token)
+ const newToken, profile = await tokenAuth(token);
- /* connect to Crimata, or listen for manual login req */
- if (auth) {
- launchSession(auth);
- }
+ /* if secret, we launch a session */
+ if (newToken)
+ launchSession(newToken, profile);
- /* initiate controls for frontend to use when needed */
- useIpc();
+ /* finally, save the most recent token */
+ store.set("token", newToken);
}
\ No newline at end of file
<!-- Main Components -->
<Messenger
- v-if="profile"
- :profile="profile"
+ v-if="state.profile"
+ :profile="state.profile"
+ :messages="state.messages"
/>
<Login v-else />
setup() {
- const state: Ref;
+ const crimataId = ref(false);
onMounted(async () => {
console.log("[APP]:mounted.");
/* listen for auth related messages */
- window.addEventListener("update-state", (event: any) => {
+ window.addEventListener("update-auth", (event: any) => {
state.value = event.data;
});
});
return {
- profile
+ crimataId
}
}
})
html, body {
margin: 0;
padding: 0;
- // Background color set in window.ts
}
#app {
import { ref } from 'vue';
-import invokeSavedMessages from "@/render/ipc";
import useScroll from "@/render/composables/scroll";
-const messages = ref(new Map());
+const canvas = ref();
-function getTimeStamp(): number {
- const currentdate = new Date();
- return currentdate.getTime();
+/* seed the canvas with messages */
+const seedCanvas = (messages: Message[]) => {
+ canvas.value = messages;
}
-const newViewMessage = (message: Message): ViewMessage => {
- 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 addMessage = (message: Message) => {
+ canvas.value.push(message);
}
const updateMessage = (message: Message) => {
- const viewMessage = messages.value.get(message.uid);
- viewMessage.context = message.context;
- viewMessage.text = message.text;
-}
-
-const loadSavedMessages = async () => {
- const messageData = await invokeSavedMessages();
- messages.value = new Map(Object.entries(messageData));
-}
-
-const saveMessages = () => {
- 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 = () => {
+ let target_message = canvas.value.filter((m: Message) => {
+ return m.uid = message.uid;
+ })[0];
- 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
+ if (target_message) {
+ target_message = message;
}
- 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() {
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 {
- messages,
- updateMessageView,
- loadSavedMessages
- }
-
-}
-
-
- // // 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);
+ canvas,
+ seedCanvas,
+ addMessage,
+ updateMessage
+ };
- // }
- // }
\ No newline at end of file
+}
\ No newline at end of file
v-for="message in messages"
:text="message.text"
:context="message.context"
+ :child
:key="message[0]"
/>
</div>
export default defineComponent({
name: "Messenger",
- props: ["state"],
+ props: ["profile", "messages"],
components: {
Message,
onMounted(() => {
- /* populate the message view with existing messages */
- updateMessageView(state.savedMessages, state.newMessages);
+ /* seed messages */
+ window.ipcRenderer.on("init-messages", (e_: any, payload: any) => {
+ seedMessages(payload.messages);
+ });
+
+ /* add a new message */
+ window.ipcRenderer.on("add-message", (_e: any, payload: any) => {
+ addMessage(payload.message);
+ });
- /* wait and listen for new messages to come in */
- window.ipcRenderer.on("new-message", (_e: any, payload: any) => {
- updateMessageView(payload.message);
+ /* update an existing message */
+ window.ipcRenderer.on("update-message", (_e: any, payload: any) => {
+ updateMessage(payload.message);
});
});
import useAudio from "@/audio";
-import { loadState, saveState, emitState } from "@/state";
+import Canvas from "@/composables/convas";
+import ipcEmit from "@/composables/emitter";
+
+/* data structure of messages that's tied to the UI */
+let msgrState: UIState | null = null;
/* start and stop audio functionality */
const { initAudio, closeAudio } = useAudio();
* Controls for interfacing with the platform.
* Takes an onMessage callback which we define below.
*/
-const { connect, send, close } = usePlatform((message: Message) => {
-
- /* add the message to the state */
- addMessage(message);
-
- /* push the message to the browser */
- if (win) emit("new-message", message);
+const { connect, send, close } = useWebsockets((content: any) => {
+
+ /* if the platform fails to authenticate, we must back down */
+ if (content === "auth_error") {
+ deauthenticate();
+ return;
+ }
+
+ /* on init, platform sends state, used to init canvas */
+ if (isInitMessage(content)) {
+ uiState.set(content);
+ }
+
+
+ else if (isAddMessage(content)) {
+ uiState.add(content);
+ }
+
+ else {
+ uiState.update(content);
+ }
});
/* send a message to the platform */
export function sendMessage(message: Message) {
- /* add the message to the state */
- addMessage(message);
-
- /* push the message to the browser */
- if (win) emit("new-message", message);
-
/* socket send */
send(message);
}
/* launch a new session (the main process for authenticated users) */
-export function launchSession(profile: Profile) {
-
- /* load any previously saved state for that user */
- loadState(profile);
+export function launchSession(platformKey: string, crimata_id: string) {
/* connect to the platform */
- connect(profile);
+ connect(PLATFORM_URL, platformKey);
/* initialize the audio streams */
- // initAudio();
+ initAudio();
- /* finally we can push state to browser */
- if (win) emitState();
+ /* push profile to window */
+ if (win)
+ ipcEmit("update-auth", crimata_id);
}
export function endSession() {
- closeAudioStreams();
+ closeAudio();
- closeSocket();
+ close();
- state.clear();
-
-}
\ No newline at end of file
+}
+++ /dev/null
-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);
-}
-
interface ViewMessage extends Message {
child: string;
- seen: boolean;
- newMessage: boolean;
}
interface WindowState {
initials: string;
}
-interface State {
- profile: Profile | null;
- messages
-}
-
interface LoginPayload {
email: string;
password: string;