more changes
This commit is contained in:
parent
3b5da6124f
commit
9e8641b4de
11 changed files with 167 additions and 285 deletions
|
|
@ -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
36
src/composables/canvas.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
});
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
60
src/main.ts
60
src/main.ts
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
src/state.ts
29
src/state.ts
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue