Skip to content

Commit

Permalink
Merge pull request #42 from berkmancenter/npiano/fix-timing-bug
Browse files Browse the repository at this point in the history
Npiano/fix timing bug
  • Loading branch information
NicholasPiano authored May 25, 2023
2 parents fde5d62 + d72cba0 commit 33124c7
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 49 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@heroicons/vue": "^1.0.5",
"axios": "^0.24.0",
"socket.io-client": "^4.4.0",
"uuid": "^9.0.0",
"vue": "^3.2.16",
"vue-cookie-next": "^1.3.0",
"vue-router": "4"
Expand All @@ -25,4 +26,4 @@
"tailwindcss": "^2.2.19",
"vite": "^2.6.4"
}
}
}
4 changes: 3 additions & 1 deletion src/components/Threads/EditThread.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@click.prevent="openModal"
title="Edit Thread"
class="border-2 border-gray-500 h-10 w-12 mt-4 ml-2 hover:bg-gray-200"
data-testid="edit-thread"
>
<PencilIcon
class="h-6 w-6 inline-block hover:text-black hover:fill-current rounded cursor-pointer"
Expand All @@ -25,13 +26,14 @@
type="checkbox"
id="lockThread"
class="cursor-pointer w-4 h-4 mr-2 align-middle"
data-testid="lock-thread"
/><label class="cursor-pointer font-semibold" for="lockThread"
>Lock Thread</label
>
</div>
<div class="text-red-500">{{ message }}</div>
<template v-slot:actions>
<button class="btn success" @click="processUpdate">Update</button>
<button class="btn success" @click="processUpdate" data-testid="update-thread">Update</button>
<button class="btn error" @click="closeModal">Cancel</button>
</template>
</Modal>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Threads/ThreadListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class="h-5 w-5 inline-block"
/>
</div>
<div class="col-span-1 font-semibold justify-self-end">
<div class="col-span-1 font-semibold justify-self-end" data-testid="unlock-thread">
<LockClosedIcon v-if="item.locked" class="h-4 w-4 inline-block" @click="unlockThread" />
</div>
<div class="col-span-3 font-semibold justify-self-end">
Expand Down
38 changes: 19 additions & 19 deletions src/plugins/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ axios.defaults.headers.common[
"Authorization"
] = `Bearer ${VueCookieNext.getCookie("access_token")}`;

axios.interceptors.response.use(
(response) => response,
(error) => {
const status = error.response ? error.response.status : null;
export function refreshToken() {
const refreshingCall = axios
.post(`${import.meta.env.VITE_API_SERVER_URL}/v1/auth/refresh-tokens`, {
refreshToken: VueCookieNext.getCookie("refresh_token"),
})
.then((res) => {
VueCookieNext.setCookie("access_token", res.data.access.token);
VueCookieNext.setCookie("refresh_token", res.data.refresh.token);

function refreshToken() {
const refreshingCall = axios
.post(`${import.meta.env.VITE_API_SERVER_URL}/v1/auth/refresh-tokens`, {
refreshToken: VueCookieNext.getCookie("refresh_token"),
})
.then((res) => {
VueCookieNext.setCookie("access_token", res.data.access.token);
VueCookieNext.setCookie("refresh_token", res.data.refresh.token);
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${res.data.access.token}`;

axios.defaults.headers.common[
"Authorization"
] = `Bearer ${res.data.access.token}`;
return Promise.resolve(true);
});

return Promise.resolve(true);
});
return refreshingCall;
}

return refreshingCall;
}
axios.interceptors.response.use(
(response) => response,
(error) => {
const status = error.response ? error.response.status : null;

if (
status === 401 &&
Expand Down
81 changes: 75 additions & 6 deletions src/service/socket.service.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { v4 as uuidv4 } from 'uuid';
import { io, Socket } from "socket.io-client";
import { VueCookieNext } from "vue-cookie-next";
import { refreshToken } from '../plugins/axios';

/**
* Singleton Socket Service class
*/
class SocketioService {
_requestCache = {};
_socketInstance = null;
constructor() {
if (!this._socketInstance) {
Expand All @@ -28,6 +33,9 @@ class SocketioService {
);
};
setupSocketConnection();

// set up cache
this._requestCache = {};
}
}

Expand All @@ -39,10 +47,20 @@ class SocketioService {
this._socketInstance.on("disconnect", onDisconnectHandler);
}

addMessageHandler(onMessageHandler) {
addMessageHandler(finalOnMessageHandler) {
const onMessageHandler = (data) => {
if (data.request && data.request in this._requestCache) {
const { resolve } = this._requestCache[data.request];
resolve(finalOnMessageHandler(data));
delete this._requestCache[data.request];
} else {
finalOnMessageHandler(data);
}
};

// New message bind
this._socketInstance.off("message:new", onMessageHandler);
this._socketInstance.on("message:new", onMessageHandler);
this._socketInstance.on("message:new", onMessageHandler);
}

addThreadHandler(onThreadHandler) {
Expand All @@ -63,6 +81,53 @@ class SocketioService {
this._socketInstance.on("vote:new", onVoteHandler);
}

async sendMessage(payload) {
const cacheWithMatchingPayload = Object.values(this._requestCache).find(
(x) => x.payload.message === payload.message
);

if (cacheWithMatchingPayload) {
console.debug(cacheWithMatchingPayload);

debugger;

return;
}

const request = uuidv4();

return new Promise((resolve, reject) => {
this._requestCache[request] = { resolve, reject, payload };
this._socketInstance.emit("message:create", { ...payload, request });
});
}

addErrorHandler() {
const onErrorHandler = async (data) => {
if (data.request) {
const { reject, resolve, payload } = this._requestCache[data.request];

delete this._requestCache[data.request];

if (data.error === "jwt expired") {
resolve();
await refreshToken();
await this.sendMessage({
...payload,
token: VueCookieNext.getCookie("access_token"),
});

} else {
reject(data.error);
}
}
};

// New error bind
this._socketInstance.off("error", onErrorHandler);
this._socketInstance.on("error", onErrorHandler);
}

disconnectTopic() {
this._socketInstance.emit("topic:disconnect");
this.disconnect();
Expand All @@ -80,17 +145,21 @@ class SocketioService {
}
}

sendMessage(payload) {
this._socketInstance.emit("message:create", payload);
}

joinThread(payload) {
this._socketInstance.emit("thread:join", payload);
}

joinTopic(payload) {
this._socketInstance.emit("topic:join", payload);
}

joinUser(payload) {
this._socketInstance.emit("user:join", payload);
}

onConnect(onConnectHandler) {
this._socketInstance.on("connect", onConnectHandler);
}
}

export default SocketioService;
62 changes: 41 additions & 21 deletions src/views/MessagesPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,20 @@
@keypress="watchTagging"
@keydown.enter.prevent="sendMessage"
class="w-full block border-2 text-sm px-1 h-20 mt-4"
:class="shouldDisplayMessageBoxLocked ? 'border-red-500' : 'border-gray-500'"
:class="(shouldDisplayMessageBoxLocked || shouldDisplayUnableToSendMessage) ? 'border-red-500' : 'border-gray-500'"
placeholder="Message (hit enter to send)"
data-testid="message-text-area"
>
</textarea>
<span v-if="shouldDisplayMessageBoxLocked" class="text-red-500 text-sm font-bold"
>This thread is now locked. Messages cannot be sent until it is unlocked by the thread creator.</span
>
<span
v-if="shouldDisplayUnableToSendMessage && !shouldDisplayMessageBoxLocked"
class="text-red-500 text-sm font-bold"
>
Unable to send message. Please try again later.
</span>
<PromptDirtyDraft :show="prompt" @response="response" />
</template>

Expand Down Expand Up @@ -116,6 +123,7 @@ const searchTag = ref("");
const goodReputation = ref(false);
const wsInstance = reactive({});
const shouldDisplayMessageBoxLocked = ref(false);
const shouldDisplayUnableToSendMessage = ref(false);
/**
* Dialog feature
Expand Down Expand Up @@ -246,26 +254,22 @@ function parseJwt (token) {
async function sendMessage() {
if (message.value.trim().length > 0 && !getActiveThread.value?.locked && !pseudonymMismatch.value) {
const accessToken = VueCookieNext.getCookie("access_token");
const decodedAccessToken = parseJwt(accessToken);
const expiresAt = new Date(decodedAccessToken.exp * 1000);
const oneMinuteFromNow = new Date();
oneMinuteFromNow.setMinutes(oneMinuteFromNow.getMinutes() + 1);
if (expiresAt < oneMinuteFromNow) {
await loadUser();
try {
await wsInstance.value.sendMessage({
message: {
body: message.value,
thread: route.params.threadId,
},
userId: getId.value,
token: VueCookieNext.getCookie("access_token"),
});
shouldDisplayUnableToSendMessage.value = false;
message.value = "";
scrollToBottom();
} catch (error) {
shouldDisplayUnableToSendMessage.value = true;
}
wsInstance.value.sendMessage({
message: {
body: message.value,
thread: route.params.threadId,
},
token: VueCookieNext.getCookie("access_token"),
});
message.value = "";
scrollToBottom();
}
}
Expand Down Expand Up @@ -310,6 +314,15 @@ function joinThread(threadId) {
}
}
function joinUser() {
if (getLoggedInStatus.value) {
wsInstance.value.joinUser({
userId: getId.value,
token: VueCookieNext.getCookie("access_token"),
});
}
}
/**
* Handle received message
*/
Expand Down Expand Up @@ -403,9 +416,16 @@ watch(
* initialization and disconnection
*/
const reconnectSockets = () =>{
wsInstance.value.addErrorHandler();
wsInstance.value.addVotesHandler(onVoteHandler);
wsInstance.value.addMessageHandler(messageHandler);
joinThread(route.params.threadId);
wsInstance.value.onConnect(() => {
setTimeout(() => {
joinThread(route.params.threadId);
joinUser();
}, 100);
});
}
onMounted(async () => {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1681,6 +1681,11 @@ util-deprecate@^1.0.2:
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=

uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==

v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
Expand Down

0 comments on commit 33124c7

Please sign in to comment.