├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist └── index.js ├── package.json ├── rollup.config.js └── src └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | #test 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "mocha": true 6 | }, 7 | "rules": { 8 | "comma-dangle": ["error", "only-multiline"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directories 24 | node_modules 25 | jspm_packages 26 | 27 | # Optional npm cache directory 28 | .npm 29 | 30 | # Optional REPL history 31 | .node_repl_history 32 | 33 | # Editors 34 | .idea 35 | 36 | # Lib 37 | lib 38 | 39 | # npm package lock 40 | package-lock.json 41 | yarn.lock 42 | 43 | others 44 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | 4 | # Coverage directory used by tools like istanbul 5 | coverage 6 | .nyc_output 7 | 8 | # Dependency directories 9 | node_modules 10 | 11 | # npm package lock 12 | package-lock.json 13 | yarn.lock 14 | 15 | # project files 16 | src 17 | test 18 | examples 19 | CHANGELOG.md 20 | .travis.yml 21 | .editorconfig 22 | .eslintignore 23 | .eslintrc 24 | .babelrc 25 | .gitignore 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - '6' 5 | script: 6 | - npm run test 7 | - npm run build 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Dineshkumar Pandiyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Chat 2 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = global || self, global.unstoppableChat = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | const Gun = require('gun'); 8 | const Sea = require('gun/sea'); 9 | require('gun/lib/not.js'); 10 | Gun.SEA = Sea; 11 | 12 | class GunChat { 13 | 14 | constructor(superpeers) { 15 | this.gun = new Gun(superpeers); 16 | this.publicName = null; 17 | this.contactsList = []; 18 | this.contactInvitesList = []; 19 | this.channelsList = []; 20 | this.channelInvitesList = []; 21 | this.announcementsList = []; 22 | this.announcementInvitesList = []; 23 | this.activeContact = null; 24 | this.activeChannel = null; 25 | } 26 | 27 | async validatePubKeyFromUsername(username, pubKey, cb){ 28 | if(!username || !cb) return; 29 | const gun = this.gun; 30 | let verified = false; 31 | gun.get(`~@${username}`).once((peerByUsername) => { 32 | Object.keys(peerByUsername._['>']).forEach((uPub) => { 33 | if(uPub.substr(1) === pubKey){ 34 | verified = true; 35 | cb(); 36 | } 37 | }); 38 | if(!verified) cb("Cannot validate pubkey from username"); 39 | }); 40 | } 41 | 42 | async join(username, password, publicName, cb) { 43 | if(!cb) return; 44 | const gun = this.gun; 45 | gun.on('auth', () => { 46 | gun.user().get('name').put(publicName); 47 | gun.user().get('epub').put(gun.user()._.sea.epub); 48 | this.publicName = publicName; 49 | cb(); 50 | }); 51 | gun.user().recall({ sessionStorage: true }); 52 | if (!username || !password) return; 53 | gun.user().auth(username, password); 54 | } 55 | 56 | async reset() { 57 | const gun = this.gun; 58 | gun.user().get('pchat').once((pubKeys) => { 59 | if(!pubKeys) return; 60 | Object.keys(pubKeys).forEach((pubKey) => { 61 | gun.user().get('pchat').get(pubKey).put({disabled : true}); 62 | }); 63 | }); 64 | gun.user().get('contacts').once((pubKeys) => { 65 | if(!pubKeys) return; 66 | Object.keys(pubKeys).forEach((pubKey) => { 67 | gun.user().get('contacts').get(pubKey).put({disabled : true}); 68 | }); 69 | }); 70 | gun.user().get('pchannel').once((chanKeys) => { 71 | if(!chanKeys) return; 72 | Object.keys(chanKeys).forEach((chanKey) => { 73 | gun.user().get('pchannel').get(chanKey).put({disabled : true}); 74 | }); 75 | }); 76 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').once((pubKeys) => { 77 | if(!pubKeys) return; 78 | Object.keys(pubKeys).forEach((pubKey) => { 79 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey).put({disabled : true}); 80 | }); 81 | }); 82 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').once((pubKeys) => { 83 | if(!pubKeys) return; 84 | Object.keys(pubKeys).forEach((pubKey) => { 85 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').get(pubKey).once((chanKeys) => { 86 | if(!chanKeys) return; 87 | Object.keys(chanKeys).forEach((chanKey) => { 88 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').get(pubKey).get(chanKey).put("disabled"); 89 | }); 90 | }); 91 | }); 92 | }); 93 | gun.get('pchat').get(gun.user().is.pub).once((pubKeys) => { 94 | if(!pubKeys) return; 95 | Object.keys(pubKeys).forEach((pubKey) => { 96 | gun.get('pchat').get(gun.user().is.pub).get(pubKey).put("disabled"); 97 | }); 98 | }); 99 | } 100 | 101 | async logout() { 102 | const gun = this.gun; 103 | gun.user().leave(); 104 | } 105 | 106 | async addContact(username, pubKey, publicName) { 107 | if (!username) return; 108 | const gun = this.gun; 109 | this.validatePubKeyFromUsername(username, pubKey, (err) => { 110 | if(err) return; 111 | gun.user().get('contacts').get(pubKey).put({ 112 | pubKey, 113 | alias: username, 114 | name: publicName, 115 | disabled: false 116 | }); 117 | gun.get(pubKey).get('invites').get('contacts').get(gun.user().is.pub) 118 | .put({ 119 | pubKey: gun.user().is.pub, 120 | alias: gun.user().is.alias, 121 | name: this.publicName, 122 | disabled : false 123 | }); 124 | }); 125 | } 126 | 127 | async removeContact(pubKey) { 128 | if (!pubKey) return; 129 | const gun = this.gun; 130 | gun.user().get('contacts').get(pubKey).put({disabled : true}); 131 | } 132 | 133 | async loadContacts(cb) { 134 | if (!cb) return; 135 | const gun = this.gun; 136 | const contactsList = this.contactsList; 137 | const loadedContacts = {}; 138 | gun.user().get('contacts').not((key) => { 139 | cb(contactsList); 140 | }); 141 | gun.user().get('contacts').on((contacts) => { 142 | if (!contacts) return; 143 | Object.keys(contacts).forEach((pubKey) => { 144 | if (pubKey === '_' || pubKey === 'null') return; 145 | gun.user().get('contacts').get(pubKey).on((contact) => { 146 | if (!contact || (contact && contact.name && !contact.disabled && loadedContacts[pubKey])) return; 147 | if(contact.disabled && loadedContacts[pubKey]){ 148 | const index = contactsList.map(c => c.pubKey).indexOf(pubKey); 149 | contactsList.splice(index, 1); 150 | loadedContacts[pubKey] = false; 151 | } else if(!contact.disabled && contact.name && !loadedContacts[pubKey]){ 152 | loadedContacts[pubKey] = true; 153 | const contactIndex = contactsList.length; 154 | contactsList.push({ 155 | pubKey: contact.pubKey, 156 | alias: contact.alias, 157 | name: contact.name 158 | }); 159 | gun.get('pchat').get(gun.user().is.pub).get(contact.pubKey).get('new') 160 | .on((newMsgs) => { 161 | if (!newMsgs) return; 162 | let newCount = 0; 163 | Object.keys(newMsgs).forEach((time) => { 164 | if (time === '_' || time === "disabled" || !newMsgs[time] || newMsgs[time] === "disabled") return; 165 | newCount += 1; 166 | }); 167 | contactsList[contactIndex].notifCount = newCount; 168 | cb(contactsList); 169 | }); 170 | } 171 | cb(contactsList); 172 | }); 173 | }); 174 | }); 175 | } 176 | 177 | async loadContactInvites(cb) { 178 | if (!cb || !this.gun.user().is) return; 179 | const gun = this.gun; 180 | const invitesList = this.contactInvitesList; 181 | const loadedInvites = {}; 182 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').not((key) => { 183 | cb(invitesList); 184 | }); 185 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts') 186 | .on(async (contacts) => { 187 | Object.keys(contacts).forEach((pubKey) => { 188 | if (pubKey === '_' || pubKey === 'null') return; 189 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey) 190 | .on((contact) => { 191 | if (!contact || (contact && contact.name && !contact.disabled && loadedInvites[contact.pubKey])) return; 192 | if(contact.disabled && loadedInvites[pubKey]){ 193 | const index = invitesList.map(c => c.pubKey).indexOf(pubKey); 194 | invitesList.splice(index, 1); 195 | loadedInvites[pubKey] = false; 196 | } else if (contact.name && !contact.disabled && !loadedInvites[pubKey]){ 197 | loadedInvites[contact.pubKey] = true; 198 | invitesList.push({ 199 | name: contact.name, 200 | pubKey: contact.pubKey, 201 | alias: contact.alias 202 | }); 203 | } 204 | cb(invitesList); 205 | }); 206 | }); 207 | }); 208 | } 209 | 210 | async acceptContactInvite(username, pubKey, publicName) { 211 | if (!username && !publicName) return; 212 | const gun = this.gun; 213 | this.validatePubKeyFromUsername(username, pubKey, (err) => { 214 | if(err) return; 215 | gun.user().get('contacts').get(pubKey) 216 | .put({ 217 | pubKey, 218 | alias: username, 219 | name: publicName, 220 | disabled: false 221 | }); 222 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey).put({disabled : true}); 223 | }); 224 | } 225 | 226 | async denyContactInvite(pubKey) { 227 | if (!pubKey) return; 228 | const gun = this.gun; 229 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey).put({disabled : true}); 230 | } 231 | 232 | async sendMessageToContact(pubKey, msg) { 233 | if (!pubKey) return; 234 | const gun = this.gun; 235 | if (msg.length < 1) return; 236 | const time = Date.now(); 237 | const otherPeer = await gun.user(pubKey); 238 | let otherPeerEpub = otherPeer.epub; 239 | if(otherPeer.epub[2] === ":"){ 240 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 241 | } 242 | const sec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 243 | const encMsg = await Gun.SEA.encrypt(msg, sec); 244 | gun.user().get('pchat').get(pubKey).get(time) 245 | .put(JSON.stringify({ 246 | msg: encMsg, 247 | time 248 | })); 249 | gun.get('pchat').get(pubKey).get(gun.user().is.pub).get('new') 250 | .get(time) 251 | .put(JSON.stringify({ 252 | msg: encMsg, 253 | time, 254 | })); 255 | gun.get('pchat').get(pubKey).get(gun.user().is.pub).get('latest') 256 | .put(JSON.stringify({ 257 | msg: JSON.stringify(encMsg), 258 | time 259 | })); 260 | gun.get('pchat').get(gun.user().is.pub).get(pubKey).get('latest') 261 | .put(JSON.stringify({ 262 | msg: JSON.stringify(encMsg), 263 | time 264 | })); 265 | } 266 | 267 | async loadMessagesOfContact(pubKey, publicName, cb) { 268 | if (!pubKey || !cb) return; 269 | const gun = this.gun; 270 | this.activeContact = pubKey; 271 | this.activeChannel = null; 272 | const thisChat = this; 273 | const loadedMsgs = {}; 274 | const loadedMsgsList = []; 275 | const otherPeer = await gun.user(pubKey); 276 | let otherPeerEpub = otherPeer.epub; 277 | if(otherPeer.epub[2] === ":"){ 278 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 279 | } 280 | async function loadMsgsOf(path, name) { 281 | path.not((key) => { 282 | cb(loadedMsgsList); 283 | }); 284 | path.on((msgs) => { 285 | if (!msgs) return; 286 | Object.keys(msgs).forEach((time) => { 287 | if (loadedMsgs[time]) return; 288 | path.get(time) 289 | .on(async (msgDataString) => { 290 | if (thisChat.activeContact !== pubKey || !msgDataString || msgDataString === "null" || loadedMsgs[time]) return; 291 | loadedMsgs[time] = true; 292 | let msgData = msgDataString; 293 | if (typeof msgDataString === 'string') { 294 | msgData = JSON.parse(msgDataString); 295 | } 296 | if (!msgData || !msgData.msg) return; 297 | if (typeof msgData.msg === 'string') { 298 | msgData.msg = JSON.parse(msgData.msg.substr(3, msgData.msg.length)); 299 | } 300 | const sec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 301 | const decMsg = await Gun.SEA.decrypt(msgData.msg, sec); 302 | if (!decMsg) return; 303 | loadedMsgsList.push({ 304 | time: msgData.time, 305 | msg: decMsg, 306 | owner: name 307 | }); 308 | loadedMsgsList.sort((a, b) => a.time - b.time); 309 | gun.get('pchat').get(gun.user().is.pub).get(pubKey).get('new').get(msgData.time).put("disabled"); 310 | cb(loadedMsgsList); 311 | }); 312 | }); 313 | }); 314 | } 315 | loadMsgsOf(gun.user().get('pchat') 316 | .get(pubKey), this.publicName); 317 | loadMsgsOf(gun.user(pubKey).get('pchat').get(gun.user()._.sea.pub), publicName); 318 | } 319 | 320 | async createChannel(channelName, cb) { 321 | const gun = this.gun; 322 | const channelPair = await Gun.SEA.pair(); 323 | const channelKey = channelPair.epub; 324 | const sec = await Gun.SEA.secret(channelKey, gun.user()._.sea); 325 | const encPair = await Gun.SEA.encrypt(JSON.stringify(channelPair), sec); 326 | const channel = { 327 | pair: encPair, 328 | name: channelName, 329 | key: channelKey, 330 | peers: {} 331 | }; 332 | gun.user().get('pchannel').get(channelKey).put(channel, () => { 333 | const userPeer = JSON.stringify({ 334 | alias: gun.user().is.alias, 335 | name: this.publicName, 336 | joined: true, 337 | disabled : false 338 | }); 339 | gun.user().get('pchannel').get(channelKey).get('peers') 340 | .get(gun.user().is.pub) 341 | .put(userPeer, () => { 342 | channel.peers[gun.user().is.pub] = userPeer; 343 | if(cb && typeof cb === 'function'){ 344 | channel.pair = channelPair; 345 | cb(channel); 346 | } 347 | }); 348 | }); 349 | } 350 | 351 | async leaveChannel(channel) { 352 | if (!channel) return; 353 | const gun = this.gun; 354 | const leaveMsg = `${this.publicName} has left the chat.`; 355 | this.sendMessageToChannel(channel, leaveMsg, { 356 | pubKey: gun.user().is.pub, 357 | alias: gun.user().is.alias, 358 | name: this.publicName, 359 | action: 'leave' 360 | }); 361 | gun.user().get('pchannel').get(channel.key) 362 | .put({disabled : true}); 363 | } 364 | 365 | async loadChannels(cb) { 366 | if (!cb) return; 367 | const gun = this.gun; 368 | const loadedChannels = {}; 369 | const loadedChannelsList = this.channelsList; 370 | gun.user().get('pchannel').not((key) => { 371 | cb(loadedChannelsList); 372 | }); 373 | gun.user().get('pchannel') 374 | .on(async (channels) => { 375 | if (!channels) return; 376 | Object.keys(channels).forEach(async (channelKey) => { 377 | if (channelKey === "_" || loadedChannels[channelKey]) return; 378 | Gun.SEA.secret(channelKey, gun.user()._.sea, (sec) => { 379 | gun.user().get('pchannel').get(channelKey).on((channel) => { 380 | if (!channel || !channel.key || (channel && channel.key && !channel.disabled && loadedChannels[channelKey])) return; 381 | if(channel.disabled && loadedChannels[channelKey]){ 382 | const index = loadedChannelsList.map(c => c.key).indexOf(channelKey); 383 | loadedChannelsList.splice(index, 1); 384 | loadedChannels[channelKey] = false; 385 | cb(loadedChannelsList); 386 | } 387 | else if(!channel.disabled && channel.name && !loadedChannels[channelKey]){ 388 | const loadedPeers = {}; 389 | gun.user().get('pchannel').get(channelKey).get('peers') 390 | .once(async (peers) => { 391 | if(!peers || loadedChannels[channelKey]) return; 392 | loadedChannels[channelKey] = true; 393 | const pair = await Gun.SEA.decrypt(channel.pair, sec); 394 | const loadedChannelIndex = loadedChannelsList.length; 395 | loadedChannelsList.push({ 396 | key: channelKey, 397 | name: channel.name, 398 | userCount: 0, 399 | latestMsg: null, 400 | peers : loadedPeers, 401 | pair, 402 | }); 403 | cb(loadedChannelsList); 404 | Object.keys(peers).forEach((pubKey) => { 405 | if(pubKey === '_' || loadedPeers[pubKey]) return; 406 | gun.user().get('pchannel').get(channelKey).get('peers') 407 | .get(pubKey).once((peerData) => { 408 | if(!peerData || peerData.disabled || loadedPeers[pubKey]) return; 409 | loadedPeers[pubKey] = peerData; 410 | loadedChannelsList[loadedChannelIndex].peers = loadedPeers; 411 | cb(loadedChannelsList); 412 | }); 413 | }); 414 | gun.get('pchannel').get(channelKey).get('peers').get(gun.user().is.pub) 415 | .get('new') 416 | .on((newMsgs) => { 417 | if (!newMsgs) return; 418 | let newCount = 0; 419 | Object.keys(newMsgs).forEach((time) => { 420 | if (time === '_' || time === "disabled" || !newMsgs[time] || newMsgs[time] === "disabled") { 421 | return; 422 | } 423 | newCount += 1; 424 | }); 425 | if(loadedChannelsList[loadedChannelIndex]){ 426 | loadedChannelsList[loadedChannelIndex].notifCount = newCount; 427 | } 428 | cb(loadedChannelsList); 429 | }); 430 | }); 431 | } 432 | }); 433 | }); 434 | }); 435 | }); 436 | } 437 | 438 | async inviteToChannel(channel, username, peerPubKey, publicName) { 439 | if (!channel || !username || !publicName || !this.gun.user().is) return; 440 | const gun = this.gun; 441 | this.validatePubKeyFromUsername(username, peerPubKey, async (err) => { 442 | if(err) return; 443 | const otherPeer = await gun.user(peerPubKey); 444 | let otherPeerEpub = otherPeer.epub; 445 | if(otherPeer.epub[2] === ":"){ 446 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 447 | } 448 | const inviteSec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 449 | const eInvitePair = await Gun.SEA.encrypt( 450 | JSON.stringify(channel.pair), 451 | inviteSec, 452 | ); 453 | const channelInvite = {...channel, peerName : this.publicName}; 454 | channelInvite.pair = eInvitePair; 455 | gun.get(peerPubKey).get('invites').get('pchannel').get(gun.user()._.sea.pub) 456 | .get(channel.key) 457 | .put(JSON.stringify(channelInvite)); 458 | this.sendMessageToChannel(channel, `${publicName} has been invited.`, { 459 | pubKey: peerPubKey, 460 | alias: username, 461 | name: publicName, 462 | action: 'invited' 463 | }); 464 | gun.user().get('pchannel').get(channel.key).get('peers') 465 | .get(peerPubKey) 466 | .put(JSON.stringify({ 467 | alias: username, 468 | name: publicName, 469 | joined: false, 470 | disabled: false 471 | })); 472 | }); 473 | } 474 | 475 | async loadChannelInvites(cb) { 476 | if (!cb || !this.gun.user().is) return; 477 | const gun = this.gun; 478 | const loadedInvites = {}; 479 | const loadedInvitesList = this.channelInvitesList; 480 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').not((key) => { 481 | cb(loadedInvitesList); 482 | }); 483 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel') 484 | .on(async (peerInvites) => { 485 | if (!peerInvites) return; 486 | Object.keys(peerInvites).forEach((peerPub) => { 487 | if (peerPub === '_') return; 488 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').get(peerPub) 489 | .on(async (channels) => { 490 | if (!channels || channels === "disabled") return; 491 | Object.keys(channels).forEach(async (channelKey) => { 492 | const channel = (typeof channels[channelKey] === 'string' && channels[channelKey] !== "disabled") ? JSON.parse(channels[channelKey]) : channels[channelKey]; 493 | if (channelKey === '_' || !channel || (channel && channel.key && loadedInvites[channelKey])) return; 494 | if(channel === "disabled" && loadedInvites[channelKey]){ 495 | const index = loadedInvitesList.map(c => c.key).indexOf(channelKey); 496 | loadedInvitesList.splice(index, 1); 497 | loadedInvites[channelKey] = false; 498 | } 499 | else if(channel.key && !loadedInvites[channelKey]){ 500 | loadedInvites[channelKey] = channelKey; 501 | const peerKeys = await gun.user(peerPub).then(); 502 | const peerEpub = peerKeys ? peerKeys.epub : null; 503 | const sec = await Gun.SEA.secret(peerEpub, gun.user()._.sea); 504 | if (typeof channel.pair === 'string') { 505 | channel.pair = JSON.parse(channel.pair.substr(3, channel.pair.length)); 506 | } 507 | channel.pair = await Gun.SEA.decrypt(channel.pair, sec); 508 | channel.peerPub = peerPub; 509 | channel.peerAlias = peerKeys.alias; 510 | channel.key = channelKey; 511 | loadedInvitesList.push(channel); 512 | } 513 | cb(loadedInvitesList); 514 | }); 515 | }); 516 | }); 517 | }); 518 | } 519 | 520 | async acceptChannelInvite(invite) { 521 | if (!invite) return; 522 | const gun = this.gun; 523 | gun.user().get('pchannel').get(invite.key).get('peers') 524 | .get(gun.user().is.pub) 525 | .put(JSON.stringify({ 526 | alias: gun.user().is.alias, 527 | name: this.publicName, 528 | joined: true, 529 | key: invite.key, 530 | peerPub: invite.peerPub 531 | })); 532 | gun.user().get('pchannel').get(invite.key) 533 | .get('peers') 534 | .get(invite.peerPub) 535 | .put(JSON.stringify({ 536 | alias: invite.peerAlias, 537 | name: invite.peerName, 538 | joined: true, 539 | key: invite.key, 540 | peerPub: invite.peerPub 541 | })); 542 | const sec = await Gun.SEA.secret(invite.key, gun.user()._.sea); 543 | const encPair = await Gun.SEA.encrypt(invite.pair, sec); 544 | gun.user().get('pchannel').get(invite.key).put({ 545 | pair: encPair, 546 | name: invite.name, 547 | key: invite.key, 548 | disabled: false 549 | }); 550 | const loadedPeers = {}; 551 | Object.keys(invite.peers).forEach((pubKey) => { 552 | if (pubKey === '_') return; 553 | const peer = invite.peers[pubKey]; 554 | if (loadedPeers[pubKey] || !peer || peer.disabled) return; 555 | loadedPeers[pubKey] = pubKey; 556 | gun.user().get('pchannel').get(invite.key) 557 | .get('peers') 558 | .get(pubKey) 559 | .put(JSON.stringify(peer)); 560 | }); 561 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel') 562 | .get(invite.peerPub) 563 | .get(invite.key) 564 | .put("disabled"); 565 | const channel = invite; 566 | if (!channel.peers[gun.user().is.pub]) { 567 | channel.peers[gun.user().is.pub] = { alias: gun.user().is.alias }; 568 | } 569 | channel.peers[gun.user().is.pub].joined = true; 570 | const joinMsg = `${this.publicName} has joined the chat!`; 571 | this.sendMessageToChannel(channel, joinMsg, { 572 | pubKey: gun.user().is.pub, 573 | alias: gun.user().is.alias, 574 | name: this.publicName, 575 | action: 'join' 576 | }); 577 | const inviteIndex = this.channelInvitesList.findIndex((c) => c.key === invite.key); 578 | this.channelInvitesList.splice(inviteIndex, 1); 579 | } 580 | 581 | async denyChannelInvite(invite) { 582 | if (!invite) return; 583 | const gun = this.gun; 584 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel') 585 | .get(invite.peerPub) 586 | .get(invite.key) 587 | .put("disabled"); 588 | } 589 | 590 | async sendMessageToChannel(channel, msg, peerInfo) { 591 | if (!channel || msg.length < 1) return; 592 | const gun = this.gun; 593 | const time = Date.now(); 594 | const sec = await Gun.SEA.secret(channel.key, channel.pair); 595 | const encMsg = await Gun.SEA.encrypt(msg, sec); 596 | const channelChatToSend = gun.user().get('pchannel').get(channel.key) 597 | .get('chat'); 598 | channelChatToSend.get(time) 599 | .put(JSON.stringify({ 600 | msg: encMsg, 601 | userPub: gun.user().is.pub, 602 | userName: this.publicName, 603 | time, 604 | peerInfo, 605 | })); 606 | gun.get('pchannel').get(channel.key).get('latest') 607 | .put({ 608 | msg: encMsg, 609 | user: gun.user().is.pub, 610 | time, 611 | peerInfo, 612 | }); 613 | if (!channel.peers) return; 614 | Object.keys(channel.peers).forEach((pubKey) => { 615 | if (pubKey !== '_' && channel.peers[pubKey] && pubKey !== gun.user().is.pub) { 616 | gun.get('pchannel').get(channel.key).get('peers').get(pubKey) 617 | .get('new') 618 | .get(time) 619 | .put(JSON.stringify({ 620 | msg: encMsg, 621 | user: gun.user().is.pub, 622 | time 623 | })); 624 | } 625 | }); 626 | } 627 | 628 | async loadMessagesOfChannel(channel, cb) { 629 | if (!channel || !cb) return; 630 | const gun = this.gun; 631 | this.activeChannel = channel.key; 632 | this.activeContact = null; 633 | const thisChat = this; 634 | const channelKey = channel.key; 635 | const loadedMsgsList = []; 636 | const loadedMsgs = {}; 637 | const channelSec = await Gun.SEA.secret(channel.key, channel.pair); 638 | async function loadMsgsOf(path, name) { 639 | path.not((key) => { 640 | cb(loadedMsgsList); 641 | }); 642 | path.on((peerMsgs) => { 643 | if (!peerMsgs) return; 644 | Object.keys(peerMsgs).forEach((time) => { 645 | if (loadedMsgs[time + name] || time === '_') return; 646 | path.get(time) 647 | .on(async (msgDataString) => { 648 | if (thisChat.activeChannel !== channel.key || loadedMsgs[time + name]) return; 649 | loadedMsgs[time + name] = true; 650 | let msgData = msgDataString; 651 | if (typeof msgDataString === 'string') { 652 | msgData = JSON.parse(msgDataString); 653 | } 654 | if (typeof msgData.msg === 'string') { 655 | msgData.msg = JSON.parse(msgData.msg.substr(3, msgData.msg.length)); 656 | } 657 | const decMsg = await Gun.SEA.decrypt(msgData.msg, channelSec); 658 | if (!msgData || !msgData.msg || !decMsg || !msgData.userPub) return; 659 | if (msgData.peerInfo) { 660 | if (typeof msgData.peerInfo === 'string') { 661 | msgData.peerInfo = JSON.parse(msgData.peerInfo); 662 | } 663 | if (msgData.peerInfo.action === 'join') { 664 | channel.peers[msgData.peerInfo.pubKey] = { 665 | alias: msgData.peerInfo.alias, 666 | pubKey: msgData.peerInfo.pubKey, 667 | name: msgData.peerInfo.name, 668 | joined: true, 669 | disabled: false 670 | }; 671 | gun.user().get('pchannel').get(channelKey).get('peers') 672 | .get(msgData.peerInfo.pubKey) 673 | .put(JSON.stringify(channel.peers[msgData.peerInfo.pubKey])); 674 | } else if (msgData.peerInfo.action === 'leave') { 675 | gun.user().get('pchannel').get(channel.key).get('peers') 676 | .get(msgData.peerInfo.pubKey) 677 | .put("disabled"); 678 | } else if (msgData.peerInfo.action === 'invited') { 679 | let peerObj = { 680 | alias: msgData.peerInfo.alias, 681 | pubKey: msgData.peerInfo.pubKey, 682 | name: msgData.peerInfo.name, 683 | disabled: false 684 | }; 685 | if(channel.peers[msgData.peerInfo.pubKey]){ 686 | peerObj.joined = channel.peers[msgData.peerInfo.pubKey].joined; 687 | } 688 | gun.user().get('pchannel').get(channelKey).get('peers') 689 | .get(msgData.peerInfo.pubKey) 690 | .put(JSON.stringify(peerObj)); 691 | } 692 | } 693 | loadedMsgsList.push({ 694 | time: msgData.time, 695 | userPub: msgData.userPub, 696 | owner: name, 697 | msg: decMsg, 698 | peerInfo: msgData.peerInfo 699 | }); 700 | loadedMsgsList.sort((a, b) => a.time - b.time); 701 | gun.get('pchannel').get(channel.key).get('peers') 702 | .get(gun.user().is.pub) 703 | .get('new') 704 | .get(msgData.time) 705 | .put("disabled"); 706 | cb(loadedMsgsList); 707 | }); 708 | }); 709 | }); 710 | } 711 | const loadedPeers = {}; 712 | gun.user().get('pchannel').get(channel.key).get('peers').on((peers) => { 713 | Object.keys(peers).forEach((pubKey) => { 714 | if(pubKey === '_' || !peers[pubKey] || typeof peers[pubKey] !== 'string') return; 715 | let peer; 716 | if(peers[pubKey] !== "disabled") { 717 | peer = JSON.parse(peers[pubKey]); 718 | if(typeof peer === 'string'){ 719 | peer = JSON.parse(peer); 720 | } 721 | }else if(peers[pubKey] === "disabled" && loadedPeers[pubKey]){ 722 | delete channel.peers[pubKey]; 723 | loadedPeers[pubKey] = false; 724 | return; 725 | } 726 | const peerChannelChatPath = gun.user(pubKey).get('pchannel') 727 | .get(channelKey) 728 | .get('chat'); 729 | if(!peer || !peer.name || (peer.name && !peer.disabled && loadedPeers[pubKey])) return; 730 | else if(!peer.disabled && peer.name && !loadedPeers[pubKey]){ 731 | loadedPeers[pubKey] = true; 732 | channel.peers[pubKey] = peer; 733 | loadMsgsOf(peerChannelChatPath, peer.name); 734 | } 735 | }); 736 | }); 737 | } 738 | 739 | async createAnnouncement(announcementName, cb) { 740 | const gun = this.gun; 741 | const publicName = this.publicName; 742 | const announcementPair = await Gun.SEA.pair(); 743 | const announcementKey = announcementPair.epub; 744 | const sec = await Gun.SEA.secret(announcementKey, gun.user()._.sea); 745 | const encPair = await Gun.SEA.encrypt(JSON.stringify(announcementPair), sec); 746 | const announcement = { 747 | pair: encPair, 748 | name: announcementName, 749 | key: announcementKey, 750 | owner: gun.user()._.sea.pub, 751 | peers: {}, 752 | admins: {} 753 | }; 754 | gun.user().get('announcement').get(announcementKey).put(announcement, () => { 755 | gun.user().get('announcement').get(announcementKey).get('admins').get(gun.user().is.pub).put(this.publicName); 756 | const userPeer = JSON.stringify({ 757 | alias: gun.user().is.alias, 758 | name: this.publicName, 759 | joined: true, 760 | disabled : false, 761 | pubKey : gun.user().is.pub 762 | }); 763 | gun.user().get('announcement').get(announcementKey).get('peers') 764 | .get(gun.user().is.pub) 765 | .put(userPeer); 766 | if(cb && typeof cb === 'function'){ 767 | announcement.admins[gun.user().is.pub] = publicName; 768 | announcement.peers[gun.user().is.pub] = userPeer; 769 | announcement.pair = announcementPair; 770 | cb(announcement); 771 | } 772 | }); 773 | } 774 | 775 | async leaveAnnouncement(announcement) { 776 | if (!announcement) return; 777 | const gun = this.gun; 778 | const leaveMsg = `${this.publicName} has left the chat.`; 779 | this.sendMessageToAnnouncement(announcement, leaveMsg, { 780 | pubKey: gun.user().is.pub, 781 | alias: gun.user().is.alias, 782 | name: this.publicName, 783 | action: 'leave' 784 | }); 785 | gun.user().get('announcement').get(announcement.key) 786 | .put({disabled : true}); 787 | } 788 | 789 | async loadAnnouncements(cb) { 790 | if (!cb) return; 791 | const gun = this.gun; 792 | const loadedAnnouncements = {}; 793 | const loadedAnnouncementsList = this.announcementsList; 794 | gun.user().get('announcement').not((key) => { 795 | cb(loadedAnnouncementsList); 796 | }); 797 | gun.user().get('announcement') 798 | .on(async (announcements) => { 799 | if (!announcements) return; 800 | Object.keys(announcements).forEach(async (announcementKey) => { 801 | if (announcementKey === "_" || loadedAnnouncements[announcementKey]) return; 802 | Gun.SEA.secret(announcementKey, gun.user()._.sea, (sec) => { 803 | gun.user().get('announcement').get(announcementKey).on((announcement) => { 804 | if (!announcement || !announcement.key || (announcement && announcement.key && !announcement.disabled && loadedAnnouncements[announcementKey])) return; 805 | if(announcement.disabled && loadedAnnouncements[announcementKey]){ 806 | const index = loadedAnnouncementsList.map(c => c.key).indexOf(announcementKey); 807 | loadedAnnouncementsList.splice(index, 1); 808 | loadedAnnouncements[announcementKey] = false; 809 | cb(loadedAnnouncementsList); 810 | } 811 | else if(!announcement.disabled && announcement.name && !loadedAnnouncements[announcementKey]){ 812 | const loadedPeers = {}; 813 | const loadedAdmins = {}; 814 | let loadedAnnouncementIndex; 815 | gun.user().get('announcement').get(announcementKey).get('peers') 816 | .once(async (peers) => { 817 | if(!peers || loadedAnnouncements[announcementKey]) return; 818 | gun.user().get('announcement').get(announcementKey).get('admins').on(async (admins) => { 819 | if(!admins) return; 820 | if(!loadedAnnouncements[announcementKey]){ 821 | loadedAnnouncements[announcementKey] = true; 822 | const pair = await Gun.SEA.decrypt(announcement.pair, sec); 823 | loadedAnnouncementIndex = loadedAnnouncementsList.length; 824 | loadedAnnouncementsList.push({ 825 | key: announcementKey, 826 | name: announcement.name, 827 | owner: announcement.owner, 828 | userCount: 0, 829 | latestMsg: null, 830 | peers : loadedPeers, 831 | admins: loadedAdmins, 832 | pair, 833 | }); 834 | cb(loadedAnnouncementsList); 835 | } 836 | if(typeof loadedAnnouncementIndex === 'undefined') return 837 | Object.keys(peers).forEach((pubKey) => { 838 | if(pubKey === '_' || loadedPeers[pubKey]) return; 839 | gun.user().get('announcement').get(announcementKey).get('peers') 840 | .get(pubKey).once((peerData) => { 841 | if(!peerData || peerData.disabled || loadedPeers[pubKey]) return; 842 | loadedPeers[pubKey] = peerData; 843 | loadedAnnouncementsList[loadedAnnouncementIndex].peers = loadedPeers; 844 | cb(loadedAnnouncementsList); 845 | }); 846 | }); 847 | Object.keys(admins).forEach((pubKey) => { 848 | if(pubKey === '_' || loadedAdmins[pubKey]) return; 849 | gun.user().get('announcement').get(announcementKey).get('admins') 850 | .get(pubKey).once((name) => { 851 | if(name === "disabled" || loadedAdmins[pubKey]) return; 852 | loadedAdmins[pubKey] = name; 853 | loadedAnnouncementsList[loadedAnnouncementIndex].admins = loadedAdmins; 854 | cb(loadedAnnouncementsList); 855 | }); 856 | }); 857 | gun.get('announcement').get(announcementKey).get('peers').get(gun.user().is.pub) 858 | .get('new') 859 | .on((newMsgs) => { 860 | if (!newMsgs) return; 861 | let newCount = 0; 862 | Object.keys(newMsgs).forEach((time) => { 863 | if (time === '_' || time === "disabled" || !newMsgs[time] || newMsgs[time] === "disabled") { 864 | return; 865 | } 866 | newCount += 1; 867 | }); 868 | if(loadedAnnouncementsList[loadedAnnouncementIndex]){ 869 | loadedAnnouncementsList[loadedAnnouncementIndex].notifCount = newCount; 870 | } 871 | cb(loadedAnnouncementsList); 872 | }); 873 | }); 874 | }); 875 | } 876 | }); 877 | }); 878 | }); 879 | }); 880 | } 881 | 882 | async inviteToAnnouncement(announcement, username, peerPubKey, publicName) { 883 | if (!announcement || !username || !publicName || !this.gun.user().is) return; 884 | const gun = this.gun; 885 | this.validatePubKeyFromUsername(username, peerPubKey, async (err) => { 886 | if(err) return; 887 | const otherPeer = await gun.user(peerPubKey); 888 | let otherPeerEpub = otherPeer.epub; 889 | if(otherPeer.epub[2] === ":"){ 890 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 891 | } 892 | const inviteSec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 893 | const eInvitePair = await Gun.SEA.encrypt( 894 | JSON.stringify(announcement.pair), 895 | inviteSec, 896 | ); 897 | const announcementInvite = {...announcement, peerName : this.publicName}; 898 | announcementInvite.pair = eInvitePair; 899 | gun.get(peerPubKey).get('invites').get('announcement').get(gun.user()._.sea.pub) 900 | .get(announcement.key) 901 | .put(JSON.stringify(announcementInvite)); 902 | this.sendMessageToAnnouncement(announcement, `${publicName} has been invited.`, { 903 | pubKey: peerPubKey, 904 | alias: username, 905 | name: publicName, 906 | action: 'invited' 907 | }); 908 | gun.user().get('announcement').get(announcement.key).get('peers') 909 | .get(peerPubKey) 910 | .put(JSON.stringify({ 911 | alias: username, 912 | name: publicName, 913 | joined: false, 914 | disabled: false, 915 | pubKey: peerPubKey 916 | })); 917 | }); 918 | } 919 | 920 | async loadAnnouncementInvites(cb) { 921 | if (!cb || !this.gun.user().is) return; 922 | const gun = this.gun; 923 | const loadedInvites = {}; 924 | const loadedInvitesList = this.announcementInvitesList; 925 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement').not((key) => { 926 | cb(loadedInvitesList); 927 | }); 928 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement') 929 | .on(async (peerInvites) => { 930 | if (!peerInvites) return; 931 | Object.keys(peerInvites).forEach((peerPub) => { 932 | if (peerPub === '_') return; 933 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement').get(peerPub) 934 | .on(async (announcements) => { 935 | if (!announcements || announcements === "disabled") return; 936 | Object.keys(announcements).forEach(async (announcementKey) => { 937 | const announcement = (typeof announcements[announcementKey] === 'string' && announcements[announcementKey] !== "disabled") ? JSON.parse(announcements[announcementKey]) : announcements[announcementKey]; 938 | if (announcementKey === '_' || !announcement || (announcement && announcement.key && loadedInvites[announcementKey])) return; 939 | if(announcement === "disabled" && loadedInvites[announcementKey]){ 940 | const index = loadedInvitesList.map(c => c.key).indexOf(announcementKey); 941 | loadedInvitesList.splice(index, 1); 942 | loadedInvites[announcementKey] = false; 943 | } 944 | else if(announcement.key && !loadedInvites[announcementKey]){ 945 | loadedInvites[announcementKey] = announcementKey; 946 | const peerKeys = await gun.user(peerPub).then(); 947 | const peerEpub = peerKeys ? peerKeys.epub : null; 948 | const sec = await Gun.SEA.secret(peerEpub, gun.user()._.sea); 949 | if (typeof announcement.pair === 'string') { 950 | announcement.pair = JSON.parse(announcement.pair.substr(3, announcement.pair.length)); 951 | } 952 | announcement.pair = await Gun.SEA.decrypt(announcement.pair, sec); 953 | announcement.peerPub = peerPub; 954 | announcement.peerAlias = peerKeys.alias; 955 | announcement.key = announcementKey; 956 | loadedInvitesList.push(announcement); 957 | } 958 | cb(loadedInvitesList); 959 | }); 960 | }); 961 | }); 962 | }); 963 | } 964 | 965 | async acceptAnnouncementInvite(invite) { 966 | if (!invite) return; 967 | const gun = this.gun; 968 | gun.user().get('announcement').get(invite.key).get('peers') 969 | .get(gun.user().is.pub) 970 | .put(JSON.stringify({ 971 | alias: gun.user().is.alias, 972 | name: this.publicName, 973 | joined: true, 974 | key: invite.key, 975 | peerPub: invite.peerPub 976 | })); 977 | gun.user().get('announcement').get(invite.key) 978 | .get('peers') 979 | .get(invite.peerPub) 980 | .put(JSON.stringify({ 981 | alias: invite.peerAlias, 982 | name: invite.peerName, 983 | joined: true, 984 | key: invite.key, 985 | peerPub: invite.peerPub 986 | })); 987 | const sec = await Gun.SEA.secret(invite.key, gun.user()._.sea); 988 | const encPair = await Gun.SEA.encrypt(invite.pair, sec); 989 | gun.user().get('announcement').get(invite.key).put({ 990 | pair: encPair, 991 | name: invite.name, 992 | key: invite.key, 993 | disabled: false, 994 | owner: invite.owner, 995 | }); 996 | const loadedPeers = {}; 997 | Object.keys(invite.peers).forEach((pubKey) => { 998 | if (pubKey === '_') return; 999 | const peer = invite.peers[pubKey]; 1000 | if (loadedPeers[pubKey] || !peer || peer.disabled) return; 1001 | loadedPeers[pubKey] = pubKey; 1002 | gun.user().get('announcement').get(invite.key) 1003 | .get('peers') 1004 | .get(pubKey) 1005 | .put(JSON.stringify(peer)); 1006 | }); 1007 | const loadedAdmins = {}; 1008 | Object.keys(invite.admins).forEach((pubKey) => { 1009 | if (pubKey === '_') return; 1010 | const admin = invite.admins[pubKey]; 1011 | if (loadedAdmins[pubKey] || !admin || admin === 'disabled') return; 1012 | loadedAdmins[pubKey] = admin; 1013 | gun.user().get('announcement').get(invite.key) 1014 | .get('admins') 1015 | .get(pubKey) 1016 | .put(admin); 1017 | }); 1018 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement') 1019 | .get(invite.peerPub) 1020 | .get(invite.key) 1021 | .put("disabled"); 1022 | const announcement = invite; 1023 | if (!announcement.peers[gun.user().is.pub]) { 1024 | announcement.peers[gun.user().is.pub] = { alias: gun.user().is.alias }; 1025 | } 1026 | announcement.peers[gun.user().is.pub].joined = true; 1027 | const joinMsg = `${this.publicName} has joined the chat!`; 1028 | this.sendMessageToAnnouncement(announcement, joinMsg, { 1029 | pubKey: gun.user().is.pub, 1030 | alias: gun.user().is.alias, 1031 | name: this.publicName, 1032 | action: 'join' 1033 | }); 1034 | const inviteIndex = this.announcementInvitesList.findIndex((c) => c.key === invite.key); 1035 | this.announcementInvitesList.splice(inviteIndex, 1); 1036 | } 1037 | 1038 | async denyAnnouncementInvite(invite) { 1039 | if (!invite) return; 1040 | const gun = this.gun; 1041 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement') 1042 | .get(invite.peerPub) 1043 | .get(invite.key) 1044 | .put("disabled"); 1045 | } 1046 | 1047 | async sendMessageToAnnouncement(announcement, msg, peerInfo) { 1048 | if (!announcement || msg.length < 1) return; 1049 | const gun = this.gun; 1050 | const isAdmin = (announcement.admins[gun.user().is.pub] && announcement.admins[gun.user().is.pub] !== "disabled"); 1051 | if(!isAdmin && !peerInfo) return; 1052 | const time = Date.now(); 1053 | const sec = await Gun.SEA.secret(announcement.key, announcement.pair); 1054 | const encMsg = await Gun.SEA.encrypt(msg, sec); 1055 | const announcementChatToSend = gun.user().get('announcement').get(announcement.key) 1056 | .get('chat'); 1057 | announcementChatToSend.get(time) 1058 | .put(JSON.stringify({ 1059 | msg: encMsg, 1060 | userPub: gun.user().is.pub, 1061 | userName: this.publicName, 1062 | time, 1063 | peerInfo, 1064 | })); 1065 | if(isAdmin){ 1066 | gun.get('announcement').get(announcement.key).get('latest') 1067 | .put({ 1068 | msg: encMsg, 1069 | user: gun.user().is.pub, 1070 | time, 1071 | peerInfo, 1072 | }); 1073 | if (!announcement.peers) return; 1074 | Object.keys(announcement.peers).forEach((pubKey) => { 1075 | if (pubKey !== '_' && announcement.peers[pubKey] && pubKey !== gun.user().is.pub) { 1076 | gun.get('announcement').get(announcement.key).get('peers').get(pubKey) 1077 | .get('new') 1078 | .get(time) 1079 | .put(JSON.stringify({ 1080 | msg: encMsg, 1081 | user: gun.user().is.pub, 1082 | time 1083 | })); 1084 | } 1085 | }); 1086 | } 1087 | } 1088 | 1089 | async loadMessagesOfAnnouncement(announcement, cb) { 1090 | if (!announcement || !cb) return; 1091 | const gun = this.gun; 1092 | this.activeAnnouncement = announcement.key; 1093 | this.activeContact = null; 1094 | const thisChat = this; 1095 | const announcementKey = announcement.key; 1096 | const loadedMsgsList = []; 1097 | const loadedMsgs = {}; 1098 | const announcementSec = await Gun.SEA.secret(announcement.key, announcement.pair); 1099 | async function loadMsgsOf(path, name) { 1100 | path.not((key) => { 1101 | cb(loadedMsgsList); 1102 | }); 1103 | path.on((peerMsgs) => { 1104 | if (!peerMsgs) return; 1105 | Object.keys(peerMsgs).forEach((time) => { 1106 | if (loadedMsgs[time + name] || time === '_') return; 1107 | path.get(time) 1108 | .on(async (msgDataString) => { 1109 | if (thisChat.activeAnnouncement !== announcement.key || loadedMsgs[time + name]) return; 1110 | loadedMsgs[time + name] = true; 1111 | let msgData = msgDataString; 1112 | if (typeof msgDataString === 'string') { 1113 | msgData = JSON.parse(msgDataString); 1114 | } 1115 | if (typeof msgData.msg === 'string') { 1116 | msgData.msg = JSON.parse(msgData.msg.substr(3, msgData.msg.length)); 1117 | } 1118 | const decMsg = await Gun.SEA.decrypt(msgData.msg, announcementSec); 1119 | if (!msgData || !msgData.msg || !decMsg || !msgData.userPub) return; 1120 | if (msgData.peerInfo) { 1121 | if (typeof msgData.peerInfo === 'string') { 1122 | msgData.peerInfo = JSON.parse(msgData.peerInfo); 1123 | } 1124 | if (msgData.peerInfo.action === 'join') { 1125 | announcement.peers[msgData.peerInfo.pubKey] = { 1126 | alias: msgData.peerInfo.alias, 1127 | pubKey: msgData.peerInfo.pubKey, 1128 | name: msgData.peerInfo.name, 1129 | joined: true, 1130 | disabled: false 1131 | }; 1132 | gun.user().get('announcement').get(announcementKey).get('peers') 1133 | .get(msgData.peerInfo.pubKey) 1134 | .put(JSON.stringify(announcement.peers[msgData.peerInfo.pubKey])); 1135 | } else if (msgData.peerInfo.action === 'leave') { 1136 | gun.user().get('announcement').get(announcement.key).get('peers') 1137 | .get(msgData.peerInfo.pubKey) 1138 | .put("disabled"); 1139 | } else if (msgData.peerInfo.action === 'invited') { 1140 | let peerObj = { 1141 | alias: msgData.peerInfo.alias, 1142 | pubKey: msgData.peerInfo.pubKey, 1143 | name: msgData.peerInfo.name, 1144 | disabled: false 1145 | }; 1146 | if(announcement.peers[msgData.peerInfo.pubKey]){ 1147 | peerObj.joined = announcement.peers[msgData.peerInfo.pubKey].joined; 1148 | } 1149 | gun.user().get('announcement').get(announcementKey).get('peers') 1150 | .get(msgData.peerInfo.pubKey) 1151 | .put(JSON.stringify(peerObj)); 1152 | } else if (msgData.peerInfo.action === 'newAdmin' && msgData.userPub === announcement.owner) { 1153 | gun.user().get('announcement').get(announcementKey).get('admins') 1154 | .get(msgData.peerInfo.pubKey).put(msgData.peerInfo.name); 1155 | announcement.admins[msgData.peerInfo.pubKey] = msgData.peerInfo.name; 1156 | } 1157 | } 1158 | if(msgData.peerInfo || (announcement.admins[msgData.userPub] && announcement.admins[msgData.userPub] !== "disabled")){ 1159 | loadedMsgsList.push({ 1160 | time: msgData.time, 1161 | userPub: msgData.userPub, 1162 | owner: name, 1163 | msg: decMsg, 1164 | peerInfo: msgData.peerInfo 1165 | }); 1166 | loadedMsgsList.sort((a, b) => a.time - b.time); 1167 | cb(loadedMsgsList); 1168 | } 1169 | gun.get('announcement').get(announcement.key).get('peers') 1170 | .get(gun.user().is.pub) 1171 | .get('new') 1172 | .get(msgData.time) 1173 | .put("disabled"); 1174 | }); 1175 | }); 1176 | }); 1177 | } 1178 | const loadedPeers = {}; 1179 | gun.user().get('announcement').get(announcement.key).get('peers').on((peers) => { 1180 | Object.keys(peers).forEach((pubKey) => { 1181 | if(pubKey === '_' || !peers[pubKey] || typeof peers[pubKey] !== 'string') return; 1182 | let peer; 1183 | if(peers[pubKey] !== "disabled") { 1184 | peer = JSON.parse(peers[pubKey]); 1185 | if(typeof peer === 'string'){ 1186 | peer = JSON.parse(peer); 1187 | } 1188 | }else if(peers[pubKey] === "disabled" && loadedPeers[pubKey]){ 1189 | delete announcement.peers[pubKey]; 1190 | loadedPeers[pubKey] = false; 1191 | return; 1192 | } 1193 | const peerAnnouncementChatPath = gun.user(pubKey).get('announcement') 1194 | .get(announcementKey) 1195 | .get('chat'); 1196 | if(!peer || !peer.name || (peer.name && !peer.disabled && loadedPeers[pubKey])) return; 1197 | else if(!peer.disabled && peer.name && !loadedPeers[pubKey]){ 1198 | loadedPeers[pubKey] = true; 1199 | announcement.peers[pubKey] = peer; 1200 | loadMsgsOf(peerAnnouncementChatPath, peer.name); 1201 | } 1202 | }); 1203 | }); 1204 | } 1205 | 1206 | async addAdminToAnnouncement(announcement, newAdmin){ 1207 | const gun = this.gun; 1208 | gun.user().get('announcement').get(announcement.key).get('owner').once((ownerPub) => { 1209 | if(gun.user().is.pub === ownerPub){ 1210 | let newAdminMsg = `${newAdmin.name} has been made an admin.`; 1211 | this.sendMessageToAnnouncement(announcement, newAdminMsg, { 1212 | pubKey: newAdmin.pubKey, 1213 | name: newAdmin.name, 1214 | action: 'newAdmin' 1215 | }); 1216 | } 1217 | }); 1218 | } 1219 | } 1220 | 1221 | return GunChat; 1222 | 1223 | }))); 1224 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unstoppable-chat", 3 | "version": "1.7.2", 4 | "description": "Decentralized End to End Encrypted Chat Network. Built with Gun.js and Sea.js.", 5 | "main": "./dist/index.js", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/JamieRez/open-chat.git" 13 | }, 14 | "keywords": [ 15 | "chat", 16 | "npm", 17 | "module", 18 | "node", 19 | "javascript", 20 | "decentralized", 21 | "encrypted", 22 | "message", 23 | "text" 24 | ], 25 | "author": "James Rezendes ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/JamieRez/open-chat/issues" 29 | }, 30 | "homepage": "https://github.com/JamieRez/open-chat#readme", 31 | "devDependencies": { 32 | "@rollup/plugin-node-resolve": "^7.1.3", 33 | "babel-cli": "^6.26.0", 34 | "babel-eslint": "^10.0.1", 35 | "babel-plugin-add-module-exports": "^1.0.0", 36 | "babel-polyfill": "^6.26.0", 37 | "babel-preset-env": "^1.6.1", 38 | "babel-preset-minify": "^0.5.0", 39 | "chai": "^4.1.2", 40 | "cross-env": "^5.1.3", 41 | "eslint": "^5.16.0", 42 | "eslint-config-airbnb": "^17.1.0", 43 | "eslint-plugin-import": "^2.7.0", 44 | "eslint-plugin-jsx-a11y": "^6.0.2", 45 | "eslint-plugin-react": "^7.4.0", 46 | "mocha": "^6.1.3", 47 | "nyc": "^15.0.1", 48 | "rimraf": "^2.6.2" 49 | }, 50 | "dependencies": { 51 | "@peculiar/webcrypto": "^1.1.1", 52 | "gun": "git://github.com/amark/gun.git#master" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | 3 | export default { 4 | input: 'src/index.js', 5 | output: { 6 | dir: 'dist', 7 | format: 'umd', 8 | name: 'unstoppableChat' 9 | }, 10 | plugins: [resolve()] 11 | }; 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const Gun = require('gun'); 2 | const Sea = require('gun/sea'); 3 | require('gun/lib/not.js'); 4 | Gun.SEA = Sea; 5 | 6 | export default class GunChat { 7 | 8 | constructor(superpeers) { 9 | this.gun = new Gun(superpeers); 10 | this.publicName = null; 11 | this.contactsList = []; 12 | this.contactInvitesList = []; 13 | this.channelsList = []; 14 | this.channelInvitesList = []; 15 | this.announcementsList = []; 16 | this.announcementInvitesList = []; 17 | this.activeContact = null; 18 | this.activeChannel = null; 19 | } 20 | 21 | async validatePubKeyFromUsername(username, pubKey, cb){ 22 | if(!username || !cb) return; 23 | const gun = this.gun; 24 | let verified = false; 25 | gun.get(`~@${username}`).once((peerByUsername) => { 26 | Object.keys(peerByUsername._['>']).forEach((uPub) => { 27 | if(uPub.substr(1) === pubKey){ 28 | verified = true; 29 | cb(); 30 | } 31 | }); 32 | if(!verified) cb("Cannot validate pubkey from username"); 33 | }); 34 | } 35 | 36 | async join(username, password, publicName, cb) { 37 | if(!cb) return; 38 | const gun = this.gun; 39 | gun.on('auth', () => { 40 | gun.user().get('name').put(publicName); 41 | gun.user().get('epub').put(gun.user()._.sea.epub); 42 | this.publicName = publicName; 43 | cb(); 44 | }); 45 | gun.user().recall({ sessionStorage: true }); 46 | if (!username || !password) return; 47 | gun.user().auth(username, password); 48 | } 49 | 50 | async reset() { 51 | const gun = this.gun; 52 | gun.user().get('pchat').once((pubKeys) => { 53 | if(!pubKeys) return; 54 | Object.keys(pubKeys).forEach((pubKey) => { 55 | gun.user().get('pchat').get(pubKey).put({disabled : true}); 56 | }); 57 | }); 58 | gun.user().get('contacts').once((pubKeys) => { 59 | if(!pubKeys) return; 60 | Object.keys(pubKeys).forEach((pubKey) => { 61 | gun.user().get('contacts').get(pubKey).put({disabled : true}); 62 | }); 63 | }); 64 | gun.user().get('pchannel').once((chanKeys) => { 65 | if(!chanKeys) return; 66 | Object.keys(chanKeys).forEach((chanKey) => { 67 | gun.user().get('pchannel').get(chanKey).put({disabled : true}); 68 | }); 69 | }); 70 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').once((pubKeys) => { 71 | if(!pubKeys) return; 72 | Object.keys(pubKeys).forEach((pubKey) => { 73 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey).put({disabled : true}); 74 | }); 75 | }); 76 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').once((pubKeys) => { 77 | if(!pubKeys) return; 78 | Object.keys(pubKeys).forEach((pubKey) => { 79 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').get(pubKey).once((chanKeys) => { 80 | if(!chanKeys) return; 81 | Object.keys(chanKeys).forEach((chanKey) => { 82 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').get(pubKey).get(chanKey).put("disabled"); 83 | }); 84 | }); 85 | }); 86 | }); 87 | gun.get('pchat').get(gun.user().is.pub).once((pubKeys) => { 88 | if(!pubKeys) return; 89 | Object.keys(pubKeys).forEach((pubKey) => { 90 | gun.get('pchat').get(gun.user().is.pub).get(pubKey).put("disabled"); 91 | }); 92 | }); 93 | } 94 | 95 | async logout() { 96 | const gun = this.gun; 97 | gun.user().leave(); 98 | } 99 | 100 | async addContact(username, pubKey, publicName) { 101 | if (!username) return; 102 | const gun = this.gun; 103 | this.validatePubKeyFromUsername(username, pubKey, (err) => { 104 | if(err) return; 105 | gun.user().get('contacts').get(pubKey).put({ 106 | pubKey, 107 | alias: username, 108 | name: publicName, 109 | disabled: false 110 | }); 111 | gun.get(pubKey).get('invites').get('contacts').get(gun.user().is.pub) 112 | .put({ 113 | pubKey: gun.user().is.pub, 114 | alias: gun.user().is.alias, 115 | name: this.publicName, 116 | disabled : false 117 | }); 118 | }); 119 | } 120 | 121 | async removeContact(pubKey) { 122 | if (!pubKey) return; 123 | const gun = this.gun; 124 | gun.user().get('contacts').get(pubKey).put({disabled : true}); 125 | } 126 | 127 | async loadContacts(cb) { 128 | if (!cb) return; 129 | const gun = this.gun; 130 | const contactsList = this.contactsList; 131 | const loadedContacts = {}; 132 | gun.user().get('contacts').not((key) => { 133 | cb(contactsList); 134 | }); 135 | gun.user().get('contacts').on((contacts) => { 136 | if (!contacts) return; 137 | Object.keys(contacts).forEach((pubKey) => { 138 | if (pubKey === '_' || pubKey === 'null') return; 139 | gun.user().get('contacts').get(pubKey).on((contact) => { 140 | if (!contact || (contact && contact.name && !contact.disabled && loadedContacts[pubKey])) return; 141 | if(contact.disabled && loadedContacts[pubKey]){ 142 | const index = contactsList.map(c => c.pubKey).indexOf(pubKey); 143 | contactsList.splice(index, 1); 144 | loadedContacts[pubKey] = false; 145 | } else if(!contact.disabled && contact.name && !loadedContacts[pubKey]){ 146 | loadedContacts[pubKey] = true; 147 | const contactIndex = contactsList.length; 148 | contactsList.push({ 149 | pubKey: contact.pubKey, 150 | alias: contact.alias, 151 | name: contact.name 152 | }); 153 | gun.get('pchat').get(gun.user().is.pub).get(contact.pubKey).get('new') 154 | .on((newMsgs) => { 155 | if (!newMsgs) return; 156 | let newCount = 0; 157 | Object.keys(newMsgs).forEach((time) => { 158 | if (time === '_' || time === "disabled" || !newMsgs[time] || newMsgs[time] === "disabled") return; 159 | newCount += 1; 160 | }); 161 | contactsList[contactIndex].notifCount = newCount; 162 | cb(contactsList); 163 | }); 164 | } 165 | cb(contactsList); 166 | }); 167 | }); 168 | }); 169 | } 170 | 171 | async loadContactInvites(cb) { 172 | if (!cb || !this.gun.user().is) return; 173 | const gun = this.gun; 174 | const invitesList = this.contactInvitesList; 175 | const loadedInvites = {}; 176 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').not((key) => { 177 | cb(invitesList); 178 | }); 179 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts') 180 | .on(async (contacts) => { 181 | Object.keys(contacts).forEach((pubKey) => { 182 | if (pubKey === '_' || pubKey === 'null') return; 183 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey) 184 | .on((contact) => { 185 | if (!contact || (contact && contact.name && !contact.disabled && loadedInvites[contact.pubKey])) return; 186 | if(contact.disabled && loadedInvites[pubKey]){ 187 | const index = invitesList.map(c => c.pubKey).indexOf(pubKey); 188 | invitesList.splice(index, 1); 189 | loadedInvites[pubKey] = false; 190 | } else if (contact.name && !contact.disabled && !loadedInvites[pubKey]){ 191 | loadedInvites[contact.pubKey] = true; 192 | invitesList.push({ 193 | name: contact.name, 194 | pubKey: contact.pubKey, 195 | alias: contact.alias 196 | }); 197 | } 198 | cb(invitesList); 199 | }); 200 | }); 201 | }); 202 | } 203 | 204 | async acceptContactInvite(username, pubKey, publicName) { 205 | if (!username && !publicName) return; 206 | const gun = this.gun; 207 | this.validatePubKeyFromUsername(username, pubKey, (err) => { 208 | if(err) return; 209 | gun.user().get('contacts').get(pubKey) 210 | .put({ 211 | pubKey, 212 | alias: username, 213 | name: publicName, 214 | disabled: false 215 | }); 216 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey).put({disabled : true}); 217 | }); 218 | } 219 | 220 | async denyContactInvite(pubKey) { 221 | if (!pubKey) return; 222 | const gun = this.gun; 223 | gun.get(gun.user()._.sea.pub).get('invites').get('contacts').get(pubKey).put({disabled : true}); 224 | } 225 | 226 | async sendMessageToContact(pubKey, msg) { 227 | if (!pubKey) return; 228 | const gun = this.gun; 229 | if (msg.length < 1) return; 230 | const time = Date.now(); 231 | const otherPeer = await gun.user(pubKey); 232 | let otherPeerEpub = otherPeer.epub; 233 | if(otherPeer.epub[2] === ":"){ 234 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 235 | } 236 | const sec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 237 | const encMsg = await Gun.SEA.encrypt(msg, sec); 238 | gun.user().get('pchat').get(pubKey).get(time) 239 | .put(JSON.stringify({ 240 | msg: encMsg, 241 | time 242 | })); 243 | gun.get('pchat').get(pubKey).get(gun.user().is.pub).get('new') 244 | .get(time) 245 | .put(JSON.stringify({ 246 | msg: encMsg, 247 | time, 248 | })); 249 | gun.get('pchat').get(pubKey).get(gun.user().is.pub).get('latest') 250 | .put(JSON.stringify({ 251 | msg: JSON.stringify(encMsg), 252 | time 253 | })); 254 | gun.get('pchat').get(gun.user().is.pub).get(pubKey).get('latest') 255 | .put(JSON.stringify({ 256 | msg: JSON.stringify(encMsg), 257 | time 258 | })); 259 | } 260 | 261 | async loadMessagesOfContact(pubKey, publicName, cb) { 262 | if (!pubKey || !cb) return; 263 | const gun = this.gun; 264 | this.activeContact = pubKey; 265 | this.activeChannel = null; 266 | const thisChat = this; 267 | const loadedMsgs = {}; 268 | const loadedMsgsList = []; 269 | const otherPeer = await gun.user(pubKey); 270 | let otherPeerEpub = otherPeer.epub; 271 | if(otherPeer.epub[2] === ":"){ 272 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 273 | } 274 | async function loadMsgsOf(path, name) { 275 | path.not((key) => { 276 | cb(loadedMsgsList); 277 | }); 278 | path.on((msgs) => { 279 | if (!msgs) return; 280 | Object.keys(msgs).forEach((time) => { 281 | if (loadedMsgs[time]) return; 282 | path.get(time) 283 | .on(async (msgDataString) => { 284 | if (thisChat.activeContact !== pubKey || !msgDataString || msgDataString === "null" || loadedMsgs[time]) return; 285 | loadedMsgs[time] = true; 286 | let msgData = msgDataString; 287 | if (typeof msgDataString === 'string') { 288 | msgData = JSON.parse(msgDataString); 289 | } 290 | if (!msgData || !msgData.msg) return; 291 | if (typeof msgData.msg === 'string') { 292 | msgData.msg = JSON.parse(msgData.msg.substr(3, msgData.msg.length)); 293 | } 294 | const sec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 295 | const decMsg = await Gun.SEA.decrypt(msgData.msg, sec); 296 | if (!decMsg) return; 297 | loadedMsgsList.push({ 298 | time: msgData.time, 299 | msg: decMsg, 300 | owner: name 301 | }); 302 | loadedMsgsList.sort((a, b) => a.time - b.time); 303 | gun.get('pchat').get(gun.user().is.pub).get(pubKey).get('new').get(msgData.time).put("disabled"); 304 | cb(loadedMsgsList); 305 | }); 306 | }); 307 | }); 308 | } 309 | loadMsgsOf(gun.user().get('pchat') 310 | .get(pubKey), this.publicName); 311 | loadMsgsOf(gun.user(pubKey).get('pchat').get(gun.user()._.sea.pub), publicName); 312 | } 313 | 314 | async createChannel(channelName, cb) { 315 | const gun = this.gun; 316 | const channelPair = await Gun.SEA.pair(); 317 | const channelKey = channelPair.epub; 318 | const sec = await Gun.SEA.secret(channelKey, gun.user()._.sea); 319 | const encPair = await Gun.SEA.encrypt(JSON.stringify(channelPair), sec); 320 | const channel = { 321 | pair: encPair, 322 | name: channelName, 323 | key: channelKey, 324 | peers: {} 325 | } 326 | gun.user().get('pchannel').get(channelKey).put(channel, () => { 327 | const userPeer = JSON.stringify({ 328 | alias: gun.user().is.alias, 329 | name: this.publicName, 330 | joined: true, 331 | disabled : false 332 | }) 333 | gun.user().get('pchannel').get(channelKey).get('peers') 334 | .get(gun.user().is.pub) 335 | .put(userPeer, () => { 336 | channel.peers[gun.user().is.pub] = userPeer; 337 | if(cb && typeof cb === 'function'){ 338 | channel.pair = channelPair; 339 | cb(channel) 340 | } 341 | }); 342 | }); 343 | } 344 | 345 | async leaveChannel(channel) { 346 | if (!channel) return; 347 | const gun = this.gun; 348 | const leaveMsg = `${this.publicName} has left the chat.`; 349 | this.sendMessageToChannel(channel, leaveMsg, { 350 | pubKey: gun.user().is.pub, 351 | alias: gun.user().is.alias, 352 | name: this.publicName, 353 | action: 'leave' 354 | }); 355 | gun.user().get('pchannel').get(channel.key) 356 | .put({disabled : true}); 357 | } 358 | 359 | async loadChannels(cb) { 360 | if (!cb) return; 361 | const gun = this.gun; 362 | const loadedChannels = {}; 363 | const loadedChannelsList = this.channelsList; 364 | gun.user().get('pchannel').not((key) => { 365 | cb(loadedChannelsList); 366 | }); 367 | gun.user().get('pchannel') 368 | .on(async (channels) => { 369 | if (!channels) return; 370 | Object.keys(channels).forEach(async (channelKey) => { 371 | if (channelKey === "_" || loadedChannels[channelKey]) return; 372 | Gun.SEA.secret(channelKey, gun.user()._.sea, (sec) => { 373 | gun.user().get('pchannel').get(channelKey).on((channel) => { 374 | if (!channel || !channel.key || (channel && channel.key && !channel.disabled && loadedChannels[channelKey])) return; 375 | if(channel.disabled && loadedChannels[channelKey]){ 376 | const index = loadedChannelsList.map(c => c.key).indexOf(channelKey); 377 | loadedChannelsList.splice(index, 1); 378 | loadedChannels[channelKey] = false; 379 | cb(loadedChannelsList); 380 | } 381 | else if(!channel.disabled && channel.name && !loadedChannels[channelKey]){ 382 | const loadedPeers = {}; 383 | gun.user().get('pchannel').get(channelKey).get('peers') 384 | .once(async (peers) => { 385 | if(!peers || loadedChannels[channelKey]) return; 386 | loadedChannels[channelKey] = true; 387 | const pair = await Gun.SEA.decrypt(channel.pair, sec); 388 | const loadedChannelIndex = loadedChannelsList.length; 389 | loadedChannelsList.push({ 390 | key: channelKey, 391 | name: channel.name, 392 | userCount: 0, 393 | latestMsg: null, 394 | peers : loadedPeers, 395 | pair, 396 | }); 397 | cb(loadedChannelsList); 398 | Object.keys(peers).forEach((pubKey) => { 399 | if(pubKey === '_' || loadedPeers[pubKey]) return; 400 | gun.user().get('pchannel').get(channelKey).get('peers') 401 | .get(pubKey).once((peerData) => { 402 | if(!peerData || peerData.disabled || loadedPeers[pubKey]) return; 403 | loadedPeers[pubKey] = peerData; 404 | loadedChannelsList[loadedChannelIndex].peers = loadedPeers; 405 | cb(loadedChannelsList); 406 | }); 407 | }); 408 | gun.get('pchannel').get(channelKey).get('peers').get(gun.user().is.pub) 409 | .get('new') 410 | .on((newMsgs) => { 411 | if (!newMsgs) return; 412 | let newCount = 0; 413 | Object.keys(newMsgs).forEach((time) => { 414 | if (time === '_' || time === "disabled" || !newMsgs[time] || newMsgs[time] === "disabled") { 415 | return; 416 | } 417 | newCount += 1; 418 | }); 419 | if(loadedChannelsList[loadedChannelIndex]){ 420 | loadedChannelsList[loadedChannelIndex].notifCount = newCount; 421 | } 422 | cb(loadedChannelsList); 423 | }); 424 | }); 425 | } 426 | }); 427 | }); 428 | }); 429 | }); 430 | } 431 | 432 | async inviteToChannel(channel, username, peerPubKey, publicName) { 433 | if (!channel || !username || !publicName || !this.gun.user().is) return; 434 | const gun = this.gun; 435 | this.validatePubKeyFromUsername(username, peerPubKey, async (err) => { 436 | if(err) return; 437 | const otherPeer = await gun.user(peerPubKey); 438 | let otherPeerEpub = otherPeer.epub; 439 | if(otherPeer.epub[2] === ":"){ 440 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 441 | } 442 | const inviteSec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 443 | const eInvitePair = await Gun.SEA.encrypt( 444 | JSON.stringify(channel.pair), 445 | inviteSec, 446 | ); 447 | const channelInvite = {...channel, peerName : this.publicName}; 448 | channelInvite.pair = eInvitePair; 449 | gun.get(peerPubKey).get('invites').get('pchannel').get(gun.user()._.sea.pub) 450 | .get(channel.key) 451 | .put(JSON.stringify(channelInvite)); 452 | this.sendMessageToChannel(channel, `${publicName} has been invited.`, { 453 | pubKey: peerPubKey, 454 | alias: username, 455 | name: publicName, 456 | action: 'invited' 457 | }); 458 | gun.user().get('pchannel').get(channel.key).get('peers') 459 | .get(peerPubKey) 460 | .put(JSON.stringify({ 461 | alias: username, 462 | name: publicName, 463 | joined: false, 464 | disabled: false 465 | })); 466 | }); 467 | } 468 | 469 | async loadChannelInvites(cb) { 470 | if (!cb || !this.gun.user().is) return; 471 | const gun = this.gun; 472 | const loadedInvites = {}; 473 | const loadedInvitesList = this.channelInvitesList; 474 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').not((key) => { 475 | cb(loadedInvitesList); 476 | }); 477 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel') 478 | .on(async (peerInvites) => { 479 | if (!peerInvites) return; 480 | Object.keys(peerInvites).forEach((peerPub) => { 481 | if (peerPub === '_') return; 482 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel').get(peerPub) 483 | .on(async (channels) => { 484 | if (!channels || channels === "disabled") return; 485 | Object.keys(channels).forEach(async (channelKey) => { 486 | const channel = (typeof channels[channelKey] === 'string' && channels[channelKey] !== "disabled") ? JSON.parse(channels[channelKey]) : channels[channelKey]; 487 | if (channelKey === '_' || !channel || (channel && channel.key && loadedInvites[channelKey])) return; 488 | if(channel === "disabled" && loadedInvites[channelKey]){ 489 | const index = loadedInvitesList.map(c => c.key).indexOf(channelKey); 490 | loadedInvitesList.splice(index, 1); 491 | loadedInvites[channelKey] = false; 492 | } 493 | else if(channel.key && !loadedInvites[channelKey]){ 494 | loadedInvites[channelKey] = channelKey; 495 | const peerKeys = await gun.user(peerPub).then(); 496 | const peerEpub = peerKeys ? peerKeys.epub : null; 497 | const sec = await Gun.SEA.secret(peerEpub, gun.user()._.sea); 498 | if (typeof channel.pair === 'string') { 499 | channel.pair = JSON.parse(channel.pair.substr(3, channel.pair.length)); 500 | } 501 | channel.pair = await Gun.SEA.decrypt(channel.pair, sec); 502 | channel.peerPub = peerPub; 503 | channel.peerAlias = peerKeys.alias; 504 | channel.key = channelKey; 505 | loadedInvitesList.push(channel); 506 | } 507 | cb(loadedInvitesList); 508 | }); 509 | }); 510 | }); 511 | }); 512 | } 513 | 514 | async acceptChannelInvite(invite) { 515 | if (!invite) return; 516 | const gun = this.gun; 517 | gun.user().get('pchannel').get(invite.key).get('peers') 518 | .get(gun.user().is.pub) 519 | .put(JSON.stringify({ 520 | alias: gun.user().is.alias, 521 | name: this.publicName, 522 | joined: true, 523 | key: invite.key, 524 | peerPub: invite.peerPub 525 | })); 526 | gun.user().get('pchannel').get(invite.key) 527 | .get('peers') 528 | .get(invite.peerPub) 529 | .put(JSON.stringify({ 530 | alias: invite.peerAlias, 531 | name: invite.peerName, 532 | joined: true, 533 | key: invite.key, 534 | peerPub: invite.peerPub 535 | })); 536 | const sec = await Gun.SEA.secret(invite.key, gun.user()._.sea); 537 | const encPair = await Gun.SEA.encrypt(invite.pair, sec); 538 | gun.user().get('pchannel').get(invite.key).put({ 539 | pair: encPair, 540 | name: invite.name, 541 | key: invite.key, 542 | disabled: false 543 | }); 544 | const loadedPeers = {}; 545 | Object.keys(invite.peers).forEach((pubKey) => { 546 | if (pubKey === '_') return; 547 | const peer = invite.peers[pubKey]; 548 | if (loadedPeers[pubKey] || !peer || peer.disabled) return; 549 | loadedPeers[pubKey] = pubKey; 550 | gun.user().get('pchannel').get(invite.key) 551 | .get('peers') 552 | .get(pubKey) 553 | .put(JSON.stringify(peer)); 554 | }); 555 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel') 556 | .get(invite.peerPub) 557 | .get(invite.key) 558 | .put("disabled"); 559 | const channel = invite; 560 | if (!channel.peers[gun.user().is.pub]) { 561 | channel.peers[gun.user().is.pub] = { alias: gun.user().is.alias }; 562 | } 563 | channel.peers[gun.user().is.pub].joined = true; 564 | const joinMsg = `${this.publicName} has joined the chat!`; 565 | this.sendMessageToChannel(channel, joinMsg, { 566 | pubKey: gun.user().is.pub, 567 | alias: gun.user().is.alias, 568 | name: this.publicName, 569 | action: 'join' 570 | }); 571 | const inviteIndex = this.channelInvitesList.findIndex((c) => c.key === invite.key); 572 | this.channelInvitesList.splice(inviteIndex, 1); 573 | } 574 | 575 | async denyChannelInvite(invite) { 576 | if (!invite) return; 577 | const gun = this.gun; 578 | gun.get(gun.user()._.sea.pub).get('invites').get('pchannel') 579 | .get(invite.peerPub) 580 | .get(invite.key) 581 | .put("disabled"); 582 | } 583 | 584 | async sendMessageToChannel(channel, msg, peerInfo) { 585 | if (!channel || msg.length < 1) return; 586 | const gun = this.gun; 587 | const time = Date.now(); 588 | const sec = await Gun.SEA.secret(channel.key, channel.pair); 589 | const encMsg = await Gun.SEA.encrypt(msg, sec); 590 | const channelChatToSend = gun.user().get('pchannel').get(channel.key) 591 | .get('chat'); 592 | channelChatToSend.get(time) 593 | .put(JSON.stringify({ 594 | msg: encMsg, 595 | userPub: gun.user().is.pub, 596 | userName: this.publicName, 597 | time, 598 | peerInfo, 599 | })); 600 | gun.get('pchannel').get(channel.key).get('latest') 601 | .put({ 602 | msg: encMsg, 603 | user: gun.user().is.pub, 604 | time, 605 | peerInfo, 606 | }); 607 | if (!channel.peers) return; 608 | Object.keys(channel.peers).forEach((pubKey) => { 609 | if (pubKey !== '_' && channel.peers[pubKey] && pubKey !== gun.user().is.pub) { 610 | gun.get('pchannel').get(channel.key).get('peers').get(pubKey) 611 | .get('new') 612 | .get(time) 613 | .put(JSON.stringify({ 614 | msg: encMsg, 615 | user: gun.user().is.pub, 616 | time 617 | })); 618 | } 619 | }); 620 | } 621 | 622 | async loadMessagesOfChannel(channel, cb) { 623 | if (!channel || !cb) return; 624 | const gun = this.gun; 625 | this.activeChannel = channel.key; 626 | this.activeContact = null; 627 | const thisChat = this; 628 | const channelKey = channel.key; 629 | const loadedMsgsList = []; 630 | const loadedMsgs = {}; 631 | const channelSec = await Gun.SEA.secret(channel.key, channel.pair); 632 | async function loadMsgsOf(path, name) { 633 | path.not((key) => { 634 | cb(loadedMsgsList); 635 | }); 636 | path.on((peerMsgs) => { 637 | if (!peerMsgs) return; 638 | Object.keys(peerMsgs).forEach((time) => { 639 | if (loadedMsgs[time + name] || time === '_') return; 640 | path.get(time) 641 | .on(async (msgDataString) => { 642 | if (thisChat.activeChannel !== channel.key || loadedMsgs[time + name]) return; 643 | loadedMsgs[time + name] = true; 644 | let msgData = msgDataString; 645 | if (typeof msgDataString === 'string') { 646 | msgData = JSON.parse(msgDataString); 647 | } 648 | if (typeof msgData.msg === 'string') { 649 | msgData.msg = JSON.parse(msgData.msg.substr(3, msgData.msg.length)); 650 | } 651 | const decMsg = await Gun.SEA.decrypt(msgData.msg, channelSec); 652 | if (!msgData || !msgData.msg || !decMsg || !msgData.userPub) return; 653 | if (msgData.peerInfo) { 654 | if (typeof msgData.peerInfo === 'string') { 655 | msgData.peerInfo = JSON.parse(msgData.peerInfo); 656 | } 657 | if (msgData.peerInfo.action === 'join') { 658 | channel.peers[msgData.peerInfo.pubKey] = { 659 | alias: msgData.peerInfo.alias, 660 | pubKey: msgData.peerInfo.pubKey, 661 | name: msgData.peerInfo.name, 662 | joined: true, 663 | disabled: false 664 | }; 665 | gun.user().get('pchannel').get(channelKey).get('peers') 666 | .get(msgData.peerInfo.pubKey) 667 | .put(JSON.stringify(channel.peers[msgData.peerInfo.pubKey])); 668 | } else if (msgData.peerInfo.action === 'leave') { 669 | gun.user().get('pchannel').get(channel.key).get('peers') 670 | .get(msgData.peerInfo.pubKey) 671 | .put("disabled"); 672 | } else if (msgData.peerInfo.action === 'invited') { 673 | let peerObj = { 674 | alias: msgData.peerInfo.alias, 675 | pubKey: msgData.peerInfo.pubKey, 676 | name: msgData.peerInfo.name, 677 | disabled: false 678 | }; 679 | if(channel.peers[msgData.peerInfo.pubKey]){ 680 | peerObj.joined = channel.peers[msgData.peerInfo.pubKey].joined; 681 | } 682 | gun.user().get('pchannel').get(channelKey).get('peers') 683 | .get(msgData.peerInfo.pubKey) 684 | .put(JSON.stringify(peerObj)); 685 | } 686 | } 687 | loadedMsgsList.push({ 688 | time: msgData.time, 689 | userPub: msgData.userPub, 690 | owner: name, 691 | msg: decMsg, 692 | peerInfo: msgData.peerInfo 693 | }); 694 | loadedMsgsList.sort((a, b) => a.time - b.time); 695 | gun.get('pchannel').get(channel.key).get('peers') 696 | .get(gun.user().is.pub) 697 | .get('new') 698 | .get(msgData.time) 699 | .put("disabled"); 700 | cb(loadedMsgsList); 701 | }); 702 | }); 703 | }); 704 | } 705 | const loadedPeers = {}; 706 | gun.user().get('pchannel').get(channel.key).get('peers').on((peers) => { 707 | Object.keys(peers).forEach((pubKey) => { 708 | if(pubKey === '_' || !peers[pubKey] || typeof peers[pubKey] !== 'string') return; 709 | let peer; 710 | if(peers[pubKey] !== "disabled") { 711 | peer = JSON.parse(peers[pubKey]); 712 | if(typeof peer === 'string'){ 713 | peer = JSON.parse(peer); 714 | } 715 | }else if(peers[pubKey] === "disabled" && loadedPeers[pubKey]){ 716 | delete channel.peers[pubKey]; 717 | loadedPeers[pubKey] = false; 718 | return; 719 | } 720 | const peerChannelChatPath = gun.user(pubKey).get('pchannel') 721 | .get(channelKey) 722 | .get('chat'); 723 | if(!peer || !peer.name || (peer.name && !peer.disabled && loadedPeers[pubKey])) return; 724 | else if(!peer.disabled && peer.name && !loadedPeers[pubKey]){ 725 | loadedPeers[pubKey] = true; 726 | channel.peers[pubKey] = peer; 727 | loadMsgsOf(peerChannelChatPath, peer.name); 728 | } 729 | }); 730 | }); 731 | } 732 | 733 | async createAnnouncement(announcementName, cb) { 734 | const gun = this.gun; 735 | const publicName = this.publicName; 736 | const announcementPair = await Gun.SEA.pair(); 737 | const announcementKey = announcementPair.epub; 738 | const sec = await Gun.SEA.secret(announcementKey, gun.user()._.sea); 739 | const encPair = await Gun.SEA.encrypt(JSON.stringify(announcementPair), sec); 740 | const announcement = { 741 | pair: encPair, 742 | name: announcementName, 743 | key: announcementKey, 744 | owner: gun.user()._.sea.pub, 745 | peers: {}, 746 | admins: {} 747 | } 748 | gun.user().get('announcement').get(announcementKey).put(announcement, () => { 749 | gun.user().get('announcement').get(announcementKey).get('admins').get(gun.user().is.pub).put(this.publicName); 750 | const userPeer = JSON.stringify({ 751 | alias: gun.user().is.alias, 752 | name: this.publicName, 753 | joined: true, 754 | disabled : false, 755 | pubKey : gun.user().is.pub 756 | }) 757 | gun.user().get('announcement').get(announcementKey).get('peers') 758 | .get(gun.user().is.pub) 759 | .put(userPeer); 760 | if(cb && typeof cb === 'function'){ 761 | announcement.admins[gun.user().is.pub] = publicName; 762 | announcement.peers[gun.user().is.pub] = userPeer; 763 | announcement.pair = announcementPair; 764 | cb(announcement); 765 | } 766 | }); 767 | } 768 | 769 | async leaveAnnouncement(announcement) { 770 | if (!announcement) return; 771 | const gun = this.gun; 772 | const leaveMsg = `${this.publicName} has left the chat.`; 773 | this.sendMessageToAnnouncement(announcement, leaveMsg, { 774 | pubKey: gun.user().is.pub, 775 | alias: gun.user().is.alias, 776 | name: this.publicName, 777 | action: 'leave' 778 | }); 779 | gun.user().get('announcement').get(announcement.key) 780 | .put({disabled : true}); 781 | } 782 | 783 | async loadAnnouncements(cb) { 784 | if (!cb) return; 785 | const gun = this.gun; 786 | const loadedAnnouncements = {}; 787 | const loadedAnnouncementsList = this.announcementsList; 788 | gun.user().get('announcement').not((key) => { 789 | cb(loadedAnnouncementsList); 790 | }); 791 | gun.user().get('announcement') 792 | .on(async (announcements) => { 793 | if (!announcements) return; 794 | Object.keys(announcements).forEach(async (announcementKey) => { 795 | if (announcementKey === "_" || loadedAnnouncements[announcementKey]) return; 796 | Gun.SEA.secret(announcementKey, gun.user()._.sea, (sec) => { 797 | gun.user().get('announcement').get(announcementKey).on((announcement) => { 798 | if (!announcement || !announcement.key || (announcement && announcement.key && !announcement.disabled && loadedAnnouncements[announcementKey])) return; 799 | if(announcement.disabled && loadedAnnouncements[announcementKey]){ 800 | const index = loadedAnnouncementsList.map(c => c.key).indexOf(announcementKey); 801 | loadedAnnouncementsList.splice(index, 1); 802 | loadedAnnouncements[announcementKey] = false; 803 | cb(loadedAnnouncementsList); 804 | } 805 | else if(!announcement.disabled && announcement.name && !loadedAnnouncements[announcementKey]){ 806 | const loadedPeers = {}; 807 | const loadedAdmins = {}; 808 | let loadedAnnouncementIndex; 809 | gun.user().get('announcement').get(announcementKey).get('peers') 810 | .once(async (peers) => { 811 | if(!peers || loadedAnnouncements[announcementKey]) return; 812 | gun.user().get('announcement').get(announcementKey).get('admins').on(async (admins) => { 813 | if(!admins) return; 814 | if(!loadedAnnouncements[announcementKey]){ 815 | loadedAnnouncements[announcementKey] = true; 816 | const pair = await Gun.SEA.decrypt(announcement.pair, sec); 817 | loadedAnnouncementIndex = loadedAnnouncementsList.length; 818 | loadedAnnouncementsList.push({ 819 | key: announcementKey, 820 | name: announcement.name, 821 | owner: announcement.owner, 822 | userCount: 0, 823 | latestMsg: null, 824 | peers : loadedPeers, 825 | admins: loadedAdmins, 826 | pair, 827 | }); 828 | cb(loadedAnnouncementsList); 829 | } 830 | if(typeof loadedAnnouncementIndex === 'undefined') return 831 | Object.keys(peers).forEach((pubKey) => { 832 | if(pubKey === '_' || loadedPeers[pubKey]) return; 833 | gun.user().get('announcement').get(announcementKey).get('peers') 834 | .get(pubKey).once((peerData) => { 835 | if(!peerData || peerData.disabled || loadedPeers[pubKey]) return; 836 | loadedPeers[pubKey] = peerData; 837 | loadedAnnouncementsList[loadedAnnouncementIndex].peers = loadedPeers; 838 | cb(loadedAnnouncementsList); 839 | }); 840 | }); 841 | Object.keys(admins).forEach((pubKey) => { 842 | if(pubKey === '_' || loadedAdmins[pubKey]) return; 843 | gun.user().get('announcement').get(announcementKey).get('admins') 844 | .get(pubKey).once((name) => { 845 | if(name === "disabled" || loadedAdmins[pubKey]) return; 846 | loadedAdmins[pubKey] = name; 847 | loadedAnnouncementsList[loadedAnnouncementIndex].admins = loadedAdmins; 848 | cb(loadedAnnouncementsList); 849 | }); 850 | }); 851 | gun.get('announcement').get(announcementKey).get('peers').get(gun.user().is.pub) 852 | .get('new') 853 | .on((newMsgs) => { 854 | if (!newMsgs) return; 855 | let newCount = 0; 856 | Object.keys(newMsgs).forEach((time) => { 857 | if (time === '_' || time === "disabled" || !newMsgs[time] || newMsgs[time] === "disabled") { 858 | return; 859 | } 860 | newCount += 1; 861 | }); 862 | if(loadedAnnouncementsList[loadedAnnouncementIndex]){ 863 | loadedAnnouncementsList[loadedAnnouncementIndex].notifCount = newCount; 864 | } 865 | cb(loadedAnnouncementsList); 866 | }); 867 | }); 868 | }); 869 | } 870 | }); 871 | }); 872 | }); 873 | }); 874 | } 875 | 876 | async inviteToAnnouncement(announcement, username, peerPubKey, publicName) { 877 | if (!announcement || !username || !publicName || !this.gun.user().is) return; 878 | const gun = this.gun; 879 | this.validatePubKeyFromUsername(username, peerPubKey, async (err) => { 880 | if(err) return; 881 | const otherPeer = await gun.user(peerPubKey); 882 | let otherPeerEpub = otherPeer.epub; 883 | if(otherPeer.epub[2] === ":"){ 884 | otherPeerEpub = JSON.parse(otherPeer.epub)[":"]; 885 | } 886 | const inviteSec = await Gun.SEA.secret(otherPeerEpub, gun.user()._.sea); 887 | const eInvitePair = await Gun.SEA.encrypt( 888 | JSON.stringify(announcement.pair), 889 | inviteSec, 890 | ); 891 | const announcementInvite = {...announcement, peerName : this.publicName}; 892 | announcementInvite.pair = eInvitePair; 893 | gun.get(peerPubKey).get('invites').get('announcement').get(gun.user()._.sea.pub) 894 | .get(announcement.key) 895 | .put(JSON.stringify(announcementInvite)); 896 | this.sendMessageToAnnouncement(announcement, `${publicName} has been invited.`, { 897 | pubKey: peerPubKey, 898 | alias: username, 899 | name: publicName, 900 | action: 'invited' 901 | }); 902 | gun.user().get('announcement').get(announcement.key).get('peers') 903 | .get(peerPubKey) 904 | .put(JSON.stringify({ 905 | alias: username, 906 | name: publicName, 907 | joined: false, 908 | disabled: false, 909 | pubKey: peerPubKey 910 | })); 911 | }); 912 | } 913 | 914 | async loadAnnouncementInvites(cb) { 915 | if (!cb || !this.gun.user().is) return; 916 | const gun = this.gun; 917 | const loadedInvites = {}; 918 | const loadedInvitesList = this.announcementInvitesList; 919 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement').not((key) => { 920 | cb(loadedInvitesList); 921 | }); 922 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement') 923 | .on(async (peerInvites) => { 924 | if (!peerInvites) return; 925 | Object.keys(peerInvites).forEach((peerPub) => { 926 | if (peerPub === '_') return; 927 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement').get(peerPub) 928 | .on(async (announcements) => { 929 | if (!announcements || announcements === "disabled") return; 930 | Object.keys(announcements).forEach(async (announcementKey) => { 931 | const announcement = (typeof announcements[announcementKey] === 'string' && announcements[announcementKey] !== "disabled") ? JSON.parse(announcements[announcementKey]) : announcements[announcementKey]; 932 | if (announcementKey === '_' || !announcement || (announcement && announcement.key && loadedInvites[announcementKey])) return; 933 | if(announcement === "disabled" && loadedInvites[announcementKey]){ 934 | const index = loadedInvitesList.map(c => c.key).indexOf(announcementKey); 935 | loadedInvitesList.splice(index, 1); 936 | loadedInvites[announcementKey] = false; 937 | } 938 | else if(announcement.key && !loadedInvites[announcementKey]){ 939 | loadedInvites[announcementKey] = announcementKey; 940 | const peerKeys = await gun.user(peerPub).then(); 941 | const peerEpub = peerKeys ? peerKeys.epub : null; 942 | const sec = await Gun.SEA.secret(peerEpub, gun.user()._.sea); 943 | if (typeof announcement.pair === 'string') { 944 | announcement.pair = JSON.parse(announcement.pair.substr(3, announcement.pair.length)); 945 | } 946 | announcement.pair = await Gun.SEA.decrypt(announcement.pair, sec); 947 | announcement.peerPub = peerPub; 948 | announcement.peerAlias = peerKeys.alias; 949 | announcement.key = announcementKey; 950 | loadedInvitesList.push(announcement); 951 | } 952 | cb(loadedInvitesList); 953 | }); 954 | }); 955 | }); 956 | }); 957 | } 958 | 959 | async acceptAnnouncementInvite(invite) { 960 | if (!invite) return; 961 | const gun = this.gun; 962 | gun.user().get('announcement').get(invite.key).get('peers') 963 | .get(gun.user().is.pub) 964 | .put(JSON.stringify({ 965 | alias: gun.user().is.alias, 966 | name: this.publicName, 967 | joined: true, 968 | key: invite.key, 969 | peerPub: invite.peerPub 970 | })); 971 | gun.user().get('announcement').get(invite.key) 972 | .get('peers') 973 | .get(invite.peerPub) 974 | .put(JSON.stringify({ 975 | alias: invite.peerAlias, 976 | name: invite.peerName, 977 | joined: true, 978 | key: invite.key, 979 | peerPub: invite.peerPub 980 | })); 981 | const sec = await Gun.SEA.secret(invite.key, gun.user()._.sea); 982 | const encPair = await Gun.SEA.encrypt(invite.pair, sec); 983 | gun.user().get('announcement').get(invite.key).put({ 984 | pair: encPair, 985 | name: invite.name, 986 | key: invite.key, 987 | disabled: false, 988 | owner: invite.owner, 989 | }); 990 | const loadedPeers = {}; 991 | Object.keys(invite.peers).forEach((pubKey) => { 992 | if (pubKey === '_') return; 993 | const peer = invite.peers[pubKey]; 994 | if (loadedPeers[pubKey] || !peer || peer.disabled) return; 995 | loadedPeers[pubKey] = pubKey; 996 | gun.user().get('announcement').get(invite.key) 997 | .get('peers') 998 | .get(pubKey) 999 | .put(JSON.stringify(peer)); 1000 | }); 1001 | const loadedAdmins = {}; 1002 | Object.keys(invite.admins).forEach((pubKey) => { 1003 | if (pubKey === '_') return; 1004 | const admin = invite.admins[pubKey]; 1005 | if (loadedAdmins[pubKey] || !admin || admin === 'disabled') return; 1006 | loadedAdmins[pubKey] = admin; 1007 | gun.user().get('announcement').get(invite.key) 1008 | .get('admins') 1009 | .get(pubKey) 1010 | .put(admin); 1011 | }); 1012 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement') 1013 | .get(invite.peerPub) 1014 | .get(invite.key) 1015 | .put("disabled"); 1016 | const announcement = invite; 1017 | if (!announcement.peers[gun.user().is.pub]) { 1018 | announcement.peers[gun.user().is.pub] = { alias: gun.user().is.alias }; 1019 | } 1020 | announcement.peers[gun.user().is.pub].joined = true; 1021 | const joinMsg = `${this.publicName} has joined the chat!`; 1022 | this.sendMessageToAnnouncement(announcement, joinMsg, { 1023 | pubKey: gun.user().is.pub, 1024 | alias: gun.user().is.alias, 1025 | name: this.publicName, 1026 | action: 'join' 1027 | }); 1028 | const inviteIndex = this.announcementInvitesList.findIndex((c) => c.key === invite.key); 1029 | this.announcementInvitesList.splice(inviteIndex, 1); 1030 | } 1031 | 1032 | async denyAnnouncementInvite(invite) { 1033 | if (!invite) return; 1034 | const gun = this.gun; 1035 | gun.get(gun.user()._.sea.pub).get('invites').get('announcement') 1036 | .get(invite.peerPub) 1037 | .get(invite.key) 1038 | .put("disabled"); 1039 | } 1040 | 1041 | async sendMessageToAnnouncement(announcement, msg, peerInfo) { 1042 | if (!announcement || msg.length < 1) return; 1043 | const gun = this.gun; 1044 | const isAdmin = (announcement.admins[gun.user().is.pub] && announcement.admins[gun.user().is.pub] !== "disabled"); 1045 | if(!isAdmin && !peerInfo) return; 1046 | const time = Date.now(); 1047 | const sec = await Gun.SEA.secret(announcement.key, announcement.pair); 1048 | const encMsg = await Gun.SEA.encrypt(msg, sec); 1049 | const announcementChatToSend = gun.user().get('announcement').get(announcement.key) 1050 | .get('chat'); 1051 | announcementChatToSend.get(time) 1052 | .put(JSON.stringify({ 1053 | msg: encMsg, 1054 | userPub: gun.user().is.pub, 1055 | userName: this.publicName, 1056 | time, 1057 | peerInfo, 1058 | })); 1059 | if(isAdmin){ 1060 | gun.get('announcement').get(announcement.key).get('latest') 1061 | .put({ 1062 | msg: encMsg, 1063 | user: gun.user().is.pub, 1064 | time, 1065 | peerInfo, 1066 | }); 1067 | if (!announcement.peers) return; 1068 | Object.keys(announcement.peers).forEach((pubKey) => { 1069 | if (pubKey !== '_' && announcement.peers[pubKey] && pubKey !== gun.user().is.pub) { 1070 | gun.get('announcement').get(announcement.key).get('peers').get(pubKey) 1071 | .get('new') 1072 | .get(time) 1073 | .put(JSON.stringify({ 1074 | msg: encMsg, 1075 | user: gun.user().is.pub, 1076 | time 1077 | })); 1078 | } 1079 | }); 1080 | } 1081 | } 1082 | 1083 | async loadMessagesOfAnnouncement(announcement, cb) { 1084 | if (!announcement || !cb) return; 1085 | const gun = this.gun; 1086 | this.activeAnnouncement = announcement.key; 1087 | this.activeContact = null; 1088 | const thisChat = this; 1089 | const announcementKey = announcement.key; 1090 | const loadedMsgsList = []; 1091 | const loadedMsgs = {}; 1092 | const announcementSec = await Gun.SEA.secret(announcement.key, announcement.pair); 1093 | async function loadMsgsOf(path, name) { 1094 | path.not((key) => { 1095 | cb(loadedMsgsList); 1096 | }); 1097 | path.on((peerMsgs) => { 1098 | if (!peerMsgs) return; 1099 | Object.keys(peerMsgs).forEach((time) => { 1100 | if (loadedMsgs[time + name] || time === '_') return; 1101 | path.get(time) 1102 | .on(async (msgDataString) => { 1103 | if (thisChat.activeAnnouncement !== announcement.key || loadedMsgs[time + name]) return; 1104 | loadedMsgs[time + name] = true; 1105 | let msgData = msgDataString; 1106 | if (typeof msgDataString === 'string') { 1107 | msgData = JSON.parse(msgDataString); 1108 | } 1109 | if (typeof msgData.msg === 'string') { 1110 | msgData.msg = JSON.parse(msgData.msg.substr(3, msgData.msg.length)); 1111 | } 1112 | const decMsg = await Gun.SEA.decrypt(msgData.msg, announcementSec); 1113 | if (!msgData || !msgData.msg || !decMsg || !msgData.userPub) return; 1114 | if (msgData.peerInfo) { 1115 | if (typeof msgData.peerInfo === 'string') { 1116 | msgData.peerInfo = JSON.parse(msgData.peerInfo); 1117 | } 1118 | if (msgData.peerInfo.action === 'join') { 1119 | announcement.peers[msgData.peerInfo.pubKey] = { 1120 | alias: msgData.peerInfo.alias, 1121 | pubKey: msgData.peerInfo.pubKey, 1122 | name: msgData.peerInfo.name, 1123 | joined: true, 1124 | disabled: false 1125 | }; 1126 | gun.user().get('announcement').get(announcementKey).get('peers') 1127 | .get(msgData.peerInfo.pubKey) 1128 | .put(JSON.stringify(announcement.peers[msgData.peerInfo.pubKey])); 1129 | } else if (msgData.peerInfo.action === 'leave') { 1130 | gun.user().get('announcement').get(announcement.key).get('peers') 1131 | .get(msgData.peerInfo.pubKey) 1132 | .put("disabled"); 1133 | } else if (msgData.peerInfo.action === 'invited') { 1134 | let peerObj = { 1135 | alias: msgData.peerInfo.alias, 1136 | pubKey: msgData.peerInfo.pubKey, 1137 | name: msgData.peerInfo.name, 1138 | disabled: false 1139 | }; 1140 | if(announcement.peers[msgData.peerInfo.pubKey]){ 1141 | peerObj.joined = announcement.peers[msgData.peerInfo.pubKey].joined; 1142 | } 1143 | gun.user().get('announcement').get(announcementKey).get('peers') 1144 | .get(msgData.peerInfo.pubKey) 1145 | .put(JSON.stringify(peerObj)); 1146 | } else if (msgData.peerInfo.action === 'newAdmin' && msgData.userPub === announcement.owner) { 1147 | gun.user().get('announcement').get(announcementKey).get('admins') 1148 | .get(msgData.peerInfo.pubKey).put(msgData.peerInfo.name); 1149 | announcement.admins[msgData.peerInfo.pubKey] = msgData.peerInfo.name; 1150 | } 1151 | } 1152 | if(msgData.peerInfo || (announcement.admins[msgData.userPub] && announcement.admins[msgData.userPub] !== "disabled")){ 1153 | loadedMsgsList.push({ 1154 | time: msgData.time, 1155 | userPub: msgData.userPub, 1156 | owner: name, 1157 | msg: decMsg, 1158 | peerInfo: msgData.peerInfo 1159 | }); 1160 | loadedMsgsList.sort((a, b) => a.time - b.time); 1161 | cb(loadedMsgsList); 1162 | } 1163 | gun.get('announcement').get(announcement.key).get('peers') 1164 | .get(gun.user().is.pub) 1165 | .get('new') 1166 | .get(msgData.time) 1167 | .put("disabled"); 1168 | }); 1169 | }); 1170 | }); 1171 | } 1172 | const loadedPeers = {}; 1173 | gun.user().get('announcement').get(announcement.key).get('peers').on((peers) => { 1174 | Object.keys(peers).forEach((pubKey) => { 1175 | if(pubKey === '_' || !peers[pubKey] || typeof peers[pubKey] !== 'string') return; 1176 | let peer; 1177 | if(peers[pubKey] !== "disabled") { 1178 | peer = JSON.parse(peers[pubKey]); 1179 | if(typeof peer === 'string'){ 1180 | peer = JSON.parse(peer); 1181 | } 1182 | }else if(peers[pubKey] === "disabled" && loadedPeers[pubKey]){ 1183 | delete announcement.peers[pubKey]; 1184 | loadedPeers[pubKey] = false; 1185 | return; 1186 | } 1187 | const peerAnnouncementChatPath = gun.user(pubKey).get('announcement') 1188 | .get(announcementKey) 1189 | .get('chat'); 1190 | if(!peer || !peer.name || (peer.name && !peer.disabled && loadedPeers[pubKey])) return; 1191 | else if(!peer.disabled && peer.name && !loadedPeers[pubKey]){ 1192 | loadedPeers[pubKey] = true; 1193 | announcement.peers[pubKey] = peer; 1194 | loadMsgsOf(peerAnnouncementChatPath, peer.name); 1195 | } 1196 | }); 1197 | }); 1198 | } 1199 | 1200 | async addAdminToAnnouncement(announcement, newAdmin){ 1201 | const gun = this.gun; 1202 | gun.user().get('announcement').get(announcement.key).get('owner').once((ownerPub) => { 1203 | if(gun.user().is.pub === ownerPub){ 1204 | let newAdminMsg = `${newAdmin.name} has been made an admin.`; 1205 | this.sendMessageToAnnouncement(announcement, newAdminMsg, { 1206 | pubKey: newAdmin.pubKey, 1207 | name: newAdmin.name, 1208 | action: 'newAdmin' 1209 | }); 1210 | } 1211 | }); 1212 | } 1213 | } 1214 | 1215 | --------------------------------------------------------------------------------