2020-10-05 20:36:03 +03:00
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.' ;
}
}
}
2020-11-16 21:16:25 +02:00
class GatewayConnection {
constructor ( ) {
this . isConnected = false ;
this . socket = null ;
2020-11-16 21:33:03 +02:00
// TODO: set up proper event listening and such, not this dumb crap
this . onDisconnect = ( ) => { }
this . onConnect = ( ) => { }
2020-11-16 21:16:25 +02:00
}
}
2020-11-17 17:20:33 +02:00
GatewayConnection . prototype . disconnect = function ( ) {
this . socket ? . disconnect ( ) ;
this . socket = null ;
this . isConnected = false ;
} ;
2020-11-16 21:16:25 +02:00
GatewayConnection . prototype . connect = function ( token ) {
console . log ( '[*] [gateway] [handshake] Trying to connect to gateway' ) ;
this . socket = io ( '/gateway' , {
query : {
token
} ,
transports : [ 'websocket' ]
} ) ;
2020-11-17 17:20:33 +02:00
this . socket . on ( 'connect' , ( ) => {
2020-11-17 18:13:07 +02:00
this . socket . once ( 'hello' , ( debugInfo ) => {
console . log ( '[*] [gateway] [handshake] Got hello from server, sending yoo...' , debugInfo ) ;
2020-11-17 17:20:33 +02:00
this . socket . emit ( 'yoo' ) ;
this . isConnected = true ;
2020-11-20 13:25:08 +02:00
this . debugInfo = debugInfo ;
2020-11-17 17:20:33 +02:00
this . onConnect ( 'CONNECT_RECEIVED_HELLO' ) ;
2020-11-17 18:13:07 +02:00
console . log ( '[*] [gateway] [handshake] Assuming that server received yoo and that connection is completed.' ) ;
2020-11-17 17:20:33 +02:00
} ) ;
2020-11-21 12:08:32 +02:00
} )
2020-11-16 21:16:25 +02:00
this . socket . on ( 'error' , ( e ) => {
console . log ( '[E] [gateway] Gateway error' , e ) ;
this . isConnected = false ;
2020-11-17 17:20:33 +02:00
this . socket = null ;
2020-11-16 21:33:03 +02:00
this . onDisconnect ( 'DISCONNECT_ERR' , e ) ;
2020-11-16 21:16:25 +02:00
} ) ;
this . socket . on ( 'disconnectNotification' , ( e ) => {
console . log ( '[E] [gateway] Received disconnect notfication' , e ) ;
this . isConnected = false ;
2020-11-17 17:20:33 +02:00
this . socket = null ;
2020-11-16 21:33:03 +02:00
this . onDisconnect ( 'DISCONNECT_NOTIF' , e ) ;
2020-11-16 21:16:25 +02:00
} ) ;
this . socket . on ( 'disconnect' , ( e ) => {
console . log ( '[E] [gateway] Disconnected from gateway: ' , e ) ;
this . isConnected = false ;
2020-11-16 21:33:03 +02:00
this . onDisconnect ( 'DISCONNECT' , e ) ;
2020-11-16 21:16:25 +02:00
} ) ;
} ;
2020-11-16 22:08:23 +02:00
GatewayConnection . prototype . sendMessage = function ( categoryId , content ) {
2020-11-20 21:35:43 +02:00
if ( ! this . isConnected ) return 1 ;
if ( content . length >= 2000 ) return 1 ;
2020-11-16 22:08:23 +02:00
2020-11-17 15:27:23 +02:00
this . socket . emit ( 'message' , {
category : {
_id : categoryId
} ,
content
} ) ;
2020-11-16 22:08:23 +02:00
} ;
GatewayConnection . prototype . subscribeToCategoryChat = function ( categoryId ) {
if ( ! this . isConnected ) return ;
2020-11-21 12:08:32 +02:00
const request = [ categoryId ] ;
console . log ( '[*] [gateway] Subscribing to channel(s)' , request ) ;
this . socket . emit ( 'subscribe' , request ) ;
2020-11-16 22:08:23 +02:00
} ;
2020-10-05 20:36:03 +03:00
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 ,
2020-11-17 11:28:11 +02:00
isCategory : false ,
isChatContext : false
2020-10-05 20:36:03 +03:00
} ,
posts : [ ]
} ,
cardButtons : [ ] ,
dialog : {
show : {
createPost : false ,
2020-11-20 13:25:08 +02:00
createCategory : false ,
debug : false
2020-10-05 20:36:03 +03:00
} ,
text : {
createPost : {
title : '' ,
body : ''
} ,
createCategory : {
title : ''
}
}
} ,
viewingProfile : {
show : false ,
_id : '' ,
username : '' ,
role : ''
2020-11-16 21:16:25 +02:00
} ,
2020-11-16 22:18:39 +02:00
gateway : new GatewayConnection ( ) ,
messages : {
2020-12-01 22:28:35 +02:00
'X' : [ { username : '__SYSTEM' , content : 'TEST MSG' } ]
2020-11-20 11:59:48 +02:00
} ,
2020-11-21 12:08:32 +02:00
userLists : {
2020-12-01 22:28:35 +02:00
'X' : [ { username : '__SYSTEM' , _id : 'INVALID_ID' } ]
2020-11-21 12:08:32 +02:00
} ,
2020-11-20 11:59:48 +02:00
message : {
typed : ''
2020-11-16 22:18:39 +02:00
}
2020-10-05 20:36:03 +03:00
} ,
mounted : async function ( ) {
const res = await fetch ( ` ${ window . location . origin } /api/v1/users/current/info ` , {
method : 'GET' ,
headers : {
'Accept' : 'application/json' ,
} ,
credentials : 'include'
} ) ;
2020-11-17 17:20:33 +02:00
if ( res . ok ) {
2020-10-05 20:36:03 +03:00
const json = await res . json ( ) ;
if ( json . user . permissionLevel >= 1 ) {
this . loggedInUser = json . user ;
this . showApp = true ;
2020-11-16 21:16:25 +02:00
this . performGatewayConnection ( ) ;
2020-10-05 20:36:03 +03:00
this . browseCategories ( ) ;
2020-11-20 22:05:55 +02:00
Notification . requestPermission ( ) ;
2020-10-05 20:36:03 +03:00
} 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 : {
2020-11-17 15:27:23 +02:00
// Gateway and chat
2020-11-16 21:16:25 +02:00
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
2020-11-16 21:33:03 +02:00
this . gateway . onDisconnect = ( e ) => {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'Connection lost.' ) ;
2020-11-16 22:08:23 +02:00
} ;
2020-11-16 21:16:25 +02:00
this . gateway . connect ( this . loggedInUser . token ) ;
2020-11-20 18:39:06 +02:00
this . gateway . socket . on ( 'message' , ( e ) => {
2020-11-21 17:40:35 +02:00
//console.log('[*] [gateway] Message received', e);
2020-11-20 18:39:06 +02:00
this . processMessage ( e ) ;
} ) ;
2020-11-21 12:08:32 +02:00
this . gateway . socket . on ( 'clientListUpdate' , ( e ) => {
console . log ( '[*] [gateway] Client list update' , e ) ;
this . processUserListUpdate ( e ) ;
} ) ;
2020-12-01 22:28:35 +02:00
this . gateway . socket . on ( 'refreshClient' , ( e ) => {
console . log ( '[*] [gateway] Gateway requested refresh' , e ) ;
2020-12-02 23:44:59 +02:00
this . gateway . disconnect ( ) ;
this . messages = { } ;
this . userLists = { } ;
this . message . typed = '' ;
2020-12-01 22:28:35 +02:00
if ( e . reason === 'exit' ) {
this . showApp = false ;
this . okNotification ( 'The server has exited. Sit tight!' ) ;
} else if ( e . reason === 'upd' ) {
this . showApp = false ;
this . okNotification ( 'An update has just rolled out! To ensure everything runs smoothly, you need to refresh the page!' ) ;
} else {
this . showApp = false ;
this . okNotification ( 'Sorry, but something happened and a refresh is required to keep using the app!' ) ;
}
this . snackbarEditButton ( 'Refresh' , ( ) => {
window . location . reload ( ) ;
} ) ;
} ) ;
2020-11-16 21:16:25 +02:00
} ,
2020-12-05 19:10:11 +02:00
shouldMergeMessage : function ( messageObject , categoryMessageList ) {
const lastMessageIndex = categoryMessageList . length - 1 ;
const lastMessage = categoryMessageList [ lastMessageIndex ] ;
if ( ! lastMessage ) return ;
if ( lastMessage . author . _id === messageObject . author . _id ) {
if ( lastMessage . nickAuthor && messageObject . nickAuthor && lastMessage . nickAuthor . username === messageObject . nickAuthor . username ) {
return true ;
2020-12-06 15:18:32 +02:00
} else {
return false ;
}
2020-12-05 19:10:11 +02:00
}
2020-12-05 23:23:50 +02:00
if ( lastMessage . author . _id === messageObject . author . _id ) return true ;
2020-12-05 19:10:11 +02:00
} ,
2020-11-17 15:27:23 +02:00
processMessage : async function ( messageObject ) {
if ( ! this . messages [ messageObject . category . _id ] ) this . $set ( this . messages , messageObject . category . _id , [ ] ) ;
2020-12-03 19:39:40 +02:00
const categoryMessageList = this . messages [ messageObject . category . _id ] ;
const lastMessageIndex = categoryMessageList . length - 1 ;
2020-12-05 19:10:11 +02:00
if ( this . shouldMergeMessage ( messageObject , categoryMessageList ) ) {
categoryMessageList [ lastMessageIndex ] . content += ` \n ${ messageObject . content } ` ;
2020-12-03 19:39:40 +02:00
} else {
this . messages [ messageObject . category . _id ] . push ( messageObject ) ;
}
2020-11-20 21:26:41 +02:00
if ( messageObject . category . _id === this . selection . category . _id ) {
this . $nextTick ( ( ) => {
// TODO: When the user presses back, actually undo this scroll cause its annoying to scroll back up in the category list
const container = this . $el . querySelector ( '#posts-container' ) ;
container . scrollTop = container . scrollHeight ;
} ) ;
}
2020-11-17 15:27:23 +02:00
if ( messageObject . author . username !== this . loggedInUser . username && messageObject . category . _id !== this . selection . category . _id ) {
2020-11-20 22:10:42 +02:00
this . okNotification ( ` ${ messageObject . category . title } / ${ messageObject . author . username } : ${ messageObject . content } ` ) ;
}
if ( messageObject . author . username !== this . loggedInUser . username ) {
2020-11-20 22:05:55 +02:00
if ( Notification . permission === 'granted' ) {
2020-11-20 22:11:21 +02:00
try {
new Notification ( ` ${ messageObject . category . title } / ${ messageObject . author . username } ` , {
body : messageObject . content
} ) ;
} catch ( e ) {
console . log ( '[E] [chat] Failed to show notification' ) ;
}
2020-11-20 22:05:55 +02:00
}
2020-11-17 15:27:23 +02:00
}
2020-11-16 22:08:23 +02:00
} ,
2020-11-21 12:08:32 +02:00
processUserListUpdate : async function ( e ) {
const { category , clientList } = e ;
if ( ! this . userLists [ category . _id ] ) this . $set ( this . userLists , category . _id , [ ] ) ;
this . userLists [ category . _id ] = clientList ;
} ,
2020-11-17 15:27:23 +02:00
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 ;
} ,
2020-11-20 11:59:48 +02:00
sendCurrentMessage : async function ( ) {
2020-11-20 22:05:55 +02:00
const status = await this . gateway . sendMessage ( this . selection . category . _id , this . message . typed ) ;
2020-11-20 21:35:43 +02:00
if ( status === 1 ) {
this . okNotification ( 'Failed to send message!' ) ;
return ;
}
2020-11-20 11:59:48 +02:00
this . message . typed = '' ;
} ,
2020-11-17 15:27:23 +02:00
2020-11-20 13:25:08 +02:00
// Debug
toggleDebugDialog : async function ( ) {
this . dialog . show . debug = ! this . dialog . show . debug ;
} ,
debugDump : async function ( ) {
console . log ( '[DEBUG DUMP] [gateway]' , this . gateway ) ;
console . log ( '[DEBUG DUMP] [loggedInUser] (this contains sensitive information about the current logged in user, do not leak it to other people lol)' , this . loggedInUser ) ;
} ,
2020-11-17 15:27:23 +02:00
// 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'
} ) ;
2020-11-17 17:20:33 +02:00
if ( res . ok ) {
2020-11-17 15:27:23 +02:00
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 ;
2020-11-20 21:26:41 +02:00
this . selection . category . _id = '__CATEGORY_LIST' ;
2020-11-17 15:27:23 +02:00
this . selection . posts = [ ] ;
this . cardButtons = [ ] ;
2020-11-21 13:01:02 +02:00
this . button ( 'chat' , ( post ) => {
2020-11-17 15:27:23 +02:00
if ( post . _id ) {
this . openChatForCategory ( post . _id ) ;
}
} ) ;
2020-11-21 13:01:02 +02:00
this . button ( 'topic' , ( post ) => {
2020-11-17 15:27:23 +02:00
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 {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'Failed to fetch category list' ) ;
2020-11-17 15:27:23 +02:00
}
} ,
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'
} ) ;
2020-11-17 17:20:33 +02:00
if ( res . ok ) {
2020-11-17 15:27:23 +02:00
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 {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'Failed to fetch category' ) ;
2020-11-17 15:27:23 +02:00
}
2020-10-05 20:36:03 +03:00
} ,
refresh : function ( ) {
if ( this . selection . category . title === 'categories' && this . selection . category . isCategory === false ) {
this . browseCategories ( ) ;
} else {
this . browse ( this . selection . category ) ;
}
} ,
2020-11-17 15:27:23 +02:00
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 = [ ] ;
} ,
2020-10-05 20:36:03 +03:00
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'
} ) ;
2020-11-17 17:20:33 +02:00
if ( res . ok ) {
2020-10-05 20:36:03 +03:00
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 {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'Failed to fetch user data' ) ;
2020-10-05 20:36:03 +03:00
}
} ,
2020-11-17 15:27:23 +02:00
// Content creation
2020-10-05 20:36:03 +03:00
showCreatePostDialog : function ( ) {
if ( ! this . selection . category . isCategory ) {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'You are not in a category' ) ;
2020-10-05 20:36:03 +03:00
return ;
}
this . dialog . show . createPost = true ;
} ,
createPost : async function ( ) {
if ( ! this . selection . category . isCategory ) {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'You are not in a category' ) ;
2020-10-05 20:36:03 +03:00
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
} )
} ) ;
2020-11-17 17:20:33 +02:00
if ( res . ok ) {
2020-11-20 13:25:08 +02:00
this . okNotification ( 'Successfully created post' ) ;
this . dialog . show . createPost = false ;
this . browse ( this . selection . category ) ;
return ;
} else {
2020-10-05 20:36:03 +03:00
if ( res . status === 401 || res . status === 403 ) {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'You are not allowed to do that' ) ;
2020-10-05 20:36:03 +03:00
return ;
}
if ( res . status === 429 ) {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'Chill! You are posting too much!' ) ;
2020-10-05 20:36:03 +03:00
return ;
}
const json = await res . json ( ) ;
2020-11-17 17:20:33 +02:00
this . okNotification ( getCreatePostError ( json ) ) ;
2020-10-05 20:36:03 +03:00
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
} )
} ) ;
2020-11-17 17:20:33 +02:00
if ( res . ok ) {
2020-11-20 13:25:08 +02:00
this . okNotification ( 'Successfully created category' ) ;
this . dialog . show . createCategory = false ;
this . browseCategories ( ) ;
return ;
} else {
2020-10-05 20:36:03 +03:00
if ( res . status === 401 || res . status === 403 ) {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'You are not allowed to do that' ) ;
2020-10-05 20:36:03 +03:00
return ;
}
if ( res . status === 429 ) {
2020-11-17 17:20:33 +02:00
this . okNotification ( 'Chill! You are posting too much!' ) ;
2020-10-05 20:36:03 +03:00
return ;
}
const json = await res . json ( ) ;
2020-11-17 17:20:33 +02:00
this . okNotification ( getCreateCategoryError ( json ) ) ;
2020-10-05 20:36:03 +03:00
return ;
}
} ,
2020-11-17 15:27:23 +02:00
// Navigation
navigateToAccountManager ( ) {
window . location . href = ` ${ window . location . origin } /auth.html ` ;
} ,
2020-10-05 20:36:03 +03:00
2020-11-17 15:27:23 +02:00
// Snackbar
snackbarButtonAction : function ( ) {
this . showSnackbarNotification = false ;
2020-11-16 21:16:25 +02:00
} ,
2020-11-17 15:27:23 +02:00
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 ;
2020-10-05 20:36:03 +03:00
} ,
2020-11-17 17:20:33 +02:00
okNotification : function ( text ) {
this . resetSnackbarButton ( ) ;
this . notification ( text ) ;
}
2020-10-05 20:36:03 +03:00
}
2020-12-06 15:18:32 +02:00
} ) ;