messenger v69
This commit is contained in:
parent
888651b4d7
commit
aeb212b5c9
10 changed files with 172 additions and 121 deletions
|
|
@ -32,6 +32,7 @@
|
|||
"mitt": "^2.1.0",
|
||||
"naudiodon": "^2.3.2",
|
||||
"node-record-lpcm16": "^1.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.0.0-0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuex": "^4.0.0-0",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ let record = false;
|
|||
const audioContainer = {
|
||||
input: '',
|
||||
}
|
||||
const encoding = "base64";
|
||||
const encoding = "hex";
|
||||
const audioOptions = {
|
||||
channelCount: 1,
|
||||
sampleFormat: 16,
|
||||
|
|
@ -22,10 +22,13 @@ const audioOptions = {
|
|||
}
|
||||
|
||||
// callback run on space bar key up and down.
|
||||
const updateRecorder = (_event, payload: boolean): void => {
|
||||
record = payload;
|
||||
const updateRecorder = (_event, payload: {
|
||||
isRecording: boolean;
|
||||
uid: string | null;
|
||||
}): void => {
|
||||
record = payload.isRecording;
|
||||
if (!record) {
|
||||
sendAudio(Buffer.from(audioContainer.input, 'base64'));
|
||||
if (payload.uid) sendAudio(audioContainer.input, payload.uid);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,26 +4,9 @@ import WebSocket from 'ws';
|
|||
import { backgroundMitt } from './emitter';
|
||||
import { ipcMain } from "electron";
|
||||
import { play } from './audio';
|
||||
|
||||
interface RenderMessage {
|
||||
content: {
|
||||
text: string;
|
||||
audio: string;
|
||||
};
|
||||
context: string;
|
||||
modifier: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface UserCreds {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface TextMessage {
|
||||
audio: 0;
|
||||
content: string;
|
||||
}
|
||||
import { UserCreds, ClientMessage } from "@/types/message/index";
|
||||
import { clientMessage } from '@/modules/message';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const ip = 'ws://127.0.0.1';
|
||||
const port = 8760;
|
||||
|
|
@ -32,8 +15,7 @@ let socket: WebSocket;
|
|||
let success = false;
|
||||
let auth = false;
|
||||
|
||||
|
||||
const authSession = async (_event, payload: string | UserCreds | null): Promise<string> => {
|
||||
const onAuthSession = async (_event, payload: string | UserCreds | null): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
console.log('authenticating...');
|
||||
|
|
@ -61,18 +43,7 @@ const authSession = async (_event, payload: string | UserCreds | null): Promise<
|
|||
});
|
||||
};
|
||||
|
||||
// function parseHexString(str: string) {
|
||||
// var result = [];
|
||||
// while (str.length >= 8) {
|
||||
// result.push(parseInt(str.substring(0, 8), 16));
|
||||
|
||||
// str = str.substring(8, str.length);
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
const receiveMessage = (message: string): void => {
|
||||
const onMessage = (message: string): void => {
|
||||
|
||||
while (!auth) {
|
||||
backgroundMitt.emit('auth-res', message);
|
||||
|
|
@ -80,34 +51,34 @@ const receiveMessage = (message: string): void => {
|
|||
}
|
||||
|
||||
// Parse the message.
|
||||
const parsed: RenderMessage = JSON.parse(message);
|
||||
console.log('message received', parsed.content.text);
|
||||
const m = JSON.parse(message);
|
||||
|
||||
// If there is audio, play it.
|
||||
if (parsed.content.audio) {
|
||||
const audioBytes = Buffer.from(parsed.content.audio, 'hex');
|
||||
play(audioBytes);
|
||||
// check to see if this is a Render Message.
|
||||
if (m.content) {
|
||||
// If there is audio, play it.
|
||||
if (m.content.audio) {
|
||||
const audioBytes = Buffer.from(m.content.audio as string, 'hex');
|
||||
play(audioBytes);
|
||||
}
|
||||
|
||||
// if there is no unique id, add it.
|
||||
if(!m.uid) m.uid = uuidv4();
|
||||
}
|
||||
|
||||
backgroundMitt.emit('ipc-renderer', {
|
||||
endpoint: 'render-message',
|
||||
message: parsed
|
||||
message: m
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const sendMessage = (_event, payload: TextMessage): void => {
|
||||
console.log('sending message')
|
||||
const onClientMessage = (_event, payload: ClientMessage): void => {
|
||||
console.log('sending message');
|
||||
socket.send(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
export const sendAudio = (content: Buffer) => {
|
||||
console.log('sending audio')
|
||||
socket.send(content);
|
||||
}
|
||||
|
||||
// Run every time we want to connect to backend.
|
||||
export const initSession = () => {
|
||||
export function initSession(): void {
|
||||
|
||||
// Close existing sockets.
|
||||
if (socket) {
|
||||
|
|
@ -123,11 +94,11 @@ export const initSession = () => {
|
|||
|
||||
// handle renderer auth-token event
|
||||
ipcMain.removeHandler('auth-session'); // avoid setting duplicate handlers
|
||||
ipcMain.handle('auth-session', authSession);
|
||||
ipcMain.handle('auth-session', onAuthSession);
|
||||
|
||||
// handle user message event
|
||||
ipcMain.removeAllListeners('message');
|
||||
ipcMain.on('message', sendMessage);
|
||||
ipcMain.removeAllListeners('client-message');
|
||||
ipcMain.on('client-message', onClientMessage);
|
||||
|
||||
// Connect to the backend.
|
||||
socket.on('open', () => {
|
||||
|
|
@ -162,6 +133,10 @@ export const initSession = () => {
|
|||
initSession();
|
||||
});
|
||||
|
||||
socket.on("message", receiveMessage);
|
||||
};
|
||||
socket.on("message", onMessage);
|
||||
}
|
||||
|
||||
export function sendAudio(content: string, uid: string): void {
|
||||
const m = clientMessage("", content, uid);
|
||||
socket.send(JSON.stringify(m));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
* Controls which input to visualize
|
||||
* Will visualize text input by default as user types or
|
||||
* if user holds space bar down, input audio stream
|
||||
* will be visualized & recorded
|
||||
* Main logic used by inputItem component.
|
||||
* Will visualize text input by default as user types.
|
||||
* If user holds space bar down, input audio stream will
|
||||
* be recorded and sent to the backend for analysis.
|
||||
*/
|
||||
|
||||
import { ref, watch, onUnmounted, onMounted } from "vue";
|
||||
|
|
@ -10,8 +10,13 @@ import useMitt from "@/modules/mitt";
|
|||
import { useIpc } from '@/modules/ipc';
|
||||
import keyboardNameMap from "./keyBoardMaps/keyboardNameMap";
|
||||
import keyboardCharMap from "./keyBoardMaps/keyboardCharMap";
|
||||
import { RenderMessage, ClientMessage } from "@/types/message/index";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {clientMessage, renderMessage} from '@/modules/message';
|
||||
|
||||
let textInput: HTMLInputElement | null;
|
||||
let m: RenderMessage | ClientMessage;
|
||||
let uid: string;
|
||||
|
||||
export default function useInputController() {
|
||||
|
||||
|
|
@ -21,19 +26,28 @@ export default function useInputController() {
|
|||
const isTyping = ref(false);
|
||||
const input = ref("");
|
||||
|
||||
// send message on ENTER
|
||||
// send message on ENTER.
|
||||
const onEnter = () => {
|
||||
if (input.value) {
|
||||
// remove first character of input.
|
||||
const text = input.value.substring(1);
|
||||
|
||||
const message = {
|
||||
audio: 0,
|
||||
text: input.value
|
||||
};
|
||||
uid = uuidv4();
|
||||
|
||||
// trigger send animation
|
||||
emitter.emit("animate-send");
|
||||
m = renderMessage(text, false, "sf", uid);
|
||||
|
||||
// send message to backend
|
||||
if (input.value) post('message', message);
|
||||
// render user message.
|
||||
emitter.emit("self-message", m);
|
||||
|
||||
m = clientMessage(m.content.text, m.content.audio, uid);
|
||||
|
||||
// send message to backend.
|
||||
post('client-message', m);
|
||||
|
||||
// reset text input.
|
||||
if (textInput) textInput.value = '';
|
||||
input.value = " ";
|
||||
}
|
||||
}
|
||||
|
||||
// update input on keydown event & listen for special commands.
|
||||
|
|
@ -59,8 +73,6 @@ export default function useInputController() {
|
|||
switch (cmd) {
|
||||
case "ENTER":
|
||||
onEnter();
|
||||
if (textInput) textInput.value = ''
|
||||
input.value = " ";
|
||||
break;
|
||||
case "BACK_SPACE":
|
||||
input.value = input.value.slice(0, -1);
|
||||
|
|
@ -74,12 +86,24 @@ export default function useInputController() {
|
|||
}
|
||||
}
|
||||
|
||||
// stops stream recording on space key up event.
|
||||
// stop stream recording and render audio message on space key up event.
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === " " && isRecording.value) {
|
||||
|
||||
isRecording.value = false;
|
||||
post('update-recorder', isRecording.value);
|
||||
|
||||
uid = uuidv4();
|
||||
|
||||
// render audio message.
|
||||
m = renderMessage("", false, "sf", uid);
|
||||
emitter.emit("self-message", m);
|
||||
|
||||
// stop stream recording.
|
||||
post('update-recorder', {
|
||||
isRecording: isRecording.value,
|
||||
uid
|
||||
});
|
||||
|
||||
input.value = ''
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +131,10 @@ export default function useInputController() {
|
|||
if (input === " " && isRecording.value === false) {
|
||||
isTyping.value = false;
|
||||
isRecording.value = true;
|
||||
post('update-recorder', isRecording.value);
|
||||
post('update-recorder', {
|
||||
isRecording: isRecording.value,
|
||||
uid: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
isTyping.value = true;
|
||||
|
|
|
|||
21
src/modules/message.ts
Normal file
21
src/modules/message.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { RenderMessage, ClientMessage } from "@/types/message/index";
|
||||
|
||||
export const renderMessage = (text: string, audio: string | boolean, modifier: string, uid: string): RenderMessage => (
|
||||
{
|
||||
content: {
|
||||
audio,
|
||||
text
|
||||
},
|
||||
context: "",
|
||||
modifier,
|
||||
uid
|
||||
}
|
||||
)
|
||||
|
||||
export const clientMessage = (text: string, audio: string | boolean, uid: string): ClientMessage => (
|
||||
{
|
||||
audio,
|
||||
text,
|
||||
uid
|
||||
}
|
||||
)
|
||||
|
|
@ -25,8 +25,8 @@ process.once("loaded", () => {
|
|||
ipcRenderer.send("update-recorder", message.content);
|
||||
}
|
||||
|
||||
if (message.endpoint === "message") {
|
||||
ipcRenderer.send("message", message.content);
|
||||
if (message.endpoint === "client-message") {
|
||||
ipcRenderer.send("client-message", message.content);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,21 +35,21 @@ const router = createRouter({
|
|||
});
|
||||
|
||||
// route auth check
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// const { accessToken } = useAuth();
|
||||
//
|
||||
// // Not logged into a guarded route?
|
||||
// if (to.meta.requiresAuth && !accessToken.value) {
|
||||
// next({ name: 'login' })
|
||||
// }
|
||||
//
|
||||
// // Logged in for an auth route
|
||||
// else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
|
||||
// next({ name: 'home' });
|
||||
// }
|
||||
//
|
||||
// // Carry On...
|
||||
// else next();
|
||||
// })
|
||||
router.beforeEach((to, from, next) => {
|
||||
const { accessToken } = useAuth();
|
||||
|
||||
// Not logged into a guarded route?
|
||||
if (to.meta.requiresAuth && !accessToken.value) {
|
||||
next({ name: 'login' })
|
||||
}
|
||||
|
||||
// Logged in for an auth route
|
||||
else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
|
||||
next({ name: 'home' });
|
||||
}
|
||||
|
||||
// Carry On...
|
||||
else next();
|
||||
})
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
export interface Message {
|
||||
export interface UserCreds {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RenderMessage {
|
||||
content: {
|
||||
text: string;
|
||||
audio: boolean | Buffer;
|
||||
audio: boolean | string;
|
||||
};
|
||||
context: string;
|
||||
modifier: string;
|
||||
id: string;
|
||||
uid: string;
|
||||
}
|
||||
|
||||
interface MessageTransform {
|
||||
initialTranslate: string;
|
||||
finalTranslate: string;
|
||||
export interface ClientMessage {
|
||||
text: string;
|
||||
audio: boolean | string;
|
||||
uid: string;
|
||||
}
|
||||
|
||||
export interface MessageAnimeFunc {
|
||||
(message: SVGElement, transform: MessageTransform, scrollHeight?: number | undefined, targetList?: NodeList | undefined): void;
|
||||
}
|
||||
export interface Annotation {
|
||||
text: string;
|
||||
context: string;
|
||||
uid: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@
|
|||
|
||||
<!-- List of message bubbles. -->
|
||||
<div id="messenger">
|
||||
<MessageItem v-for="message in messages" :message="message" :key="message.id" />
|
||||
<MessageItem v-for="message in messages" :message="message[1]" :key="message[0]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, onMounted, onUnmounted } from "vue";
|
||||
import { Message } from "@/types/message/index";
|
||||
import { RenderMessage , Annotation } from "@/types/message/index";
|
||||
import MessageItem from "@/components/messageItem.vue";
|
||||
import InputItem from "@/components/inputItem/inputItem.vue";
|
||||
import useMitt from "@/modules/mitt";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Messenger",
|
||||
|
|
@ -22,32 +23,42 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
setup() {
|
||||
const { emitter } = useMitt();
|
||||
// List of messages in the view.
|
||||
// Oldest message first.
|
||||
const messages: Message[] = reactive([]);
|
||||
const messages_ref: any = {};
|
||||
const messages = reactive(new Map());
|
||||
|
||||
// Render a message.
|
||||
// Called on "enter" instead of on "recv message"
|
||||
const render = (message: Message) => {
|
||||
messages.push(message);
|
||||
messages_ref[message.id] = message;
|
||||
};
|
||||
let m: RenderMessage | Annotation;
|
||||
|
||||
// Annotate/update an existing message.
|
||||
const annotate = (message: Message) => {
|
||||
1 + 1;
|
||||
};
|
||||
const onRenderMessage = (event: any, payload: any) => {
|
||||
|
||||
// render message if there is content.
|
||||
if (payload.message.content) {
|
||||
m = payload.message as RenderMessage;
|
||||
|
||||
messages.set(m.uid, m);
|
||||
|
||||
} else {
|
||||
// must be an annotation, update messages.
|
||||
m = payload.message as Annotation;
|
||||
|
||||
if (messages.has(m.uid)) {
|
||||
messages.get(m.uid).context = m.context;
|
||||
messages.get(m.uid).content.text = m.text;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
emitter.on('self-message', (message) => messages.set(message.uid, message));
|
||||
// Listen for new messages to render.
|
||||
window.ipcRenderer.on("render-message", (event, payload) => {
|
||||
if (payload.message.content) render(payload.message);
|
||||
});
|
||||
window.ipcRenderer.on("render-message", onRenderMessage);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.ipcRenderer.removeAllListeners("render-message");
|
||||
emitter.all.clear();
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -12838,6 +12838,11 @@ uuid@^8.0.0:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
||||
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
v8-compile-cache@^2.0.3:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
|
||||
|
|
|
|||
Loading…
Reference in a new issue