improve message object structure, rearrange methods in app.js just for the code to look better and make sure the message list updates when a new message is sent

This commit is contained in:
hippoz 2020-11-17 15:27:23 +02:00
parent 89acdf6a3c
commit 667a843370
5 changed files with 172 additions and 135 deletions

View file

@ -1,9 +1,11 @@
const User = require('../../../models/User');
const secret = require('../../../secret');
const config = require('../../../config');
const Category = require('../../../models/Category');
const jwt = require('jsonwebtoken');
const siolib = require('socket.io')
const siolib = require('socket.io');
const uuid = require('uuid');
class GatewayServer {
constructor(httpServer) {
@ -68,24 +70,38 @@ GatewayServer.prototype.eventSetup = function() {
console.log(`[*] [gateway] [handshake] Got yoo from ${socket.username}, connection is finally completed!`);
socket.isConnected = true;
socket.on('message', ({ categoryId, content }) => {
// TODO: URGENT: Check if the category exists and if the user has access to it (access coming soon)
const msgConfig = {
socket.on('message', ({ category, content }) => {
// TODO: When/if category permissions are added, check if the user has permissions for that category
const categoryTitle = socket.joinedCategories[category._id];
if (!categoryTitle) return;
const messageObject = {
author: {
username: socket.username,
creatorId: socket.userId,
categoryId: categoryId,
content: content
_id: socket.userId
},
category: {
title: categoryTitle,
_id: category._id
},
content: content,
_id: uuid.v4()
};
socket.to(categoryId).emit('message', msgConfig);
socket.emit('message', msgConfig);
socket.to(category._id).emit('message', messageObject);
socket.emit('message', messageObject);
});
socket.on('subscribe', async (categories) => {
for (let v of categories) {
// TODO: URGENT: Check if the category exists and if the user has access to it (access coming soon)
// TODO: When/if category permissions are added, check if the user has permissions for that category
const category = await Category.findById(v);
if (category && category.title && category._id) {
if (!socket.joinedCategories) socket.joinedCategories = {};
socket.joinedCategories[v] = category.title;
socket.join(v);
}
}
});
});
});

View file

@ -109,10 +109,10 @@
<md-button v-for="button in cardButtons" v-bind:key="button.text" @click="button.click(post)">{{ button.text }}</md-button>
</md-card-actions>
</md-card>
<div v-for="post,k in messages[selection.category._id]" v-if="selection.category.isChatContext" :key="post.content + post.creatorId + k">
<div v-for="post,k in messages[selection.category._id]" v-if="selection.category.isChatContext" :key="post._id + post.author._id">
<md-card>
<md-card-header>
<a class="md-dense cursor md-title" v-on:click="viewProfile(post.creatorId)"><span>{{ post.username }}</span></a>
<a class="md-dense cursor md-title" v-on:click="viewProfile(post.author._id)"><span>{{ post.author.username }}</span></a>
</md-card-header>
<md-card-content v-html="post.content"></md-card-content>

View file

@ -100,7 +100,12 @@ GatewayConnection.prototype.connect = function(token) {
GatewayConnection.prototype.sendMessage = function(categoryId, content) {
if (!this.isConnected) return;
this.socket.emit('message', { categoryId, content });
this.socket.emit('message', {
category: {
_id: categoryId
},
content
});
};
GatewayConnection.prototype.subscribeToCategoryChat = function(categoryId) {
@ -193,29 +198,7 @@ const app = new Vue({
}
},
methods: {
navigateToAccountManager() {
window.location.href = `${window.location.origin}/auth.html`;
},
snackbarButtonAction: function() {
this.showSnackbarNotification = false;
},
snackbarButtonClick: function() {
this.snackbarButtonAction();
},
snackbarEditButton: function(buttonText="Ok", action) {
this.snackbarButtonText = buttonText;
this.snackbarButtonAction = action;
},
resetSnackbarButton: function() {
this.snackbarButtonText = 'Ok';
this.snackbarButtonAction = () => {
this.showSnackbarNotification = false;
};
},
notification: function(text) {
this.snackbarNotification = text;
this.showSnackbarNotification = true;
},
// Gateway and chat
performGatewayConnection: function() {
// TODO: again, the thing im doing with the token is not very secure, since its being sent by the current user info endpoint and is also being send through query parameters
this.gateway.onDisconnect = (e) => {
@ -223,21 +206,99 @@ const app = new Vue({
this.notification('ERROR: You have been disconnected from the gateway. Realtime features such as chat will not work and unexpected errors may occur.');
};
this.gateway.onConnect = () => {
this.gateway.socket.on('message', ({ username, categoryId, content, creatorId }) => {
this.showMessageNotification(categoryId, username, creatorId, content);
})
this.gateway.socket.on('message', this.processMessage);
};
this.gateway.connect(this.loggedInUser.token);
},
showMessageNotification: async function(categoryId, username, creatorId, content) {
if (!this.messages[categoryId]) this.messages[categoryId] = []
this.messages[categoryId].push({ username, content, creatorId });
processMessage: async function(messageObject) {
if (!this.messages[messageObject.category._id]) this.$set(this.messages, messageObject.category._id, []);
this.messages[messageObject.category._id].push(messageObject);
if (messageObject.author.username !== this.loggedInUser.username && messageObject.category._id !== this.selection.category._id) {
this.resetSnackbarButton();
this.notification(`${username}: ${content}`);
this.notification(`${messageObject.author.username}: ${messageObject.content}`);
}
},
button: function(text, click) {
this.cardButtons.push({ text, click });
openChatForCategory: async function(categoryId) {
this.gateway.subscribeToCategoryChat(categoryId);
this.selection.category.isChatContext = true;
this.selection.category.browsing = true;
this.selection.category.title = 'Chat';
this.selection.category._id = categoryId;
},
// Category and post browsing
browseCategories: async function() {
const res = await fetch(`${window.location.origin}/api/v1/content/category/list?count=50`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include'
});
if (res.status === 200) {
const json = await res.json();
this.selection.category.title = 'categories';
this.selection.category.browsing = true;
this.selection.category.isCategory = false;
this.selection.category.isChatContext = false;
this.selection.posts = [];
this.cardButtons = [];
this.button('Chat', (post) => {
if (post._id) {
this.openChatForCategory(post._id);
this.gateway.sendMessage(post._id, 'yoooo');
}
});
this.button('View', (post) => {
this.browse(post);
});
for (let i = 0; i < json.categories.length; i++) {
const v = json.categories[i];
this.selection.posts.push({ title: v.title, body: '', _id: v._id, creator: v.creator });
}
} else {
this.resetSnackbarButton();
this.notification('Failed to fetch category list');
}
},
browse: async function(category) {
const { _id, title } = category;
const res = await fetch(`${window.location.origin}/api/v1/content/category/${_id}/info`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include'
});
if (res.status === 200) {
const json = await res.json();
this.selection.category.title = title;
this.selection.category._id = _id;
this.selection.category.browsing = true;
this.selection.category.isCategory = true;
this.selection.category.isChatContext = false;
this.selection.posts = [];
this.cardButtons = [];
for (let i = 0; i < json.category.posts.length; i++) {
const v = json.category.posts[i];
this.selection.posts.push({ title: v.title, body: v.body, _id: v._id, creator: v.creator });
}
} else {
this.resetSnackbarButton();
this.notification('Failed to fetch category');
}
},
refresh: function() {
if (this.selection.category.title === 'categories' && this.selection.category.isCategory === false) {
@ -246,6 +307,20 @@ const app = new Vue({
this.browse(this.selection.category);
}
},
button: function(text, click) {
this.cardButtons.push({ text, click });
},
stopBrowsing: function() {
this.selection.category = {
title: '',
browsing: false,
_id: undefined,
isCategory: false,
isChatContext: false
};
this.selection.posts = [];
this.cardButtons = [];
},
viewProfile: async function(id) {
// TODO: this just returns the username for now
const res = await fetch(`${window.location.origin}/api/v1/users/user/${id}/info`, {
@ -267,16 +342,8 @@ const app = new Vue({
this.notification('Failed to fetch user data');
}
},
stopBrowsing: function() {
this.selection.category = {
title: '',
browsing: false,
_id: undefined,
isCategory: false
};
this.selection.posts = [];
this.cardButtons = [];
},
// Content creation
showCreatePostDialog: function() {
if (!this.selection.category.isCategory) {
this.resetSnackbarButton();
@ -373,84 +440,32 @@ const app = new Vue({
return;
}
},
browse: async function(category) {
const { _id, title } = category;
const res = await fetch(`${window.location.origin}/api/v1/content/category/${_id}/info`, {
method: 'GET',
headers: {
'Accept': 'application/json',
// Navigation
navigateToAccountManager() {
window.location.href = `${window.location.origin}/auth.html`;
},
credentials: 'include'
});
if (res.status === 200) {
const json = await res.json();
this.selection.category.title = title;
this.selection.category._id = _id;
this.selection.category.browsing = true;
this.selection.category.isCategory = true;
this.selection.category.isChatContext = false;
this.selection.posts = [];
this.cardButtons = [];
for (let i = 0; i < json.category.posts.length; i++) {
const v = json.category.posts[i];
this.selection.posts.push({ title: v.title, body: v.body, _id: v._id, creator: v.creator });
}
} else {
this.resetSnackbarButton();
this.notification('Failed to fetch category');
}
// Snackbar
snackbarButtonAction: function() {
this.showSnackbarNotification = false;
},
openChatForCategory: async function(categoryId) {
this.gateway.subscribeToCategoryChat(categoryId);
this.selection.category.isChatContext = true;
this.selection.category.browsing = true;
this.selection.category.title = 'Chat';
this.selection.category._id = categoryId;
snackbarButtonClick: function() {
this.snackbarButtonAction();
},
browseCategories: async function() {
const res = await fetch(`${window.location.origin}/api/v1/content/category/list?count=50`, {
method: 'GET',
headers: {
'Accept': 'application/json',
snackbarEditButton: function(buttonText="Ok", action) {
this.snackbarButtonText = buttonText;
this.snackbarButtonAction = action;
},
resetSnackbarButton: function() {
this.snackbarButtonText = 'Ok';
this.snackbarButtonAction = () => {
this.showSnackbarNotification = false;
};
},
notification: function(text) {
this.snackbarNotification = text;
this.showSnackbarNotification = true;
},
credentials: 'include'
});
if (res.status === 200) {
const json = await res.json();
this.selection.category.title = 'categories';
this.selection.category.browsing = true;
this.selection.category.isCategory = false;
this.selection.category.isChatContext = false;
this.selection.posts = [];
this.cardButtons = [];
this.button('Chat', (post) => {
if (post._id) {
this.openChatForCategory(post._id);
this.gateway.sendMessage(post._id, 'yoooo');
}
});
this.button('View', (post) => {
this.browse(post);
});
for (let i = 0; i < json.categories.length; i++) {
const v = json.categories[i];
this.selection.posts.push({ title: v.title, body: '', _id: v._id, creator: v.creator });
}
} else {
this.resetSnackbarButton();
this.notification('Failed to fetch category list');
}
}
}
});

5
package-lock.json generated
View file

@ -1202,6 +1202,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
},
"validator": {
"version": "13.1.1",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz",

View file

@ -18,6 +18,7 @@
"express-validator": "^6.6.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.10.0",
"socket.io": "^3.0.1"
"socket.io": "^3.0.1",
"uuid": "^8.3.1"
}
}