diff --git a/src/app/channel/connectedUser.js b/src/app/channel/connectedUser.js index 157cc49..b03c614 100644 --- a/src/app/channel/connectedUser.js +++ b/src/app/channel/connectedUser.js @@ -41,6 +41,8 @@ module.exports = class{ await this.sendSiteEmotes(); await this.sendChanEmotes(chanDB); await this.sendPersonalEmotes(userDB); + + //Send out used tokes await this.sendUsedTokes(userDB); //Tattoo hashed IP address to user account for seven days @@ -64,7 +66,12 @@ module.exports = class{ //at the end of the day there has to be some penance for decent multi-session handling on-top of a library that doesn't do it. //Having to crawl through these sockets is that. Because the other ways seem more gross somehow. emit(eventName, args){ - this.socketCrawl((socket)=>{socket.emit(eventName, args)}); + this.socketCrawl((socket)=>{ + //Ensure our socket is initialized + if(socket != null){ + socket.emit(eventName, args); + } + }); } //generic disconnect function, defaults to kick @@ -73,11 +80,33 @@ module.exports = class{ this.socketCrawl((socket)=>{socket.disconnect()}); } - async sendClientMetadata(){ + //This is the big first push upon connection + //It should only fire once, so things that only need to be sent once can be slapped into here + async sendClientMetadata(userDB, chanDB){ //Get flairList from DB and setup flairList array const flairListDB = await flairModel.find({}); var flairList = []; + //if we wherent handed a user document + if(userDB == null){ + //Pull it based on user name + userDB = await userModel.findOne({user: this.user}); + } + + //if we wherent handed a channel document + if(chanDB == null){ + //Pull it based on channel name + chanDB = await channelModel.findOne({name: this.channel.name}); + } + + //If our perm map is un-initiated + //can't set this in constructor easily since it's asyncornous + //need to wait for it to complete before sending this off, but shouldnt re-do the wait for later connections + if(this.permMap == null){ + //Grab perm map + this.permMap = await chanDB.getPermMapByUserDoc(userDB); + } + //Setup our userObj const userObj = { id: this.id, @@ -85,7 +114,11 @@ module.exports = class{ rank: this.rank, chanRank: this.chanRank, highLevel: this.highLevel, - flair: this.flair + permMap: { + site: Array.from(this.permMap.site), + chan: Array.from(this.permMap.chan), + }, + flair: this.flair, } //For each flair listed in the Database diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js index b147fe6..dbac81b 100644 --- a/src/schemas/channel/channelSchema.js +++ b/src/schemas/channel/channelSchema.js @@ -447,6 +447,17 @@ channelSchema.methods.getChannelRank = async function(user){ return await this.getChannelRankByUserDoc(userDB); } +channelSchema.methods.permCheck = async function (user, perm){ + //Set userDB to null if we wheren't passed a real user + if(user != null){ + var userDB = await userModel.findOne({user: user.user}); + }else{ + var userDB = null; + } + + return await this.permCheckByUserDoc(userDB, perm) +} + channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){ //Get site-wide rank as number, default to anon for anonymous users const rank = userDB ? permissionModel.rankToNum(userDB.rank) : permissionModel.rankToNum("anon"); @@ -464,15 +475,25 @@ channelSchema.methods.permCheckByUserDoc = async function(userDB, perm){ return (permCheck || overrideCheck); } -channelSchema.methods.permCheck = async function (user, perm){ - //Set userDB to null if we wheren't passed a real user - if(user != null){ - var userDB = await userModel.findOne({user: user.user}); - }else{ - var userDB = null; - } +channelSchema.methods.getPermMapByUserDoc = async function(userDB){ + //Grap site-wide permissions + const sitePerms = await permissionModel.getPerms(); + const siteMap = sitePerms.getPermMapByUserDoc(userDB); + //Pull chan permissions keys + let permTree = channelPermissionSchema.tree; + let permMap = new Map(); - return await this.permCheckByUserDoc(userDB, perm) + //For each object in the temporary permissions object + for(let perm of Object.keys(permTree)){ + //Check the current permission + permMap.set(perm, await this.permCheckByUserDoc(userDB, perm)); + } + + //return perm map + return { + site: siteMap.site, + chan: permMap + }; } channelSchema.methods.checkBanByUserDoc = async function(userDB){ diff --git a/src/schemas/permissionSchema.js b/src/schemas/permissionSchema.js index 619bff7..d1621f8 100644 --- a/src/schemas/permissionSchema.js +++ b/src/schemas/permissionSchema.js @@ -139,26 +139,8 @@ permissionSchema.statics.permCheck = async function(user, perm){ permissionSchema.statics.permCheckByUserDoc = async function(user, perm){ //Get permission list const perms = await this.getPerms(); - - //Set user to anon rank if no rank was found for the given user - if(user == null || user.rank == null){ - user ={ - rank: "anon" - }; - } - - //Check if this permission exists - if(perms[perm] != null){ - //if so get required rank as a number - requiredRank = this.rankToNum(perms[perm]) - //if so get user rank as a number - userRank = user ? this.rankToNum(user.rank) : 0; - //return whether or not the user is equal to or higher than the required rank for this permission - return (userRank >= requiredRank); - }else{ - //if not scream and shout - throw new Error(`Permission check '${perm}' not found!`); - } + //Call the perm check method + return perms.permCheckByUserDoc(user, perm); } permissionSchema.statics.overrideCheck = async function(user, perm){ @@ -175,27 +157,9 @@ permissionSchema.statics.overrideCheck = async function(user, perm){ permissionSchema.statics.overrideCheckByUserDoc = async function(user, perm){ //Get permission list - const perms = (await this.getPerms()).channelOverrides; - - //Set user to anon rank if no rank was found for the given user - if(user == null || user.rank == null){ - user ={ - rank: "anon" - }; - } - - //Check if this permission exists - if(perms[perm] != null){ - //if so get required rank as a number - requiredRank = this.rankToNum(perms[perm]) - //if so get user rank as a number - userRank = user ? this.rankToNum(user.rank) : 0; - //return whether or not the user is equal to or higher than the required rank for this permission - return (userRank >= requiredRank); - }else{ - //if not scream and shout - throw new Error(`Permission check '${perm}' not found!`); - } + const perms = await this.getPerms(); + //Call the perm check method + return perms.overrideCheckByUserDoc(user, perm); } //Middleware for rank checks @@ -211,4 +175,76 @@ permissionSchema.statics.reqPermCheck = function(perm){ } } +//methods +//these are good to have even for single-doc collections since we can loop through them without finding them in the database each time +permissionSchema.methods.permCheckByUserDoc = function(userDB, perm){ + //Set user to anon rank if no rank was found for the given user + if(userDB == null || userDB.rank == null){ + userDB ={ + rank: "anon" + }; + } + + //Check if this permission exists + if(this[perm] != null){ + //if so get required rank as a number + requiredRank = this.model().rankToNum(this[perm]) + //if so get user rank as a number + userRank = userDB ? this.model().rankToNum(userDB.rank) : 0; + //return whether or not the user is equal to or higher than the required rank for this permission + return (userRank >= requiredRank); + }else{ + //if not scream and shout + throw new Error(`Permission check '${perm}' not found!`); + } +} + +permissionSchema.methods.overrideCheckByUserDoc = function(userDB, perm){ + //Set user to anon rank if no rank was found for the given user + if(userDB == null || userDB.rank == null){ + userDB ={ + rank: "anon" + }; + } + + //Check if this permission exists + if(this.channelOverrides[perm] != null){ + //if so get required rank as a number + requiredRank = this.model().rankToNum(this.channelOverrides[perm]) + //if so get user rank as a number + userRank = userDB ? this.model().rankToNum(userDB.rank) : 0; + //return whether or not the user is equal to or higher than the required rank for this permission + return (userRank >= requiredRank); + }else{ + //if not scream and shout + throw new Error(`Permission check '${perm}' not found!`); + } +} + +permissionSchema.methods.getPermMapByUserDoc = function(userDB){ + //Pull permissions keys + let permTree = this.schema.tree; + let overrideTree = channelPermissionSchema.tree; + let permMap = new Map(); + let overrideMap = new Map(); + + //For each object in the temporary permissions object + for(let perm of Object.keys(permTree)){ + //Check the current permission + permMap.set(perm, this.permCheckByUserDoc(userDB, perm)); + } + + //For each object in the temporary permissions object + for(let perm of Object.keys(overrideTree)){ + //Check the current permission + overrideMap.set(perm, this.overrideCheckByUserDoc(userDB, perm)); + } + + //return the auto-generated schema + return { + site: permMap, + channelOverrides: overrideMap + } +} + module.exports = mongoose.model("permissions", permissionSchema); \ No newline at end of file diff --git a/src/validators/permissionsValidator.js b/src/validators/permissionsValidator.js index 757035e..c304406 100644 --- a/src/validators/permissionsValidator.js +++ b/src/validators/permissionsValidator.js @@ -30,18 +30,18 @@ module.exports.isRank = function(value){ //Internal functions for loading validator schema through the database so we only have to maintain permissions in one place function loadPermValidatorSchema(){ //Pull permissions keys - var tempPerms = permissionModel.schema.tree; + var permTree = permissionModel.schema.tree; //Create empty object for schema var schema = {}; //Scrape out gunk - delete tempPerms.id; - delete tempPerms._id; - delete tempPerms.__v; - delete tempPerms.channelOverrides; + delete permTree.id; + delete permTree._id; + delete permTree.__v; + delete permTree.channelOverrides; //For each object in the temporary permissions object - Object.keys(tempPerms).forEach((key) => { + for(let key of Object.keys(permTree)){ //Create an entry in the validation schema for the current permission schema[`permissionsMap.${key}`] = { optional: true, @@ -49,7 +49,7 @@ function loadPermValidatorSchema(){ options: module.exports.isRank } } - }); + } //return the auto-generated schema return schema; @@ -57,17 +57,17 @@ function loadPermValidatorSchema(){ function loadChanPermValidatorSchema(){ //Pull permissions keys - var tempPerms = channelPermissionSchema.tree; + var permTree = channelPermissionSchema.tree; //Create empty object for schema var schema = {}; //Scrape out gunk - delete tempPerms.id; - delete tempPerms._id; - delete tempPerms.__v; + delete permTree.id; + delete permTree._id; + delete permTree.__v; //For each object in the temporary permissions object - Object.keys(tempPerms).forEach((key) => { + for(let key of Object.keys(permTree)){ //Create an entry in the validation schema for the current permission schema[`channelPermissionsMap.${key}`] = { optional: true, @@ -75,7 +75,7 @@ function loadChanPermValidatorSchema(){ options: module.exports.isRank } } - }); + } //return the schema return schema; diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 42398bf..c2d951c 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -58,12 +58,15 @@ class channel{ } handleClientInfo(data){ - this.user = { - id: data.user.id, - name: data.user.name, - rank: data.user.rank - } + //Ingest user data + this.user = data.user; + //Re-hydrate permission maps + this.user.permMap.site = new Map(data.user.permMap.site); + this.user.permMap.chan = new Map(data.user.permMap.chan); + + //Tell the chatbox to handle client info + //should it have its own event listener instead? Guess it's a stylistic choice :P this.chatBox.handleClientInfo(data); } } diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index 7eba314..b8c1eac 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -143,7 +143,10 @@ class chatBox{ send(event){ if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){ this.commandPreprocessor.preprocess(this.chatPrompt.value); + //Clear our prompt and autocomplete nodes this.chatPrompt.value = ""; + this.autocompletePlaceholder.innerHTML = ''; + this.autocompleteDisplay.innerHTML = ''; } } @@ -198,11 +201,12 @@ class chatBox{ //I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix //and also directly push it into a shared array :P for(let cmd of dictionary[set].cmds){ - //Append the proper prefix/postfix to the current command - const definition = (`${dictionary[set].prefix}${cmd}${dictionary[set].postfix}`); - //if definition starts with the current word - if(word == '' ? false : definition.indexOf(word) == 0){ + //Append the proper prefix/postfix to the current command + const definition = (`${dictionary[set].prefix}${cmd[0]}${dictionary[set].postfix}`); + + //if definition starts with the current word and the command is enabled + if((word == '' ? false : definition.indexOf(word) == 0) && cmd[1]){ //Add definition to match list matches.push(definition); } diff --git a/www/js/channel/commandPreprocessor.js b/www/js/channel/commandPreprocessor.js index 223c28e..2be84be 100644 --- a/www/js/channel/commandPreprocessor.js +++ b/www/js/channel/commandPreprocessor.js @@ -170,7 +170,9 @@ class commandPreprocessor{ tokes: { prefix: '!', postfix: '', - cmds: this.usedTokes + cmds: [ + ['toke', true] + ].concat(injectPerms(this.usedTokes)) }, //Make sure to add spaces at the end for commands that take arguments //Not necissary but definitely nice to have @@ -178,50 +180,48 @@ class commandPreprocessor{ prefix: '!', postfix: '', cmds: [ - "whisper ", - "announce ", - "serverannounce ", - "clear ", - "kick " + ["whisper ", true], + ["announce ", client.user.permMap.chan.get('announce')], + ["serverannounce ", client.user.permMap.site.get('announce')], + ["clear ", client.user.permMap.chan.get('clearChat')], + ["kick ", client.user.permMap.chan.get('kickUser')], ] }, localCMD:{ prefix: '/', postfix: '', cmds: [ - "high " + ["high ", true] ] }, usernames:{ prefix: '', postfix: '', - cmds: Array.from(client.userList.colorMap.keys()) + cmds: injectPerms(Array.from(client.userList.colorMap.keys())) }, emotes:{ prefix:'[', postfix:']', - cmds: this.getEmoteNames() + cmds: injectPerms(this.getEmoteNames()) } - }; - - //Ensure default toke command - //Check if 'toke' is the first registered toke - if(dictionary.tokes.cmds[0] != 'toke'){ - //Find the current place of the 'toke' command, if any - const tokeIndex = dictionary.tokes.cmds.indexOf('toke'); - - //If the default command is present but is out of order - if(tokeIndex != -1){ - //Splice it out - dictionary.tokes.cmds.splice(tokeIndex,1); - } - - //Throw it into the beggining of the array - dictionary.tokes.cmds.unshift('toke'); - } + }; //return our dictionary object return dictionary; + + function injectPerms(cmds, perm = true){ + //Create empty array to hold cmds + let cmdSet = []; + + //For each cmd + for(let cmd of cmds){ + //Add the cmd with its perm to the cmdset + cmdSet.push([cmd, perm]); + } + + //return the cmd set + return cmdSet; + } } }