Vue.use(VueMaterial.default); const getCreatePostError = (json) => { switch (json.message) { case 'ERROR_REQUEST_INVALID_DATA': { switch (json.errors[0].param) { case 'title': { return 'Invalid title. Must be between 3 and 32 characters.'; } case 'body': { return 'Invalid content. Must be between 3 and 1000 characters'; } case 'category': { return 'Invalid category. Something went wrong.'; } default: { return 'Invalid value sent to server. Something went wrong.'; } } } case 'ERROR_CATEGORY_NOT_FOUND': { return 'The category you tried to post to no longer exists.'; } case 'ERROR_ACCESS_DENIED': { return 'You are not allowed to perform this action.' } default: { return 'Unknown error. Something went wrong.'; } } } const getCreateCategoryError = (json) => { switch (json.message) { case 'ERROR_REQUEST_INVALID_DATA': { switch (json.errors[0].param) { case 'title': { return 'Invalid title. Title must be between 3 and 32 characters.'; } } } case 'ERROR_ACCESS_DENIED': { return 'You are not allowed to perform this action.' } default: { return 'Unknown error. Something went wrong.'; } } } class GatewayConnection { constructor() { this.isConnected = false; this.socket = null; // TODO: set up proper event listening and such, not this dumb crap this.onDisconnect = () => {} this.onConnect = () => {} } } GatewayConnection.prototype.connect = function(token) { console.log('[*] [gateway] [handshake] Trying to connect to gateway'); this.socket = io('/gateway', { query: { token }, transports: ['websocket'] }); this.socket.on('hello', () => { console.log('[*] [gateway] [handshake] Got hello from server, sending yoo...'); this.socket.emit('yoo'); this.onConnect('CONNECT_RECEIVED_HELLO'); }); this.socket.on('error', (e) => { console.log('[E] [gateway] Gateway error', e); this.isConnected = false; this.onDisconnect('DISCONNECT_ERR', e); }); this.socket.on('disconnectNotification', (e) => { console.log('[E] [gateway] Received disconnect notfication', e); this.isConnected = false; this.onDisconnect('DISCONNECT_NOTIF', e); }); this.socket.on('disconnect', (e) => { console.log('[E] [gateway] Disconnected from gateway: ', e); this.isConnected = false; this.onDisconnect('DISCONNECT', e); }); }; const app = new Vue({ el: '#app', data: { showSnackbarNotification: false, snackbarNotification: '', snackbarNotificationDuration: 999999, snackbarButtonText: 'Ok', loggedInUser: {}, showApp: false, menuVisible: false, selection: { category: { title: '', browsing: false, _id: undefined, isCategory: false }, posts: [] }, cardButtons: [], dialog: { show: { createPost: false, createCategory: false }, text: { createPost: { title: '', body: '' }, createCategory: { title: '' } } }, viewingProfile: { show: false, _id: '', username: '', role: '' }, gateway: new GatewayConnection() }, mounted: async function() { const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, { method: 'GET', headers: { 'Accept': 'application/json', }, credentials: 'include' }); if (res.status === 200) { const json = await res.json(); if (json.user.permissionLevel >= 1) { this.loggedInUser = json.user; this.showApp = true; this.performGatewayConnection(); this.browseCategories(); } else { this.showApp = false; this.snackbarEditButton('Manage', () => { window.location.href = `${window.location.origin}/auth.html`; this.resetSnackbarButton(); this.showSnackbarNotification = false; }); this.notification('Your account does not have the required permissions to enter this page'); } } else { this.showApp = false; this.snackbarEditButton('Manage', () => { window.location.href = `${window.location.origin}/auth.html`; this.resetSnackbarButton(); this.showSnackbarNotification = false; }); this.notification('You are not logged in or your session is invalid'); } }, 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; }, 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) => { this.resetSnackbarButton(); 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.connect(this.loggedInUser.token); }, button: function(text, click) { this.cardButtons.push({ text, click }); }, refresh: function() { if (this.selection.category.title === 'categories' && this.selection.category.isCategory === false) { this.browseCategories(); } else { this.browse(this.selection.category); } }, 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`, { method: 'GET', headers: { 'Accept': 'application/json', }, credentials: 'include' }); if (res.status === 200) { const json = await res.json(); this.viewingProfile.username = json.user.username; this.viewingProfile._id = json.user._id; this.viewingProfile.role = json.user.role; this.viewingProfile.show = true; } else { this.resetSnackbarButton(); this.notification('Failed to fetch user data'); } }, stopBrowsing: function() { this.selection.category = { title: '', browsing: false, _id: undefined, isCategory: false }; this.selection.posts = []; this.cardButtons = []; }, showCreatePostDialog: function() { if (!this.selection.category.isCategory) { this.resetSnackbarButton(); this.notification('You are not in a category'); return; } this.dialog.show.createPost = true; }, createPost: async function() { if (!this.selection.category.isCategory) { this.resetSnackbarButton(); this.notification('You are not in a category'); return; } const category = this.selection.category; const input = this.dialog.text.createPost; const res = await fetch(`${window.location.origin}/api/v1/content/post/create`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ category: category._id, title: input.title, body: input.body }) }); if (res.status !== 200) { if (res.status === 401 || res.status === 403) { this.resetSnackbarButton(); this.notification('You are not allowed to do that'); return; } if (res.status === 429) { this.resetSnackbarButton(); this.notification('Chill! You are posting too much!'); return; } const json = await res.json(); this.resetSnackbarButton(); this.notification(getCreatePostError(json)); return; } else { this.resetSnackbarButton(); this.notification('Successfully created post'); this.dialog.show.createPost = false; this.browse(this.selection.category); return; } }, createCategory: async function() { const input = this.dialog.text.createCategory; const res = await fetch(`${window.location.origin}/api/v1/content/category/create`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ title: input.title }) }); if (res.status !== 200) { if (res.status === 401 || res.status === 403) { this.resetSnackbarButton(); this.notification('You are not allowed to do that'); return; } if (res.status === 429) { this.resetSnackbarButton(); this.notification('Chill! You are posting too much!'); return; } const json = await res.json(); this.resetSnackbarButton(); this.notification(getCreateCategoryError(json)); return; } else { this.resetSnackbarButton(); this.notification('Successfully created category'); this.dialog.show.createCategory = false; this.browseCategories(); 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', }, 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.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'); } }, openChatForCategory: async function(categoryId) { }, 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.posts = []; this.cardButtons = []; this.button('Chat', (post) => { this.browse(post); }); 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'); } } } });