more changes

This commit is contained in:
Andrew Gundersen 2021-06-12 09:49:41 -05:00
commit 9e8641b4de
11 changed files with 167 additions and 285 deletions

View file

@ -4,20 +4,33 @@ import axios from "axios";
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;
}
}

36
src/composables/canvas.ts Normal file
View file

@ -0,0 +1,36 @@
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);
}
}
}

View file

@ -1,16 +0,0 @@
const Store = require('electron-store');
const schema = {
key: {
type: 'string',
},
crimataId: {
type: 'string'
}
};
export const store = new Store({
schema,
encryptionKey: "super user test"
});

View file

@ -3,7 +3,7 @@
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;
@ -19,36 +19,25 @@ export default function useWebSockets(receiveCallback: (s: string) => void, open
});
}
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) => {
const connect = (socketUrl: string, secret: string) => {
/* create a new socket */
socket = new WebSocket(socketUrl)
// Add listeners.
socket.addEventListener("open", onOpen);
socket.addEventListener("message", onServerMessage);
socket.addEventListener("close", onClose);
socket.addEventListener("error", onError);
/* add event listeners */
socket.on("open", () => {
if (socket)
socket.send(secret);
});
socket.on("message", (event: WebSocket.MessageEvent) => {
onMessageCallback(event.data.toString())
});
socket.on("close", () => {
return
});
}
@ -59,15 +48,10 @@ export default function useWebSockets(receiveCallback: (s: string) => void, open
}
}
const checkConnection = () => {
return true;
}
return {
createSocket,
connect,
send,
close,
checkConnection
close
};
}

View file

@ -9,37 +9,29 @@
*
*/
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 profile */
if (auth) {
launchSession(auth);
}
/* launch if successful */
if (platformKey)
launchSession(platformKey, crimataId);
/* 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();
@ -47,26 +39,24 @@ export function deauthenticate() {
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();
/* 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);
/* if secret, we launch a session */
if (newToken)
launchSession(newToken, profile);
/* finally, save the most recent token */
store.set("token", newToken);
}

View file

@ -6,8 +6,9 @@
<!-- Main Components -->
<Messenger
v-if="profile"
:profile="profile"
v-if="state.profile"
:profile="state.profile"
:messages="state.messages"
/>
<Login v-else />
@ -39,13 +40,13 @@ export default defineComponent({
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;
});
@ -56,7 +57,7 @@ export default defineComponent({
});
return {
profile
crimataId
}
}
})
@ -68,7 +69,6 @@ export default defineComponent({
html, body {
margin: 0;
padding: 0;
// Background color set in window.ts
}
#app {

View file

@ -1,143 +1,38 @@
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));
}
let target_message = canvas.value.filter((m: Message) => {
return m.uid = message.uid;
})[0];
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 = () => {
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
}
canvas,
seedCanvas,
addMessage,
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);
// }
// }
}

View file

@ -11,6 +11,7 @@
v-for="message in messages"
:text="message.text"
:context="message.context"
:child
:key="message[0]"
/>
</div>
@ -28,7 +29,7 @@ import useMessages from "@/render/composables/messages";
export default defineComponent({
name: "Messenger",
props: ["state"],
props: ["profile", "messages"],
components: {
Message,
@ -43,12 +44,19 @@ export default defineComponent({
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);
});
/* wait and listen for new messages to come in */
window.ipcRenderer.on("new-message", (_e: any, payload: any) => {
updateMessageView(payload.message);
/* add a new message */
window.ipcRenderer.on("add-message", (_e: any, payload: any) => {
addMessage(payload.message);
});
/* update an existing message */
window.ipcRenderer.on("update-message", (_e: any, payload: any) => {
updateMessage(payload.message);
});
});

View file

@ -3,7 +3,11 @@
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();
@ -12,53 +16,57 @@ 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) => {
const { connect, send, close } = useWebsockets((content: any) => {
/* add the message to the state */
addMessage(message);
/* if the platform fails to authenticate, we must back down */
if (content === "auth_error") {
deauthenticate();
return;
}
/* push the message to the browser */
if (win) emit("new-message", message);
/* 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();
}
}

View file

@ -1,29 +0,0 @@
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);
}

View file

@ -9,8 +9,6 @@ interface Message {
interface ViewMessage extends Message {
child: string;
seen: boolean;
newMessage: boolean;
}
interface WindowState {
@ -26,11 +24,6 @@ interface Profile {
initials: string;
}
interface State {
profile: Profile | null;
messages
}
interface LoginPayload {
email: string;
password: string;