Best JavaScript code snippet using wpt
chatd.js
Source:chatd.js
1// chatd interface2// jshint -W0893var Chatd = function(userId, megaChat, options) {4 var self = this;5 self.megaChat = megaChat;6 // maps the chatd shard number to its corresponding Chatd.Shard object7 self.shards = {};8 // maps a chatId to the handling Chatd.Shard object9 self.chatIdShard = {};10 // maps chatIds to the Message object11 self.chatIdMessages = {};12 // local cache of the Message object13 self.messagesQueueKvStorage = new SharedLocalKVStorage("cqmsgs2");14 /**15 * Set to true when this chatd instance is (being) destroyed16 * @type {boolean}17 */18 self.destroyed = false;19 if (20 (21 ua.details.browser === "Chrome" ||22 ua.details.browser === "Firefox" ||23 ua.details.browser === "Opera"24 ) && ChatdPersist.isMasterTab()25 ) {26 self.chatdPersist = new ChatdPersist(self);27 }28 // random starting point for the new message transaction ID29 // FIXME: use cryptographically strong PRNG instead30 // CHECK: is this sufficiently collision-proof? a collision would have to occur in the same second for the same31 // userId.32 self.msgTransactionId = '';33 self.userId = base64urldecode(userId);34 for (var i = 8; i--;) {35 self.msgTransactionId += String.fromCharCode(Math.random() * 256);36 }37 self.logger = new MegaLogger("chatd", {38 minLogLevel: function() {39 return Chatd.LOGGER_LEVEL;40 }41 });42 // Initialize with a dummy webrtc event handler43 /* jshint -W098 */44 self.rtcHandler = self.defaultRtcHandler = {45 handleMessage: function(shard, msg, len) {},46 onClientLeftCall: function(chat, userid, clientid) {},47 onClientJoinedCall: function(chat, userid, clientid) {},48 onKickedFromChatroom: function(chat) {},49 handleCallData: function(shard, cmd, payloadLen) {50 var type = cmd.charCodeAt(31);51 if (type === CallDataType.kRinging) {52 var chatid = cmd.substr(1, 8);53 var userid = cmd.substr(9, 8);54 var clientid = cmd.substr(17, 4);55 var callid = cmd.substr(23, 8);56 shard.rtcmd(chatid, userid, clientid, RTCMD.CALL_REQ_DECLINE,57 callid + String.fromCharCode(Term.kErrNotSupported));58 }59 },60 onShutdown: function() {},61 onChatOnline: function(chat) {}62 };63 /* jshint +W098 */64 // load persistent client id, or generate one if none was stored65 // self.identity = localStorage['chatdIdentity'];66 // if (!self.identity) {67 self.identity = Chatd.pack32le((Math.random() * 0xffffffff) | 0) +68 Chatd.pack16le((Math.random() * 0xffff) | 0) +69 Chatd.pack16le(Date.now() & 0xffff);70 localStorage.setItem('chatdIdentity', base64urlencode(self.identity));71 self.logger.debug("Generated new client identity: " + Chatd.clientIdToString(self.identity));72 // } else {73 // self.identity = base64urldecode(self.identity);74 // assert(self.identity.length === 8);75 // }76 self.options = $.extend({}, Chatd.DEFAULT_OPTIONS, options);77 // debug mode78 [79 // 'onError',80 // 'onOpen',81 // 'onClose',82 // 'onRoomConnected',83 // 'onRoomDisconnected',84 // 'onMessageUpdated',85 // 'onMessageConfirm',86 // 'onMessageReject',87 // 'onMessageCheck',88 // 'onMessageModify',89 // 'onMessageStore',90 // 'onMessageSeen',91 // 'onMessageLastSeen',92 // 'onMessageReceived',93 // 'onMessageLastReceived',94 // 'onRetentionChanged',95 // 'onMembersUpdated',96 // 'onMessagesHistoryDone',97 // 'onMessagesHistoryRequest',98 // 'onMessageDiscard',99 // 'onMessageKeysDone',100 ].forEach(function(evt) {101 self.rebind(evt + '.chatd', function(e) {102 if (arguments[1].shard) {103 var tmp = $.extend({}, arguments[1]);104 delete tmp.shard;105 tmp.shard = "shard#" + arguments[1].shard.shard;106 console.error(evt, unixtime(), JSON.stringify(107 tmp108 ));109 }110 else {111 console.error(evt, unixtime(), JSON.stringify(arguments[1]));112 }113 });114 });115 window.addEventListener('beforeunload', function() {116 self.shutdown();117 });118 this._proxyEventsToRooms();119};120makeObservable(Chatd);121Chatd.DEFAULT_OPTIONS = {122};123// command opCodes124Chatd.Opcode = {125 'KEEPALIVE' : 0,126 'JOIN' : 1,127 'OLDMSG' : 2,128 'NEWMSG' : 3,129 'MSGUPD' : 4,130 'SEEN' : 5,131 'RECEIVED' : 6,132 'RETENTION' : 7,133 'HIST' : 8,134 'RANGE' : 9,135 'NEWMSGID' : 10,136 'REJECT' : 11,137 'BROADCAST' : 12,138 'HISTDONE' : 13,139 'NEWKEY' : 17,140 'KEYID' : 18,141 'JOINRANGEHIST' : 19,142 'MSGUPDX' : 20,143 'MSGID' : 21,144 'CLIENTID': 24,145 'RTMSG_BROADCAST': 25,146 'RTMSG_USER': 26,147 'RTMSG_ENDPOINT': 27,148 'INCALL': 28,149 'ENDCALL': 29,150 'KEEPALIVEAWAY': 30,151 'CALLDATA': 31,152 'ECHO': 32,153 'ADDREACTION': 33,154 'DELREACTION': 34,155 'OPCODE_HANDLEJOIN': 36,156 'OPCODE_HANDLEJOINRANGEHIST': 37,157 'CALLTIME': 42,158 'NEWNODEMSG': 44,159 'NODEHIST': 45,160 'NUMBYHANDLE': 46,161 'HANDLELEAVE': 47162};163// privilege levels164Chatd.Priv = {165 'NOCHANGE' : -2,166 'NOTPRESENT' : -1,167 'RDONLY' : 0,168 'RDWR' : 1,169 'FULL' : 2,170 'OPER' : 3171};172// message's edited time is (TIMESTAMP + UPDATED - 1).173Chatd.MsgField = {174 'MSGID' : 0,175 'USERID' : 1,176 'TIMESTAMP' : 2,177 'MESSAGE' : 3,178 'KEYID' : 4,179 'UPDATED' : 5,180 'TYPE' : 6181};182Chatd.MsgType = {183 'KEY' : strongvelope.MESSAGE_TYPES.GROUP_KEYED,184 'MESSAGE' : strongvelope.MESSAGE_TYPES.GROUP_FOLLOWUP,185 'EDIT' : strongvelope.MESSAGE_TYPES.GROUP_FOLLOWUP + 1186};187Chatd.Const = {188 'UNDEFINED' : '\0\0\0\0\0\0\0\0'189};190Chatd.MAX_KEEPALIVE_DELAY = 45000;191Chatd.KEEPALIVE_PING_INTERVAL = 20000;192// 1 hour is agreed by everyone.193Chatd.MESSAGE_EXPIRY = 60 * 60; // 60*60194var MESSAGE_EXPIRY = Chatd.MESSAGE_EXPIRY;195Chatd.MESSAGE_HISTORY_LOAD_COUNT = 32;196Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL = 3;197Chatd.LOGGER_ENABLED = !!localStorage.chatdLogger;198Chatd.LOGGER_LEVEL =199 Chatd.LOGGER_ENABLED ? MegaLogger.LEVELS.DEBUG : d > 1 ? MegaLogger.LEVELS.WARN : MegaLogger.LEVELS.ERROR;200Chatd.VERSION = 1;201// The per-chat state of the join/login sequence. It can also be null if we are no longer202// part of that chatroom (i.e. -1 privilege)203var LoginState = Chatd.LoginState = Object.freeze({204 DISCONN: 0,205 JOIN_SENT: 1,206 JOIN_RECEIVED: 2,207 HISTDONE: 3208});209/**210 * Global sendingnum (for generating msxid). Reasonably high id for pending messages in buf.211 *212 * @type {Number}213 */214Chatd.sendingnum = 2 << 30;215Chatd.prototype._proxyEventsToRooms = function() {216 "use strict";217 var self = this;218 [219 'onMessagesHistoryDone',220 'onMessageStore',221 'onMessageKeysDone',222 'onMessageKeyRestore',223 'onMessageLastSeen',224 'onMessageLastReceived',225 'onMessagesHistoryRequest',226 'onBroadcast',227 'onRoomDisconnected',228 'onMembersUpdated',229 'onMessageConfirm',230 'onMessageUpdated',231 'onMessagesHistoryRetrieve',232 'onMessageCheck',233 'onMessagesKeyIdDone',234 'onMessageIncludeKey',235 'onMarkAsJoinRequested'236 ]237 .forEach(function(eventName) {238 self.rebind(eventName + '.chatdProxy', function(e, eventData) {239 assert(eventData.chatId, 'chatid is missing');240 var chatRoom = megaChat.getChatById(eventData.chatId);241 if (chatRoom) {242 chatRoom.trigger(eventName, eventData);243 }244 else {245 if (d) {246 console.warn("chatRoom was missing for event:", eventName, eventData);247 }248 }249 });250 });251};252// add a new chatd shard253Chatd.prototype._addShardAndChat = function(chatId, shardNo, url) {254 // instantiate Chatd.Shard object for this shard if needed255 var shard = this.shards[shardNo];256 var isNewShard;257 if (!shard) {258 isNewShard = true;259 shard = this.shards[shardNo] = new Chatd.Shard(this, shardNo);260 }261 // always update the URL to give the API an opportunity to migrate chat shards between hosts262 shard.url = url;263 // map chatId to this shard264 this.chatIdShard[chatId] = shard;265 // add chatId to the connection's chatIds266 var chat = this.chatIdMessages[chatId] = new Chatd.Messages(this, shard, chatId);267 shard.chatIds[chatId] = true;268 if (isNewShard) {269 // attempt a connection ONLY if this is a new shard.270 shard.reconnect();271 } else {272 chat.join();273 }274 return isNewShard;275};276// Chatd.Shard - everything specific to a chatd instance277Chatd.Shard = function(chatd, shard) {278 var self = this;279 // parent backlink280 self.chatd = chatd;281 // shard for this connection282 self.shard = shard;283 // active chats on this connection284 self.chatIds = {};285 self.joinedChatIds = {};286 self.userRequestedJoin = {};287 self.mcurlRequests = {};288 // queued commands289 self.cmdq = '';290 self.histRequests = {};291 self.logger = new MegaLogger(292 "shard-" + shard, {293 minLogLevel: function() {294 return Chatd.LOGGER_LEVEL;295 }296 },297 chatd.logger298 );299 self.loggerIsEnabled = Chatd.LOGGER_ENABLED;300 /**301 * Will initialise/reset a timer that would force reconnect the shard connection IN case that the keep alive is not302 * received during a delay of max `Chatd.MAX_KEEPALIVE_DELAY` ms303 *304 * @type {KeepAlive}305 */306 self.keepAlive = new KeepAlive(Chatd.MAX_KEEPALIVE_DELAY, function() {307 if (self.s && self.s.readyState === self.s.OPEN) {308 self.logger.error("Server heartbeat missed/delayed. Will force reconnect.");309 // current connection is active, but the keep alive detected delay of the keep alive. reconnect!310 self.disconnect();311 self.reconnect();312 }313 });314 /**315 * Every Xs (usually 30s), we would ping the chatd server with either OpCode.KEEPALIVE or KEEPALIVEAWAY.316 *317 * @type {KeepAlive}318 */319 self.keepAlivePing = new KeepAlive(Chatd.KEEPALIVE_PING_INTERVAL, function() {320 self.sendKeepAlive();321 });322 self.destroyed = false;323 self.connectionRetryManager = new ConnectionRetryManager(324 {325 functions: {326 reconnect: function(connectionRetryManager) {327 connectionRetryManager.pause();328 self.retrieveMcurlAndExecOnce(base64urlencode(Object.keys(self.chatIds)[0]))329 .then(function(mcurl) {330 connectionRetryManager.unpause();331 self.url = mcurl;332 self.reconnect();333 })334 .catch(function(ex) {335 self.logger.warn(ex);336 if (ex === EEXPIRED) {337 megaChat.plugins.chatdIntegration.requiresUpdate();338 }339 else {340 connectionRetryManager.resetConnectionRetries();341 }342 });343 },344 /**345 * A Callback that will trigger the 'forceDisconnect' procedure for this type of connection346 * (Karere/Chatd/etc)347 * @param connectionRetryManager {ConnectionRetryManager}348 */349 forceDisconnect: function(connectionRetryManager) {350 return self.disconnect();351 },352 /**353 * Should return true or false depending on the current state of this connection,354 * e.g. (connected || connecting)355 * @param connectionRetryManager {ConnectionRetryManager}356 * @returns {bool}357 */358 isConnectedOrConnecting: function(connectionRetryManager) {359 return (360 self.s && (361 self.s.readyState === self.s.CONNECTING ||362 self.s.readyState === self.s.OPEN363 )364 );365 },366 /**367 * Should return true/false if the current state === CONNECTED368 * @param connectionRetryManager {ConnectionRetryManager}369 * @returns {bool}370 */371 isConnected: function(connectionRetryManager) {372 return (373 self.s && (374 self.s.readyState === self.s.OPEN375 )376 );377 },378 /**379 * Should return true/false if the current state === DISCONNECTED380 * @param connectionRetryManager {ConnectionRetryManager}381 * @returns {bool}382 */383 isDisconnected: function(connectionRetryManager) {384 return (385 !self.s || self.s.readyState === self.s.CLOSED386 );387 },388 /**389 * Should return true IF the user had forced the connection to go offline390 * @param connectionRetryManager {ConnectionRetryManager}391 * @returns {bool}392 */393 isUserForcedDisconnect: function(connectionRetryManager) {394 return (395 self.chatd.destroyed === true ||396 self.destroyed === true397 );398 }399 }400 },401 self.logger402 );403 Object.defineProperty(self, 'userIsActive', {404 get: function() {405 return megaChat.activeCallManagerCall || mega.active;406 }407 });408 var ooa = window.onactivity;409 window.onactivity = function() {410 if (ooa) {411 onIdle(ooa);412 }413 delay('chatd:shard:activity.' + shard, function() {414 // restart would also "start" the keepalive tracker, which is415 // not something we want in case chatd is not yet connected.416 if (self.isOnline()) {417 self.sendKeepAlive(true);418 self.keepAlive.restart();419 self.keepAlivePing.restart();420 }421 }, 3e4);422 };423 // HistoryDone queue manager424 self.chatd.rebind('onMessagesHistoryDone.histQueueManager' + shard, function(e, eventData) {425 var chatIdDecoded = base64urldecode(eventData.chatId);426 if (self.histRequests[chatIdDecoded] && self.histRequests[chatIdDecoded].resolve) {427 self.histRequests[chatIdDecoded].resolve();428 }429 });430};431Chatd.Shard.prototype.markAsJoinRequested = function(chatId) {432 "use strict";433 if (!this.userRequestedJoin[chatId]) {434 this.chatd.trigger('onMarkAsJoinRequested', {435 chatId: base64urlencode(chatId)436 });437 this.userRequestedJoin[chatId] = true;438 }439};440/**441 * Trigger multiple events for each chat which this shard relates to.442 * (used to trigger events related to room changes because of a shard disconnect/connect)443 *444 * @param evtName445 */446Chatd.Shard.prototype.triggerEventOnAllChats = function(evtName) {447 var self = this;448 Object.keys(self.chatd.chatIdShard).forEach(function(chatId) {449 var shard = self.chatd.chatIdShard[chatId];450 if (shard === self) {451 self.chatd.trigger(evtName, {452 chatId: base64urlencode(chatId)453 });454 }455 });456};457Chatd.Shard.prototype.retrieveMcurlAndExecOnce = promisify(function(resolve, reject, chatId) {458 'use strict';459 var publicChatHandle = anonymouschat && pchandle;460 var chatHandleOrId = chatId;461 if (publicChatHandle) {462 chatHandleOrId = pchandle;463 }464 else {465 var chatRoom = megaChat.getChatById(chatId);466 if (chatRoom && chatRoom.publicChatHandle) {467 publicChatHandle = chatRoom.publicChatHandle;468 chatHandleOrId = chatRoom.chatId;469 }470 }471 megaChat.plugins.chatdIntegration._retrieveShardUrl(publicChatHandle, chatHandleOrId)472 .then(function(ret) {473 if (typeof ret === "string") {474 resolve(ret);475 }476 else if (ret && ret.url) {477 resolve(ret.url);478 }479 else {480 reject(ret);481 }482 })483 .catch(reject);484});485// is this chatd connection currently active?486Chatd.Shard.prototype.isOnline = function() {487 return (!!this.s) && this.s.readyState === this.s.OPEN;488};489/**490 * Helper function that return the chat Ids (base64urlencode'd) related to this shard491 * as an Array492 * @returns {Array}493 */494Chatd.Shard.prototype.getRelatedChatIds = function() {495 var chatIds = [];496 Object.keys(this.chatIds).forEach(function(v) {497 chatIds.push(498 base64urlencode(v)499 );500 });501 return chatIds;502};503Chatd.Shard.prototype.reconnect = function() {504 var self = this;505 if (self.s) {506 self.disconnect();507 }508 var chats = self.chatd.chatIdMessages;509 for (var chatid in self.chatIds) {510 var chat = chats[chatid];511 if (chat) {512 // seems at first connect self.chatIds is filled with ids,513 // but the chat objects in chatd.chatdIdMessages are not there514 chat._setLoginState(LoginState.DISCONN);515 }516 }517 var chatdTag = (localStorage.chatdTag ? localStorage.chatdTag : '5');518 self.s = new WebSocket(this.url + '/' + chatdTag);519 self.s.binaryType = "arraybuffer";520 self.s.onopen = function() {521 self.keepAlive.restart();522 self.keepAlivePing.restart();523 self.logger.debug('chatd connection established');524 self.connectionRetryManager.gotConnected();525 self.cmdq = "";526 self.sendIdentity();527 if (!self.triggerSendIfAble(true)) {528 // XXX: websocket.send() failed for whatever reason, onerror should529 // have been called and the connection restablished afterwards.530 self.logger.warn('chatd connection closed unexpectedly...');531 return;532 }533 self.rejoinexisting();534 self.chatd.trigger('onOpen', {535 shard: self536 });537 // Resending of pending message should be done via the integration code, since it have more info and a direct538 // relation with the UI related actions on pending messages (persistence, user can click resend/cancel/etc).539 // self.resendpending();540 self.triggerEventOnAllChats('onRoomConnected');541 if (!self.userIsActive) {542 self.sendKeepAlive(true);543 }544 };545 self.s.onerror = function(e) {546 self.logger.error("WebSocket error:", e);547 self.keepAlive.stop();548 self.connectionRetryManager.doConnectionRetry();549 self.histRequests = {};550 self.chatd.trigger('onError', {551 shard: self552 });553 };554 self.s.onmessage = function(e) {555 // verify that WebSocket frames are always delivered as a contiguous message556 self.exec(new Uint8Array(e.data));557 };558 self.s.onclose = function(e) {559 self.handleDisconnect();560 };561};562Chatd.Shard.prototype.handleDisconnect = function() {563 var self = this;564 if (!self.s) {565 return;566 }567 self.s.onclose = null;568 self.s.onerror = null;569 self.s.onopen = null;570 self.s.onmessage = null;571 self.s = null;572 self.logger.warn('chatd connection to shard ' + self.shard + ' lost, will eventually reconnect...');573 self.keepAlive.stop();574 self.keepAlivePing.stop();575 self.joinedChatIds = {};576 self.userRequestedJoin = {};577 self.mcurlRequests = {};578 self.triggerEventOnAllChats('onRoomDisconnected');579 self.histRequests = {};580 var chatd = self.chatd;581 var chats = chatd.chatIdMessages;582 for (var chatid in self.chatIds) {583 var chat = chats[chatid];584 assert(chat);585 chat._clearCallInfo();586 chats[chatid]._setLoginState(LoginState.DISCONN);587 }588 chatd.trigger('onClose', {589 shard: self590 });591 delete self.clientId;592 self.connectionRetryManager.gotDisconnected();593};594Chatd.Shard.prototype.disconnect = function() {595 var self = this;596 if (!self.s) {597 return;598 }599 self.s.close();600 if (self.s) {601 self.handleDisconnect();602 }603};604Chatd.Shard.prototype.sendIdentity = function() {605 assert(this.chatd.identity);606 this.cmd(Chatd.Opcode.CLIENTID, this.chatd.identity, true);607};608Chatd.clientIdToString = function(data, offset) {609 return '0x' + Chatd.dumpToHex(data, offset ? offset : 0, 4, true);610};611Chatd.logCmdsToString = function(logger, cmd, tx, prefix, isReconnect) {612 var result = Chatd.cmdsToArrayStrings(cmd, tx);613 if (!prefix) {614 prefix = tx ? "send:" : "recv:";615 }616 var len = result.length;617 if (len > 1) {618 prefix += "(multicmd#";619 }620 for (var k = 0; k < len; k++) {621 var line = result[k];622 logger.debug(isReconnect ? "initial cmdq flush:" : "", (len > 1) ? (prefix + k + ")") : prefix, line);623 }624};625/**626 * Parse string of command(s) to array627 *628 * @param cmd {String}629 * @param tx {boolean}630 * @returns [Array]631 */632Chatd.cmdsToArrayStrings = function(cmd, tx) {633 if (!cmd) {634 return [];635 }636 var lines = [];637 for (;;) {638 var ret = Chatd.cmdToString(cmd, tx);639 lines.push(ret[0]);640 var next = ret[1];641 if (next >= cmd.length) {642 return lines;643 }644 cmd = cmd.substr(next);645 }646};647Chatd.cmdToString = function(cmd, tx) {648 var opCode = cmd.charCodeAt(0);649 var result;650 if (opCode >= Chatd.Opcode.RTMSG_BROADCAST && opCode <= Chatd.Opcode.RTMSG_ENDPOINT) {651 var rtcmdInfo = RtcModule.rtcmdToString(cmd, tx, opCode);652 result = rtcmdInfo[0];653 return [result, rtcmdInfo[1]];654 }655 result = constStateToText(Chatd.Opcode, opCode);656 if (opCode === Chatd.Opcode.CALLDATA) {657 var ret = RtcModule.callDataToString(cmd, tx);658 result += ret[0];659 return [result, ret[1]];660 }661 // To ease debugging, please add more commands here when needed662 // (debugging = console grepping for ids, etc, so thats why, I'm parsing663 // them here and showing them in non-hex format)664 var chatId; // avoid declaring it in each 'case', to make JSHint happy665 switch (opCode) {666 case Chatd.Opcode.HISTDONE:667 chatId = base64urlencode(cmd.substr(1, 8));668 result += " chatId: " + chatId;669 return [result, 9];670 case Chatd.Opcode.BROADCAST:671 chatId = base64urlencode(cmd.substr(1, 8));672 var userId = base64urlencode(cmd.substr(9, 8));673 var bCastCode = cmd.substr(17, 1).charCodeAt(0);674 result += " chatId: " + chatId + " userId: " + userId + " bCastCode:" + bCastCode;675 return [result, 18];676 case Chatd.Opcode.NEWMSGID:677 var msgId = base64urlencode(cmd.substr(9, 8));678 var msgxid = base64urlencode(cmd.substr(1, 8));679 result += " msgxid: " + msgxid + " msgId: " + msgId;680 return [result, 17];681 case Chatd.Opcode.KEYID:682 chatId = base64urlencode(cmd.substr(1, 8));683 var keyxid = Chatd.unpack32le(cmd.substr(9, 4));684 var keyid = Chatd.unpack32le(cmd.substr(13, 4));685 result += " chatId: " + chatId + " keyXid: " + keyxid + " keyId: " + keyid;686 return [result, 17];687 case Chatd.Opcode.NEWKEY:688 // chatId + Chatd.pack32le(keyid) + Chatd.pack32le(message.length) + message689 var keyId = Chatd.unpack32le(cmd.substr(9, 4));690 var keyLength = Chatd.unpack32le(cmd.substr(13, 4));691 result += " chatid:" + base64urlencode(cmd.substr(1, 8)) +692 " keyId: " + keyId + " keyLen: " + keyLength;693 return [result, 17 + keyLength];694 case Chatd.Opcode.CLIENTID:695 var clientId = cmd.substr(1, 4);696 result += " clientId: " + Chatd.clientIdToString(clientId);697 return [result, 9];698 case Chatd.Opcode.HIST:699 var histLen = Chatd.unpack32le(cmd.substr(9, 4));700 result += " chatId: " + base64urlencode(cmd.substr(1, 8)) + " len: " + (histLen - 4294967296);701 return [result, 13];702 case Chatd.Opcode.JOIN:703 chatId = base64urlencode(cmd.substr(1, 8));704 var userId = base64urlencode(cmd.substr(9, 8));705 var priv = cmd.charCodeAt(17);706 if (priv > 255) {707 priv = priv - 65536;708 } else if (priv > 127) {709 priv -= 256;710 }711 result += " chatId: " + chatId + " userId: " + userId +712 " priv: " + constStateToText(Chatd.Priv, priv) + "(" + priv + ")";713 return [result, 18];714 case Chatd.Opcode.OPCODE_HANDLEJOIN:715 chatId = base64urlencode(cmd.substr(1, 6));716 var userId = base64urlencode(cmd.substr(7, 8));717 var priv = cmd.charCodeAt(15);718 result += " chatId: " + chatId + " userId: " + userId +719 " priv: " + constStateToText(Chatd.Priv, priv) + "(" + priv + ")";720 return [result, 18];721 case Chatd.Opcode.SEEN:722 // self.chatd.cmd(Chatd.Opcode.SEEN, base64urldecode(chatRoom.chatId), base64urldecode(msgid));723 chatId = base64urlencode(cmd.substr(1, 8));724 var msgId = base64urlencode(cmd.substr(9, 8));725 result += " chatId: " + chatId + " msgId: " + msgId;726 return [result, 17];727 case Chatd.Opcode.JOINRANGEHIST:728 chatId = base64urlencode(cmd.substr(1, 8));729 var fromMsg = base64urlencode(cmd.substr(9, 8));730 var toMsg = base64urlencode(cmd.substr(17, 8));731 result += " chatId: " + chatId + " fromMsgId: " + fromMsg + " toMsgId: " + toMsg;732 return [result, 25];733 case Chatd.Opcode.OPCODE_HANDLEJOINRANGEHIST:734 chatId = base64urlencode(cmd.substr(1, 8));735 var fromMsg = base64urlencode(cmd.substr(9, 8));736 var toMsg = base64urlencode(cmd.substr(17, 8));737 result += " chatId: " + chatId + " fromMsgId: " + fromMsg + " toMsgId: " + toMsg;738 return [result, 25];739 case Chatd.Opcode.OLDMSG:740 case Chatd.Opcode.NEWMSG:741 case Chatd.Opcode.MSGUPD:742 case Chatd.Opcode.NEWNODEMSG:743 // chatId + userId + msgid + Chatd.pack32le(timestamp) +744 // 8 8 8 4745 // Chatd.pack16le(0) + Chatd.pack32le(keyid) + Chatd.pack32le(message.length) + message];746 // 2 4 4 ...747 chatId = base64urlencode(cmd.substr(1, 8));748 var msgId = base64urlencode(cmd.substr(17, 8));749 var ts = Chatd.unpack32le(cmd.substr(25, 4));750 var keyid = Chatd.unpack32le(cmd.substr(31, 4));751 var msgLength = Chatd.unpack32le(cmd.substr(35, 4));752 // var msg = cmd.substr(39, msgLength);753 var sender = base64urlencode(cmd.substr(9, 8));754 result += " chatId: " + chatId +755 (tx ? " to: " : " from: ") + sender +756 " msgId: " + msgId +757 " keyId: " + keyid +758 " ts: (" + new Date(ts * 1000).toLocaleString() +759 ") msgLen: " + msgLength;760 if (sender === strongvelope.COMMANDER) {761 var parsed = strongvelope._parseMessageContent(cmd.substr(39));762 result += " mgmt-type: " + constStateToText(strongvelope.MESSAGE_TYPES, parsed.type);763 switch (parsed.type) {764 case strongvelope.MESSAGE_TYPES.CALL_END:765 // call-end management message format is:766 // callId.8 endCallReason.1 callDuration.4767 var reason = parsed.payload.charCodeAt(8);768 result += " callend-type: " + constStateToText(CallManager.CALL_END_REMOTE_REASON, reason) +769 " dur: " + Chatd.unpack32le(parsed.payload.substr(9, 4));770 break;771 }772 } else {773 var updated = Chatd.unpack16le(cmd.substr(29, 2));774 result += " edited: " + updated;775 }776 return [result, 39 + msgLength];777 case Chatd.Opcode.INCALL:778 case Chatd.Opcode.ENDCALL:779 result += ' chatId: ' + base64urlencode(cmd.substr(1, 8)) +780 ' user: ' + base64urlencode(cmd.substr(9, 8)) +781 ' clientId: ' + Chatd.clientIdToString(cmd, 17);782 return [result, 21];783 case Chatd.Opcode.RETENTION:784 result += " policy change on '" +785 base64urlencode(cmd.substr(1, 8)) + "' by '" +786 base64urlencode(cmd.substr(9, 8)) + "': " +787 Chatd.unpack32le(cmd.substr(17, 4)) + " second(s)";788 return [result, 21];789 case Chatd.Opcode.NUMBYHANDLE:790 result += " num by handle in '" +791 base64urlencode(cmd.substr(1, 8)) + "' with " +792 Chatd.unpack32le(cmd.substr(9, 4)) + " user(s)";793 return [result, 13];794 case Chatd.Opcode.KEEPALIVE:795 case Chatd.Opcode.KEEPALIVEAWAY:796 return [result, 1];797 case Chatd.Opcode.NODEHIST:798 // chatid.8 msgid.8 count.4 ?799 var count = Chatd.unpack32le(cmd.substr(17, 4));800 if (count > 0x7fffffff) {801 count -= 0x100000000;802 }803 result += "chatId: " + base64urlencode(cmd.substr(1, 8)) + " " +804 "msgId: " + base64urlencode(cmd.substr(9, 8)) + " " +805 "count: " + count;806 return [result, 21];807 default:808 if (cmd.length > 64) {809 result += ' ' + Chatd.dumpToHex(cmd, 1, 64) + '...';810 } else {811 result += ' ' + Chatd.dumpToHex(cmd, 1);812 }813 return [result, cmd.length];814 }815};816Chatd.Shard.prototype.multicmd = function(cmds) {817 var self = this;818 cmds.forEach(function(cmdObj) {819 var opCode = cmdObj[0];820 var cmd = cmdObj[1];821 var buf = String.fromCharCode(opCode) + cmd;822 self.cmdq += buf;823 });824 return this.triggerSendIfAble();825};826Chatd.Shard.prototype.cmd = function(opCode, cmd, sendFirst) {827 var buf = String.fromCharCode(opCode);828 if (cmd) {829 buf += cmd;830 }831 if (!sendFirst) {832 this.cmdq += buf;833 }834 else {835 this.cmdq = buf + this.cmdq;836 }837 return this.triggerSendIfAble(sendFirst);838};839/*840 * Checks if the shard has a clientId assigned. It may be841 * connected and fully operational for text chat, but not yet assigned a clientId,842 * which is needed for webrtc843 */844Chatd.Shard.prototype.rtcmd = function(chatid, userid, clientid, rtcmdCode, payload) {845 if (!this.clientId) {846 console.warn("Chatd.Shard.rtcmd: Trying to send an RTCMD when we don't yet have a CLIENTID assigned");847 return false;848 }849 var opcode;850 if (!userid) {851 userid = '\0\0\0\0\0\0\0\0';852 clientid = '\0\0\0\0';853 opcode = Chatd.Opcode.RTMSG_BROADCAST;854 } else if (!clientid) {855 clientid = '\0\0\0\0';856 opcode = Chatd.Opcode.RTMSG_USER;857 } else {858 opcode = Chatd.Opcode.RTMSG_ENDPOINT;859 }860 var len = payload ? payload.length + 1 : 1;861 var data = chatid + userid + clientid + Chatd.pack16le(len) + String.fromCharCode(rtcmdCode);862 if (payload) {863 data += payload;864 }865 return this.cmd(opcode, data);866};867Chatd.Shard.prototype.sendKeepAlive = function(forced) {868 var self = this;869 var lastKeepAliveSent = self._lastKeepAliveSent || 0;870 if (871 (forced || unixtime() - lastKeepAliveSent > Chatd.KEEPALIVE_PING_INTERVAL / 1000) &&872 self.s && self.s.readyState === self.s.OPEN873 ) {874 self.cmd(self.userIsActive ? Chatd.Opcode.KEEPALIVE : Chatd.Opcode.KEEPALIVEAWAY);875 self._lastKeepAliveSent = unixtime();876 self.keepAlivePing.restart();877 }878};879Chatd.Shard.prototype.triggerSendIfAble = function(isReconnect) {880 if (!this.isOnline()) {881 return false;882 }883 if (this.cmdq.length > 0) {884 var a = new Uint8Array(this.cmdq.length);885 for (var i = this.cmdq.length; i--;) {886 a[i] = this.cmdq.charCodeAt(i);887 }888 try {889 this.s.send(a);890 if (this.loggerIsEnabled) {891 Chatd.logCmdsToString(this.logger, this.cmdq, true, undefined, isReconnect);892 }893 }894 catch (ex) {895 if (this.loggerIsEnabled) {896 this.logger.warn("ws.send failed with exception:", ex);897 }898 return false;899 }900 this.cmdq = '';901 }902 return true;903};904// rejoin all open chats after reconnection (this is mandatory)905Chatd.Shard.prototype.rejoinexisting = function() {906 var chats = this.chatd.chatIdMessages;907 for (var c in this.chatIds) {908 var chat = chats[c];909 if (chat._loginState === LoginState.DISCONN) {910 // rejoin chat and immediately set the locally buffered message range911 chat.join();912 }913 }914};915Chatd.Shard.prototype.clearpending = function() {916 var self = this;917 for (var chatId in this.chatIds) {918 self.chatd.chatIdMessages[chatId].clearpending();919 }920};921// resend all unconfirmed messages (this is mandatory)922// @deprecated923Chatd.Shard.prototype.resendpending = function(chatId) {924 var self = this;925 var chatIdMessages = self.chatd.chatIdMessages[chatId];926 if (chatIdMessages) {927 chatIdMessages.resend();928 }929};930Chatd.prototype.cmd = function(opCode, chatId, cmd) {931 return this.chatIdShard[chatId].cmd(opCode, chatId + cmd);932};933Chatd.prototype.hist = function(chatId, count) {934 this.chatIdShard[chatId].hist(chatId, count);935};936// send HIST937Chatd.Shard.prototype._sendHist = function(chatId, count) {938 this.chatd.trigger('onMessagesHistoryRequest', {939 count: count,940 chatId: base64urlencode(chatId)941 });942 this.cmd(Chatd.Opcode.HIST, chatId + Chatd.pack32le(count));943};944// send NODEHIST945Chatd.Shard.prototype._sendNodeHist = function(chatId, lastMsgId, count) {946 this.chatd.trigger('onMessagesHistoryRequest', {947 count: count,948 chatId: base64urlencode(chatId),949 isRetrievingSharedFiles: true,950 });951 this.cmd(Chatd.Opcode.NODEHIST, chatId + lastMsgId + Chatd.pack32le(count));952};953Chatd.Shard.prototype.joinbyhandle = function(handle) {954 var self = this;955 self.cmd(Chatd.Opcode.OPCODE_HANDLEJOIN, handle + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE));956};957Chatd.Shard.prototype.join = function(handle) {958 var self = this;959 var chat = self.chatd.chatIdMessages[handle];960 assert(chat, 'chat not found');961 chat.join();962};963Chatd.Shard.prototype.hist = function(chatId, count, isInitial) {964 var self = this;965 var promise = new MegaPromise();966 var megaChat = self.chatd.megaChat;967 var chatRoom;968 if (self.histRequests[chatId] && self.histRequests[chatId].always) {969 // queue970 self.histRequests[chatId].always(function() {971 promise.linkDoneAndFailTo(self.hist(chatId, count, isInitial));972 });973 return promise;974 }975 self.histRequests[chatId] = promise;976 promise.always(function() {977 if (self.histRequests[chatId] === promise) {978 self.histRequests[chatId] = null;979 }980 });981 if (self.chatd.chatdPersist) {982 if (isInitial) {983 self.chatd.trigger('onMessagesHistoryRequest', {984 count: Math.abs(count),985 chatId: base64urlencode(chatId)986 });987 }988 self.chatd.chatdPersist.retrieveChatHistory(989 base64urlencode(chatId),990 count * -1991 )992 .then(function(result) {993 chatRoom = megaChat.getChatById(base64urlencode(chatId));994 var messages = result[0];995 var keys = result[1];996 var highnum = result[3];997 var lastSeen = result[4];998 if (!messages || messages.length === 0) {999 if (isInitial) {1000 self.markAsJoinRequested(chatId);1001 self.cmd(1002 Chatd.Opcode.JOIN, chatId + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE)1003 );1004 }1005 self._sendHist(chatId, count);1006 return;1007 }1008 if (lastSeen) {1009 self.chatd.trigger('onMessageLastSeen', {1010 chatId: base64urlencode(chatId),1011 messageId: lastSeen,1012 chatdPersist: true1013 });1014 }1015 var chatIdMessagesObj = self.chatd.chatIdMessages[chatId];1016 if (chatRoom && chatIdMessagesObj) {1017 var keysArr = [];1018 Object.keys(keys).forEach(function(k) {1019 keysArr.push({1020 userId : keys[k][2],1021 keyid : keys[k][0],1022 keylen : keys[k][1].length,1023 key : keys[k][1]1024 });1025 });1026 self.chatd.trigger('onMessageKeysDone', {1027 chatId: base64urlencode(chatId),1028 keys : keysArr,1029 chatdPersist: true1030 });1031 if (isInitial) {1032 chatIdMessagesObj.lownum = chatIdMessagesObj.highnum = highnum;1033 }1034 messages.forEach(function(msg) {1035 var msgObj = msg.msgObject;1036 var msg = new Message(1037 chatRoom,1038 chatRoom.messagesBuff,1039 {1040 'messageId': msg.msgId,1041 'userId': msg.userId,1042 'keyid': msg.keyId,1043 'textContents': msgObj.textContents,1044 'delay': msgObj.delay,1045 'orderValue': msg.orderValue,1046 'updated': msgObj.updated,1047 'sent': (msg.userId === u_handle ? Message.STATE.SENT : Message.STATE.NOT_SENT),1048 'deleted': msgObj.deleted,1049 'revoked': msgObj.revoked1050 }1051 );1052 // move non Message's internal DataStruct properties manually.1053 [1054 'meta',1055 'dialogType',1056 'references',1057 'msgIdentity',1058 'metaType'1059 ].forEach(function(k) {1060 if (typeof msgObj[k] !== 'undefined') {1061 msg[k] = msgObj[k];1062 }1063 });1064 msg.source = Message.SOURCE.IDB;1065 chatRoom.messagesBuff.restoreMessage(msg);1066 });1067 if (isInitial) {1068 // do JOINRANGE after our buffer is filled1069 self.chatd.chatdPersist.getLowHighIds(base64urlencode(chatId))1070 .then(function(result) {1071 var chatIdMessagesObj = self.chatd.chatIdMessages[chatId];1072 if (chatIdMessagesObj) {1073 if (result[2]) {1074 chatIdMessagesObj.lownum = result[2] - 1;1075 }1076 if (result[3]) {1077 chatIdMessagesObj.highnum = result[3];1078 }1079 }1080 self.markAsJoinRequested(chatId);1081 self.chatd.cmd(Chatd.Opcode.JOINRANGEHIST, chatId,1082 base64urldecode(result[0]) + base64urldecode(result[1]));1083 })1084 .catch(function(ex) {1085 if (ex && d) {1086 self.logger.warn(ex);1087 }1088 // in case low/high fails, proceed w/ joining anyway so that further commands would not1089 // get stuck w/ no response from chatd.1090 self.markAsJoinRequested(chatId);1091 self.cmd(1092 Chatd.Opcode.JOIN,1093 chatId + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE)1094 );1095 self.chatd.trigger('onMessagesHistoryDone', {1096 chatId: base64urlencode(chatId),1097 chatdPersist: true1098 });1099 if (chatRoom) {1100 chatRoom.trigger('onHistoryDecrypted');1101 }1102 });1103 }1104 else {1105 if (messages.length < count * -1) {1106 if (isInitial) {1107 self.markAsJoinRequested(chatId);1108 self.cmd(1109 Chatd.Opcode.JOIN,1110 chatId + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE)1111 );1112 }1113 // if the returned # of messages is < the requested, see if there1114 // is more history stored on the server.1115 self._sendHist(chatId, count);1116 }1117 else {1118 self.chatd.trigger('onMessagesHistoryDone', {1119 chatId: base64urlencode(chatId),1120 chatdPersist: true1121 });1122 if (chatRoom) {1123 chatRoom.trigger('onHistoryDecrypted');1124 }1125 }1126 }1127 }1128 })1129 .catch(function() {1130 if (isInitial) {1131 self.markAsJoinRequested(chatId);1132 self.cmd(Chatd.Opcode.JOIN, chatId + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE));1133 }1134 self._sendHist(chatId, count);1135 });1136 }1137 else {1138 if (isInitial) {1139 self.markAsJoinRequested(chatId);1140 self.cmd(Chatd.Opcode.JOIN, chatId + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE));1141 }1142 self._sendHist(chatId, count);1143 }1144 return promise;1145};1146Chatd.Shard.prototype.arrayBufferToString = function arrayBufferToString(buffer) {1147 'use strict';1148 // Thanks to https://stackoverflow.com/a/206045611149 var result = '';1150 var addition = Math.pow(2, 16) - 1;1151 var bufView = new Uint16Array(buffer);1152 var length = bufView.length;1153 for (var i = 0; i < length; i += addition) {1154 if (i + addition > length) {1155 addition = length - i;1156 }1157 result += String.fromCharCode.apply(null, bufView.subarray(i, i + addition));1158 }1159 return result;1160};1161// inbound command processing1162// multiple commands can appear as one WebSocket frame, but commands never cross frame boundaries1163// CHECK: is this assumption correct on all browsers and under all circumstances?1164Chatd.Shard.prototype.exec = function(a) {1165 var self = this;1166 var chatd = self.chatd;1167 // TODO: find more optimised way of doing this...fromCharCode may also cause exceptions if too big array is passed1168 var cmd = this.arrayBufferToString(a);1169 if (self.loggerIsEnabled) {1170 Chatd.logCmdsToString(self.logger, cmd, false);1171 }1172 var len;1173 // jshint -W0041174 while (cmd.length) {1175 var opcode = cmd.charCodeAt(0);1176 switch (opcode) {1177 case Chatd.Opcode.KEEPALIVE:1178 if (self.loggerIsEnabled) {1179 self.logger.log("Server heartbeat received");1180 }1181 self.sendKeepAlive();1182 self.keepAlive.restart();1183 len = 1;1184 break;1185 case Chatd.Opcode.JOIN:1186 self.keepAlive.restart();1187 var chatIdBin = cmd.substr(1, 8);1188 var chatId = base64urlencode(chatIdBin);1189 var userIdBin = cmd.substr(9, 8);1190 var userId = base64urlencode(userIdBin);1191 var priv = cmd.charCodeAt(17);1192 if (priv > 127) {1193 priv -= 256;1194 }1195 var wasAddedToRoom = false;1196 var chat = chatd.chatIdMessages[chatIdBin];1197 if (chat && chat._loginState === LoginState.JOIN_SENT) {1198 // We mark the start of a login since the first received JOIN from the1199 // initial JOIN dump send to us by chatd1200 chat._setLoginState(LoginState.JOIN_RECEIVED);1201 }1202 // new room state logic:1203 if (userId === u_handle) {1204 if (priv === -1) { // we left the chatroom1205 if (chat) {1206 chat._setLoginState(null); // chat disabled, don't join upon re/connect1207 chat._clearCallInfo();1208 chatd.rtcHandler.onKickedFromChatroom(chat);1209 }1210 } else if (priv === 0 || priv === 1 || priv === 2 || priv === 3) {1211 // ^^ explicit and easy to read...despite that i could have done >= 1 <= 3 or something like1212 // that..1213 if (chat._loginState === null) {1214 // our privilege is changed from -1 to something valid, so have already been logged in1215 chat._setLoginState(LoginState.HISTDONE);1216 }1217 }1218 }1219 if (userId === u_handle) {1220 // todo: merge this w/ the new room state logic1221 if (priv === 0 || priv === 1 || priv === 2 || priv === 3) {1222 // ^^ explicit and easy to read...despite that i could have done >= 1 <= 3 or something like1223 // that..1224 if (!self.joinedChatIds[chatIdBin]) {1225 self.joinedChatIds[chatIdBin] = true;1226 }1227 // joinedChatIds may be set for pub chats.1228 if (!self.userRequestedJoin[chatIdBin]) {1229 wasAddedToRoom = true;1230 }1231 }1232 else if (priv === -1) { // we left the chatroom1233 delete self.joinedChatIds[chatIdBin];1234 delete self.userRequestedJoin[chatIdBin];1235 }1236 else {1237 self.logger.error("Not sure how to handle priv: " + priv + ".");1238 }1239 } else {1240 if (priv === -1) { // Someone else left the group chat1241 if (chat) {1242 chat.onUserLeftRoom(userIdBin);1243 }1244 }1245 }1246 self.chatd.trigger('onMembersUpdated', {1247 userId: userId,1248 chatId: chatId,1249 priv: priv1250 });1251 if (wasAddedToRoom) {1252 self.joinedChatIds[chatIdBin] = true;1253 }1254 len = 18;1255 break;1256 case Chatd.Opcode.OLDMSG:1257 case Chatd.Opcode.NEWMSG:1258 self.keepAlive.restart();1259 var newmsg = opcode === Chatd.Opcode.NEWMSG;1260 len = Chatd.unpack32le(cmd.substr(35, 4));1261 len += 39;1262 self.chatd.msgstore(newmsg,1263 cmd.substr(1, 8),1264 cmd.substr(9, 8),1265 cmd.substr(17, 8),1266 Chatd.unpack32le(cmd.substr(25, 4)),1267 Chatd.unpack16le(cmd.substr(29, 2)),1268 Chatd.unpack32le(cmd.substr(31, 4)),1269 cmd.substr(39, len)1270 );1271 break;1272 case Chatd.Opcode.MSGUPD:1273 case Chatd.Opcode.MSGUPDX:1274 self.keepAlive.restart();1275 len = Chatd.unpack32le(cmd.substr(35, 4));1276 if (self.loggerIsEnabled) {1277 self.logger.log("Message '" +1278 base64urlencode(cmd.substr(17, 8)) +1279 "' EDIT/DELETION: in " +1280 base64urlencode(cmd.substr(1, 8)) +1281 " ts: " +1282 Chatd.unpack32le(cmd.substr(25, 4)) +1283 " update: " +1284 Chatd.unpack16le(cmd.substr(29, 2)) +1285 ' from ' + base64urlencode(cmd.substr(9, 8)) +1286 ' with ' + Chatd.dumpToHex(cmd, 39, len)1287 );1288 }1289 len += 39;1290 self.chatd.msgmodify(cmd.substr(1, 8),1291 cmd.substr(9, 8), cmd.substr(17, 8),1292 Chatd.unpack32le(cmd.substr(25, 4)),1293 Chatd.unpack16le(cmd.substr(29, 2)),1294 Chatd.unpack32le(cmd.substr(31, 4)),1295 cmd.substr(39, len)1296 );1297 break;1298 case Chatd.Opcode.RTMSG_BROADCAST:1299 case Chatd.Opcode.RTMSG_USER:1300 case Chatd.Opcode.RTMSG_ENDPOINT:1301 self.keepAlive.restart();1302 // (opcode.1 chatid.8 userid.8 clientid.4 len.2) (type.1 data.(len-1))1303 // ^ ^1304 // header.hdrlen payload.len1305 if (cmd.length < 23) {1306 this.logger.error("received rtMessage is too short(len=" + cmd.length + "): data:\n" +1307 Chatd.dumpToHex(cmd));1308 len = 0xffffffff; // force abort of processing1309 break;1310 }1311 len = 23 + Chatd.unpack16le(cmd.substr(21, 2));1312 var rtcmd = cmd.charCodeAt(23);1313 self.chatd.rtcHandler.handleMessage(self, cmd, len);1314 break;1315 case Chatd.Opcode.CALLDATA:1316 self.keepAlive.restart();1317 // (opcode.1 chatid.8 userid.8 clientid.4 len.2) (payload.len)1318 var pllen = Chatd.unpack16le(cmd.substr(21, 2));1319 len = 23 + pllen;1320 if (len > cmd.length) {1321 break; // will be re-checked and cause error1322 }1323 // checks if payload spans outside actual cmd.length1324 chatd.rtcHandler.handleCallData(self, cmd, pllen);1325 break;1326 case Chatd.Opcode.INCALL:1327 case Chatd.Opcode.ENDCALL:1328 self.keepAlive.restart();1329 // opcode.1 chatid.8 userid.8 clientid.41330 if (cmd.length < 21) {1331 this.logger.error("received INCALL/ENDCALL is too short");1332 len = 0xffffffff;1333 break;1334 }1335 len = 21;1336 var chatid = cmd.substr(1, 8);1337 var chat = self.chatd.chatIdMessages[chatid];1338 if (!chat) {1339 this.logger.warn("Ingoring INCALL/ENDCALL for unknown chatid " + base64urlencode(chatid));1340 break;1341 }1342 var userid = cmd.substr(9, 8);1343 var clientid = cmd.substr(17, 4);1344 if (opcode === Chatd.Opcode.INCALL) {1345 chat.onInCall(userid, clientid);1346 } else {1347 chat.onEndCall(userid, clientid);1348 }1349 break;1350 case Chatd.Opcode.CLIENTID:1351 self.keepAlive.restart();1352 // clientid.4 reserved.41353 if (cmd.length < 9) {1354 this.logger.error("received CLIENTID is shorter than 9 bytes");1355 len = 0xffffffff;1356 break;1357 }1358 len = 9;1359 self.clientId = cmd.substr(1, 4);1360 if (self.loggerIsEnabled) {1361 self.logger.log("Assigned CLIENTID " + Chatd.clientIdToString(self.clientId),1362 " for shard", self.shard);1363 }1364 break;1365 case Chatd.Opcode.SEEN:1366 self.keepAlive.restart();1367 if (self.loggerIsEnabled) {1368 self.logger.log("Newest seen message on '" +1369 base64urlencode(cmd.substr(1, 8)) + "': '" + base64urlencode(cmd.substr(9, 8)) + "'");1370 }1371 self.chatd.trigger('onMessageLastSeen', {1372 chatId: base64urlencode(cmd.substr(1, 8)),1373 messageId: base64urlencode(cmd.substr(9, 8)),1374 chatd: true1375 });1376 len = 17;1377 break;1378 case Chatd.Opcode.RECEIVED:1379 self.keepAlive.restart();1380 if (self.loggerIsEnabled) {1381 self.logger.log("Newest delivered message on '" +1382 base64urlencode(cmd.substr(1, 8)) + "': '" + base64urlencode(cmd.substr(9, 8)) + "'");1383 }1384 self.chatd.trigger('onMessageLastReceived', {1385 chatId: base64urlencode(cmd.substr(1, 8)),1386 messageId: base64urlencode(cmd.substr(9, 8))1387 });1388 len = 17;1389 break;1390 case Chatd.Opcode.RETENTION:1391 self.keepAlive.restart();1392 if (self.loggerIsEnabled) {1393 self.logger.log("Retention policy change on '" +1394 base64urlencode(cmd.substr(1, 8)) + "' by '" +1395 base64urlencode(cmd.substr(9, 8)) + "': " +1396 Chatd.unpack32le(cmd.substr(17, 4)) + " second(s)");1397 }1398 self.chatd.trigger('onRetentionChanged', {1399 chatId: base64urlencode(cmd.substr(1, 8)),1400 userId: base64urlencode(cmd.substr(9, 8)),1401 retention: Chatd.unpack32le(cmd.substr(17, 4))1402 });1403 len = 21;1404 break;1405 case Chatd.Opcode.NEWMSGID:1406 self.keepAlive.restart();1407 if (self.loggerIsEnabled) {1408 self.logger.log(1409 "Sent message ID confirmed: '" + base64urlencode(cmd.substr(9, 8)) + "'");1410 }1411 self.chatd.msgconfirm(cmd.substr(1, 8), cmd.substr(9, 8));1412 len = 17;1413 break;1414 case Chatd.Opcode.RANGE:1415 self.keepAlive.restart();1416 if (self.loggerIsEnabled) {1417 self.logger.log(1418 "Known chat message IDs on '" + base64urlencode(cmd.substr(1, 8)) + "' " +1419 "- oldest: '" + base64urlencode(cmd.substr(9, 8)) + "' " +1420 "newest: '" + base64urlencode(cmd.substr(17, 8)) + "'"1421 );1422 }1423 self.chatd.trigger('onMessagesHistoryInfo', {1424 chatId: base64urlencode(cmd.substr(1, 8)),1425 oldest: base64urlencode(cmd.substr(9, 8)),1426 newest: base64urlencode(cmd.substr(17, 8))1427 });1428 self.chatd.msgcheck(cmd.substr(1, 8), cmd.substr(17, 8));1429 len = 25;1430 break;1431 case Chatd.Opcode.REJECT:1432 self.keepAlive.restart();1433 if (self.loggerIsEnabled) {1434 self.logger.log("Command was rejected, chatId : " +1435 base64urlencode(cmd.substr(1, 8)) + " / msgId : " +1436 base64urlencode(cmd.substr(9, 8)) + " / opcode: " +1437 constStateToText(Chatd.Opcode, cmd.substr(17, 1).charCodeAt(0)) +1438 " / reason: " + cmd.substr(18, 1).charCodeAt(0));1439 }1440 if (cmd.charCodeAt(17) === Chatd.Opcode.NEWMSG) {1441 // the message was rejected1442 self.chatd.msgconfirm(cmd.substr(9, 8), false);1443 }1444 else if (cmd.charCodeAt(17) === Chatd.Opcode.RANGE && cmd.substr(18, 1).charCodeAt(0) === 1) {1445 // JOINRANGEHIST was rejected1446 self.chatd.onJoinRangeHistReject(1447 cmd.substr(1, 8),1448 self.shard1449 );1450 }1451 else if (cmd.charCodeAt(17) === Chatd.Opcode.MSGUPD || cmd.charCodeAt(17) === Chatd.Opcode.MSGUPDX) {1452 // the edit was rejected1453 self.chatd.editreject(cmd.substr(1, 8), cmd.substr(9, 8));1454 }1455 len = 19;1456 break;1457 case Chatd.Opcode.BROADCAST:1458 self.keepAlive.restart();1459 self.chatd.trigger('onBroadcast', {1460 chatId: base64urlencode(cmd.substr(1, 8)),1461 userId: base64urlencode(cmd.substr(9, 8)),1462 bCastCode: cmd.substr(17, 1).charCodeAt(0)1463 });1464 len = 18;1465 break;1466 case Chatd.Opcode.HISTDONE:1467 self.keepAlive.restart();1468 var chatid = cmd.substr(1, 8);1469 if (self.loggerIsEnabled) {1470 self.logger.log("History retrieval finished: " + base64urlencode(chatid));1471 }1472 // Resending of pending message should be done via the integration code,1473 // since it have more info and a direct relation with the UI related actions on pending messages1474 // (persistence, user can click resend/cancel/etc).1475 self.resendpending();1476 var chat = chatd.chatIdMessages[chatid];1477 if (chat) {1478 var loginState = chat.loginState();1479 if ((loginState !== null) && (loginState < LoginState.HISTDONE)) {1480 chat._setLoginState(LoginState.HISTDONE); // logged in1481 self.chatd.rtcHandler.onChatOnline(chat);1482 }1483 chat.restoreIfNeeded(cmd.substr(1, 8));1484 }1485 self.chatd.trigger('onMessagesHistoryDone', {1486 // TODO: Should we trigger this for unknown chats as well?1487 chatId: base64urlencode(chatid)1488 });1489 len = 9;1490 break;1491 case Chatd.Opcode.NEWKEY:1492 // self.keepAlive.restart();1493 if (self.loggerIsEnabled) {1494 self.logger.log("Set keys: " + base64urlencode(cmd.substr(1, 8)) +1495 " length: " + Chatd.unpack32le(cmd.substr(13, 4)));1496 }1497 len = Chatd.unpack32le(cmd.substr(13, 4));1498 var index = 17;1499 var keys = [];1500 while (index < len + 17) {1501 var keylen = Chatd.unpack16le(cmd.substr(index + 12, 2));1502 keys.push(1503 {1504 userId : base64urlencode(cmd.substr(index, 8)),1505 keyid : Chatd.unpack32le(cmd.substr(index + 8, 4)),1506 keylen : keylen,1507 key : cmd.substr(index + 14, keylen)1508 });1509 index += keylen + 14;1510 }1511 self.chatd.trigger('onMessageKeysDone',1512 {1513 chatId: base64urlencode(cmd.substr(1, 8)),1514 keyid : cmd.substr(9, 4),1515 keys : keys1516 }1517 );1518 len += 17;1519 break;1520 case Chatd.Opcode.KEYID:1521 if (self.loggerIsEnabled) {1522 self.logger.log("GET new key: " + base64urlencode(cmd.substr(1, 8)));1523 }1524 self.chatd.trigger('onMessagesKeyIdDone',1525 {1526 chatId: base64urlencode(cmd.substr(1, 8)),1527 keyxid: Chatd.unpack32le(cmd.substr(9, 4)),1528 keyid: Chatd.unpack32le(cmd.substr(13, 4))1529 }1530 );1531 self.chatd.keyconfirm(cmd.substr(1, 8), Chatd.unpack32le(cmd.substr(13, 4)));1532 len = 17;1533 break;1534 case Chatd.Opcode.MSGID:1535 // self.keepAlive.restart();1536 if (self.loggerIsEnabled) {1537 self.logger.log("MSG already exists: " + base64urlencode(cmd.substr(1, 8)) +1538 " - " + base64urlencode(cmd.substr(9, 8)));1539 }1540 self.chatd.msgreject(cmd.substr(1, 8), cmd.substr(9, 8));1541 len = 17;1542 break;1543 case Chatd.Opcode.ECHO:1544 len = 1;1545 self.logger.log("Ignoring received " + constStateToText(Chatd.Opcode, opcode));1546 break;1547 case Chatd.Opcode.CALLTIME:1548 self.keepAlive.restart();1549 len = 13;1550 var chatid = cmd.substr(1, 8);1551 var chat = self.chatd.chatIdMessages[chatid];1552 if (!chat) {1553 self.logger.warn("CALLTIME received for an unknown chatid", base64urlencode(chatid));1554 break;1555 }1556 var callTime = Chatd.unpack32le(cmd.substr(9, 4));1557 chat.tsCallStart = Date.now() - (callTime * 1000);1558 break;1559 case Chatd.Opcode.NUMBYHANDLE:1560 self.keepAlive.restart();1561 if (self.loggerIsEnabled) {1562 self.logger.log("Num by handle in '" +1563 base64urlencode(cmd.substr(1, 8)) + "' with " +1564 Chatd.unpack32le(cmd.substr(9, 4)) + " user(s)");1565 }1566 self.chatd.trigger('onNumByHandle', {1567 chatId: base64urlencode(cmd.substr(1, 8)),1568 count: Chatd.unpack32le(cmd.substr(9, 4))1569 });1570 len = 13;1571 break;1572 default:1573 self.logger.error(1574 "FATAL: Unknown opCode " + cmd.charCodeAt(0) +1575 ". To stop potential loop-forever case, the next commands in the buffer were rejected!"1576 );1577 // remove the command from the queue, its already processed,1578 // if this is not done, the code will loop forever1579 cmd = "";1580 }1581 if (cmd.length < len) {1582 self.logger.error(1583 "FATAL: Short WebSocket frame - got " + cmd.length + ", expected " + len +1584 ". To stop potential loop-forever case, the next commands in the buffer were rejected!"1585 );1586 // remove the command from the queue, its already processed, if this is not done,1587 // the code will loop forever1588 cmd = "";1589 break;1590 }1591 else if (Number.isNaN(len)) {1592 self.logger.error(1593 "FATAL: Internally got nextlen == NaN, with queued cmdlen: " + cmd.length + ". " +1594 "To stop potential loop-forever case, the next commands in the buffer were rejected!"1595 );1596 // remove the command from the queue, its already processed, if this is not done,1597 // the code will loop forever1598 cmd = "";1599 len = 0;1600 break;1601 }1602 cmd = cmd.substr(len);1603 }1604};1605// generate and return next msgTransactionId in sequence1606Chatd.prototype.nexttransactionid = function() {1607 for (var i = 0; i < this.msgTransactionId.length; i++) {1608 var c = (this.msgTransactionId.charCodeAt(i) + 1) & 0xff;1609 this.msgTransactionId = this.msgTransactionId.substr(0, i) +1610 String.fromCharCode(c) + this.msgTransactionId.substr(i + 1);1611 if (c) {1612 break;1613 }1614 }1615 return this.msgTransactionId;1616};1617Chatd.prototype.join = function(chatId, shardNo, url) {1618 if (this.chatIdShard[chatId]) { // this.chatidMessages[chatid] may be still missing!1619 return false;1620 }1621 // unknown chat1622 assert(!this.chatIdMessages[chatId]);1623 this._addShardAndChat(chatId, shardNo, url);1624 return true;1625};1626Chatd.prototype._sendNodeHist = function(chatId, lastMsgId, len) {1627 var shard = this.chatIdShard[chatId];1628 assert(shard, 'shard not found');1629 shard._sendNodeHist(chatId, lastMsgId, len);1630};1631Chatd.prototype.leave = function(chatId) {1632 var chat = this.chatIdMessages[chatId];1633 if (!chat) {1634 return;1635 }1636 chat._setLoginState(null);1637 // clear up pending list.1638 chat.clearpending();1639};1640// gracefully terminate all connections/calls1641Chatd.prototype.shutdown = function() {1642 this.rtcHandler.onShutdown();1643};1644// submit a new message to the chatId1645Chatd.prototype.submit = function(chatId, messages, keyId, isAttachment) {1646 if (this.chatIdMessages[chatId]) {1647 return this.chatIdMessages[chatId].submit(messages, keyId, isAttachment);1648 }1649 else {1650 return false;1651 }1652};1653// edit or delete an existing message, returns false upon failure1654Chatd.prototype.modify = function(chatId, msgnum, message) {1655 if (!this.chatIdMessages[chatId]) {1656 return false;1657 }1658 return this.chatIdMessages[chatId].modify(msgnum, message);1659};1660Chatd.Shard.prototype.msg = function(chatId, messages) {1661 var cmds = [];1662 for (var i = 0; i < messages.length; i++) {1663 var messageObj = messages[i];1664 var msgxid = messageObj.msgxid;1665 var keyid = messageObj.keyid;1666 var timestamp = messageObj.timestamp;1667 var message = messageObj.message;1668 var updated = messageObj.updated;1669 var type = messageObj.type;1670 var cmd = '';1671 if (type === Chatd.MsgType.KEY) {// this is key message;1672 cmd = [Chatd.Opcode.NEWKEY,1673 chatId + Chatd.pack32le(keyid) + Chatd.pack32le(message.length) + message];1674 } else if (type === Chatd.MsgType.EDIT) {// this is edit message;1675 cmd = [Chatd.Opcode.MSGUPD,1676 chatId + Chatd.Const.UNDEFINED + msgxid + Chatd.pack32le(0) +1677 Chatd.pack16le(updated) + Chatd.pack32le(keyid) +1678 Chatd.pack32le(message.length) + message];1679 } else {1680 cmd = [1681 messageObj.isAttachment ? Chatd.Opcode.NEWNODEMSG : Chatd.Opcode.NEWMSG,1682 chatId + Chatd.Const.UNDEFINED + msgxid + Chatd.pack32le(timestamp) +1683 Chatd.pack16le(0) + Chatd.pack32le(keyid) + Chatd.pack32le(message.length) + message1684 ];1685 }1686 cmds.push(cmd);1687 }1688 this.multicmd(cmds);1689};1690Chatd.Shard.prototype.msgupd = function(chatId, msgid, updatedelta, message, keyid) {1691 this.cmd(Chatd.Opcode.MSGUPD,1692 chatId + Chatd.Const.UNDEFINED + msgid + Chatd.pack32le(0) +1693 Chatd.pack16le(updatedelta) + Chatd.pack32le(keyid) + Chatd.pack32le(message.length) + message);1694};1695Chatd.Shard.prototype.msgupdx = function(chatId, msgxid, updatedelta, message, keyxid) {1696 this.cmd(Chatd.Opcode.MSGUPDX,1697 chatId + Chatd.Const.UNDEFINED + msgxid + Chatd.pack32le(0) +1698 Chatd.pack16le(updatedelta) + Chatd.pack32le(keyxid) +1699 Chatd.pack32le(message.length) + message);1700};1701// message storage subsystem1702Chatd.Messages = function(chatd, shard, chatId, oldInstance) {1703 // parent linkage1704 this.chatd = chatd;1705 this.shard = shard;1706 this.chatId = chatId;1707 // the message buffer can grow in two directions and is always contiguous, i.e. there are no "holes"1708 // there is no guarantee as to ordering1709 this.lownum = 2 << 28; // oldest message in buf1710 this.highnum = 2 << 28; // newest message in buf1711 this.lowSharedFiles = 2 << 28; // newest message in buf1712 this.sentid = false;1713 this.receivedid = false;1714 this.seenid = false;1715 // message format: [msgid/transactionid, userId, timestamp, message]1716 // messages in buf are indexed by a numeric id1717 this.buf = {};1718 this.sharedBuf = {};1719 this.sendingbuf = {};1720 // mapping of transactionids of messages being sent to the numeric index of this.buf1721 this.sending = {};1722 this.sendingList = [];1723 // expired message list1724 this.expired = {};1725 this.needsRestore = true;1726 this._clearCallInfo();1727 this._loginState = oldInstance ? oldInstance._loginState : LoginState.DISCONN;1728};1729Chatd.Messages.prototype._setLoginState = function(state) {1730 var oldState = this._loginState;1731 if (oldState === state) {1732 return;1733 }1734 if ((oldState !== null) && (state !== LoginState.DISCONN && state !== null && state !== oldState + 1)1735 && !(oldState === LoginState.HISTDONE && state === LoginState.JOIN_SENT)) {1736 console.error("chat %s: Invalid login state transition from %s to %s",1737 base64urlencode(this.chatId), constStateToText(LoginState, oldState), constStateToText(LoginState, state));1738 }1739 this._loginState = state;1740};1741Chatd.Messages.prototype.loginState = function() {1742 return this._loginState;1743};1744Chatd.Messages.prototype._clearCallInfo = function() {1745 this.callInfo = new CallInfo();1746};1747// send JOIN1748Chatd.Messages.prototype.join = function() {1749 var self = this;1750 var shard = self.shard;1751 if (!shard.isOnline()) {1752 return;1753 }1754 var chatId = self.chatId;1755 var chatRoom = self.chatd.megaChat.getChatById(base64urlencode(self.chatId));1756 // reset chat state before join1757 self._clearCallInfo();1758 self._setLoginState(LoginState.JOIN_SENT); // joining1759 // send a `JOIN` (if no local messages are buffered) or a `JOINRANGEHIST` (if local messages are buffered)1760 if (1761 Object.keys(self.buf).length === 0 &&1762 (!chatRoom.messagesBuff || chatRoom.messagesBuff.messages.length === 0)1763 ) {1764 // if the buff is empty and (messagesBuff not initialized (chat is initializing for the first time) OR its1765 // empty)1766 if ((chatRoom.type === "public") && (!chatRoom.members.hasOwnProperty(base64urlencode(shard.chatd.userId)))) {1767 if (chatRoom.publicChatHandle) {1768 shard.joinbyhandle(base64urldecode(chatRoom.publicChatHandle));1769 shard.hist(chatId, Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL * -1);1770 }1771 else {1772 // from mcf?1773 // .hist should trigger a JOINRANGEHIST< so no JOIN/JOINBYHANDLE needed here.1774 shard.hist(chatId, Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL * -1, true);1775 }1776 }1777 else {1778 if (self.chatd.chatdPersist) {1779 shard.hist(chatId, Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL * -1, true);1780 }1781 else {1782 shard.markAsJoinRequested(chatId);1783 shard.cmd(Chatd.Opcode.JOIN, chatId + shard.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE));1784 shard.hist(chatId, Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL * -1);1785 }1786 }1787 } else {1788 if (1789 chatRoom.type === "public" &&1790 !chatRoom.members.hasOwnProperty(base64urlencode(shard.chatd.userId)) &&1791 chatRoom.publicChatHandle1792 ) {1793 self.handlejoinrangehist(chatId, base64urldecode(chatRoom.publicChatHandle));1794 }1795 else {1796 self.joinrangehist(chatId);1797 }1798 }1799};1800Chatd.Messages.prototype.submit = function(messages, keyId, isAttachment) {1801 // messages is an array1802 var messageConstructs = [];1803 for (var i = 0; i < messages.length; i++) {1804 var message = messages[i];1805 // allocate a transactionid for the new message1806 var msgxid = this.chatd.nexttransactionid();1807 var timestamp = Math.floor(new Date().getTime() / 1000);1808 // write the new message to the message buffer and mark as in sending state1809 // FIXME: there is a tiny chance of a namespace clash between msgid and msgxid, FIX1810 var messagekey = this.getmessagekey(msgxid, message.type);1811 this.sendingbuf[++Chatd.sendingnum] =1812 [msgxid, this.chatd.userId, timestamp, message.message, (keyId >>> 0), 0, message.type];1813 this.sending[messagekey] = Chatd.sendingnum;1814 this.sendingList.push(messagekey);1815 this.persist(messagekey);1816 messageConstructs.push({1817 "msgxid": msgxid,1818 "timestamp": timestamp,1819 "keyid": keyId,1820 "updated": 0,1821 "message": message.message,1822 "type": message.type,1823 "isAttachment": isAttachment1824 });1825 }1826 var chatRoom = this.chatd.megaChat.getChatById(base64urlencode(this.chatId));1827 // if we believe to be online, send immediately1828 if (this.chatd.chatIdShard[this.chatId].isOnline() &&1829 chatRoom && chatRoom.messagesBuff && chatRoom.messagesBuff.sendingListFlushed) {1830 this.chatd.chatIdShard[this.chatId].msg(this.chatId, messageConstructs);1831 }1832 return Chatd.sendingnum >>> 0;1833};1834Chatd.Messages.prototype.getShard = function() {1835 return this.chatd.chatIdShard[this.chatId];1836};1837Chatd.Messages.prototype.markAsJoinRequested = function() {1838 var s = this.getShard();1839 if (s && s.userRequestedJoin) {1840 s.userRequestedJoin[this.chatId] = true;1841 }1842};1843Chatd.Messages.prototype.updatekeyid = function(keyid) {1844 var self = this;1845 this.sendingList.forEach(function(msgxid) {1846 if (!self.expired[msgxid] && self.sendingbuf[self.sending[msgxid]]) {1847 if (self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.KEY) {1848 return;1849 }1850 else {1851 self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.KEYID] = keyid;1852 self.persist(msgxid);1853 }1854 }1855 });1856};1857Chatd.Messages.prototype.modify = function(msgnum, message) {1858 var self = this;1859 var shard = self.chatd.chatIdShard[self.chatId];1860 if (self.loggerIsEnabled) {1861 shard.logger.debug("mod", msgnum, message);1862 }1863 var mintimestamp = Math.floor(new Date().getTime() / 1000);1864 var messagekey;1865 // modify pending message so that a potential resend includes the change1866 if (self.sendingbuf[msgnum]) {1867 // overwrite the original messsage with the edited content1868 self.sendingbuf[msgnum][Chatd.MsgField.MESSAGE] = message;1869 var pendingmsgkey = self.getmessagekey(self.sendingbuf[msgnum][Chatd.MsgField.MSGID], Chatd.MsgType.MESSAGE);1870 self.persist(pendingmsgkey);1871 messagekey = self.getmessagekey(self.sendingbuf[msgnum][Chatd.MsgField.MSGID], Chatd.MsgType.EDIT);1872 // if there is a pending edit after the pending new message,1873 // overwrite the pending edit to only keep 1 pending edit.1874 if (self.sending[messagekey]) {1875 self.sendingbuf[self.sending[messagekey]][Chatd.MsgField.UPDATED] =1876 mintimestamp - self.sendingbuf[msgnum][Chatd.MsgField.TIMESTAMP] + 1;1877 self.sendingbuf[self.sending[messagekey]][Chatd.MsgField.MESSAGE] = message;1878 }1879 // if there is no any pending edit, append a pending edit.1880 else {1881 self.sendingbuf[++Chatd.sendingnum] = [self.sendingbuf[msgnum][Chatd.MsgField.MSGID],1882 self.sendingbuf[msgnum][Chatd.MsgField.USERID],1883 self.sendingbuf[msgnum][Chatd.MsgField.TIMESTAMP],1884 message, self.sendingbuf[msgnum][Chatd.MsgField.KEYID],1885 mintimestamp - self.sendingbuf[msgnum][Chatd.MsgField.TIMESTAMP] + 1, Chatd.MsgType.EDIT];1886 self.sending[messagekey] = Chatd.sendingnum;1887 self.sendingList.push(messagekey);1888 }1889 if (self.chatd.chatIdShard[self.chatId].isOnline()) {1890 // if the orginal message is still in the pending list, send out a msgupx.1891 self.chatd.chatIdShard[self.chatId].msgupdx(self.chatId, self.sendingbuf[msgnum][Chatd.MsgField.MSGID],1892 self.sendingbuf[msgnum][Chatd.MsgField.UPDATED],1893 message, self.sendingbuf[msgnum][Chatd.MsgField.KEYID]);1894 }1895 self.persist(messagekey);1896 }1897 else if (self.buf[msgnum]) {1898 var updated = mintimestamp - self.buf[msgnum][Chatd.MsgField.TIMESTAMP];1899 messagekey = self.getmessagekey(self.buf[msgnum][Chatd.MsgField.MSGID], Chatd.MsgType.EDIT);1900 // a very quick udpate, then add by 11901 if (self.sending[messagekey]) {1902 updated = self.sendingbuf[self.sending[messagekey]][Chatd.MsgField.UPDATED] + 1;1903 } else {1904 updated = updated + 1;1905 if (updated === self.buf[msgnum][Chatd.MsgField.UPDATED]) {1906 updated = updated + 1;1907 }1908 }1909 self.sendingbuf[++Chatd.sendingnum] = [1910 self.buf[msgnum][Chatd.MsgField.MSGID],1911 self.buf[msgnum][Chatd.MsgField.USERID],1912 self.buf[msgnum][Chatd.MsgField.TIMESTAMP],1913 message,1914 self.buf[msgnum][Chatd.MsgField.KEYID],1915 updated,1916 Chatd.MsgType.EDIT1917 ];1918 self.sending[messagekey] = Chatd.sendingnum;1919 self.sendingList.push(messagekey);1920 self.persist(messagekey);1921 if (self.chatd.chatIdShard[self.chatId].isOnline()) {1922 self.chatd.chatIdShard[self.chatId].msgupd(self.chatId, self.buf[msgnum][Chatd.MsgField.MSGID],1923 updated,1924 message, self.buf[msgnum][Chatd.MsgField.KEYID]);1925 }1926 }1927};1928Chatd.Messages.prototype.clearpending = function() {1929 // mapping of transactionids of messages being sent to the numeric index of this.buf1930 var self = this;1931 this.sendingList.forEach(function(msgxid) {1932 var num = self.sending[msgxid];1933 if (!num) {1934 return ;1935 }1936 self.chatd.trigger('onMessageUpdated', {1937 chatId: base64urlencode(self.chatId),1938 userId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.USERID]),1939 messageId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.MSGID]),1940 id: num >>> 0,1941 state: 'DISCARDED',1942 keyid: self.sendingbuf[num][Chatd.MsgField.KEYID],1943 message: self.sendingbuf[num][Chatd.MsgField.MESSAGE]1944 });1945 self.removefrompersist(msgxid);1946 });1947 this.sending = {};1948 this.sendingList = [];1949 this.sendingbuf = {};1950};1951Chatd.Messages.prototype.broadcast = function(bCastCode) {1952 var self = this;1953 var chatId = self.chatId;1954 if (!chatId) {1955 // This can happen, in case the chat was recently started and the UI is still retrieving the mcc response.1956 // A simple halt/return shoud be fine.1957 return false;1958 }1959 this.chatd.cmd(Chatd.Opcode.BROADCAST, chatId, base64urldecode(u_handle) + bCastCode);1960};1961/**1962 * Resend all (OR only a specific) messages1963 * @param [restore] {Boolean}1964 */1965Chatd.Messages.prototype.resend = function(restore) {1966 var self = this;1967 restore = (typeof restore === 'undefined') ? false : restore;1968 // resend all pending new messages and modifications1969 var mintimestamp = Math.floor(new Date().getTime() / 1000);1970 var lastexpiredpendingkey = null;1971 var trivialmsgs = [];1972 this.sendingList.forEach(function(msgxid) {1973 if (!self.sending[msgxid] || !self.sendingbuf[self.sending[msgxid]]) {1974 trivialmsgs.push(msgxid);1975 }1976 else if (mintimestamp - self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TIMESTAMP] <= MESSAGE_EXPIRY) {1977 if (self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.KEY) {1978 lastexpiredpendingkey = null;1979 }1980 if (self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.EDIT) {1981 var messagekey = self.getmessagekey(self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.MSGID],1982 Chatd.MsgType.MESSAGE);1983 // if the edit is pending on the original message, then wait.1984 if (self.sending[messagekey]) {1985 return;1986 }1987 }1988 var messageConstructs = [];1989 messageConstructs.push({1990 "msgxid":self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.MSGID],1991 "timestamp":self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TIMESTAMP],1992 "keyid":self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.KEYID],1993 "updated":self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.UPDATED],1994 "message":self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.MESSAGE],1995 "type":self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE]1996 });1997 self.chatd.chatIdShard[self.chatId].msg(1998 self.chatId,1999 messageConstructs2000 );2001 }2002 else {2003 // if it is an expired message, require manul send.2004 if (self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.MESSAGE) {2005 self.chatd.trigger('onMessageUpdated', {2006 chatId: base64urlencode(self.chatId),2007 userId: base64urlencode(self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.USERID]),2008 messageId: base64urlencode(self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.MSGID]),2009 id: self.sending[msgxid] >>> 0,2010 state: restore ? 'RESTOREDEXPIRED' : 'EXPIRED',2011 keyid: self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.KEYID],2012 message: self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.MESSAGE],2013 ts:self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TIMESTAMP]2014 });2015 }2016 // if it is an expired edit, throw it away.2017 else if (self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.EDIT) {2018 trivialmsgs.push(msgxid);2019 }2020 // if it is an expired key2021 else if (self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.KEY) {2022 // if it an expired pending key2023 if (((self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.KEYID] & 0xffff0000) >>> 0)2024 === (0xffff0000 >>> 0)) {2025 lastexpiredpendingkey = msgxid;2026 }2027 }2028 self.expired[msgxid] = 1;2029 }2030 });2031 // the last key is an expired pending key, it is possible it may not be delivered to chatd, so flag2032 // strongvelope to include the key next time it sends out a message.2033 if (!restore && lastexpiredpendingkey) {2034 self.chatd.trigger('onMessageIncludeKey', {2035 chatId: base64urlencode(self.chatId),2036 userId: base64urlencode(self.sendingbuf[self.sending[lastexpiredpendingkey]][Chatd.MsgField.USERID]),2037 messageId: base64urlencode(self.sendingbuf[self.sending[lastexpiredpendingkey]][Chatd.MsgField.MSGID])2038 });2039 }2040 for (var msgkeyid in trivialmsgs) {2041 self.discard(trivialmsgs[msgkeyid]);2042 }2043};2044/**2045 * Same as .joinrangehist, but would be internally called by joinrangehist in case buff is empty,2046 * but there are messages in the UI (in which case, this func would get the low/highnums from the UI and trigger the2047 * JOINRANGEHIST with those)2048 *2049 * @param chatId2050 */2051Chatd.Messages.prototype._joinrangehistViaMessagesBuff = function() {2052 var self = this;2053 var chatId = self.chatId;2054 var chatRoom = self.chatd.megaChat.getChatById(base64urlencode(chatId));2055 // we are a lower level func, the login state is set by the higher level one2056 assert(self._loginState <= LoginState.JOIN_SENT);2057 var firstLast;2058 if (chatRoom && chatRoom.messagesBuff && chatRoom.messagesBuff.messages.length > 0) {2059 var firstLast = chatRoom.messagesBuff.getLowHighIds();2060 if (firstLast) {2061 // queued this as last to execute after this current .done cb.2062 self.chatd.trigger('onMessagesHistoryRequest', {2063 count: 0xDEAD,2064 chatId: base64urlencode(chatId)2065 });2066 self.markAsJoinRequested();2067 self.chatd.cmd(Chatd.Opcode.JOINRANGEHIST, chatId,2068 base64urldecode(firstLast[0]) + base64urldecode(firstLast[1]));2069 return;2070 }2071 }2072 if (!firstLast) {2073 if (d) {2074 console.warn("JOINRANGEHIST failed, firstLast was missing");2075 }2076 }2077 self.chatd.cmd(2078 Chatd.Opcode.JOIN,2079 chatId,2080 self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE)2081 );2082 self.chatd.trigger('onMessagesHistoryRequest', {2083 count: Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL,2084 chatId: base64urlencode(chatId)2085 });2086 self.chatd.chatIdShard[chatId]._sendHist(chatId, Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL * -1);2087};2088// after a reconnect, we tell the chatd the oldest and newest buffered message2089Chatd.Messages.prototype.handlejoinrangehist = function(chatId, handle) {2090 var self = this;2091 var low;2092 var high;2093 var requested = false;2094 for (low = this.lownum; low <= this.highnum; low++) {2095 if (2096 this.buf[low] && !this.sending[this.buf[low][Chatd.MsgField.MSGID]] &&2097 this.buf[low][Chatd.MsgField.TYPE] === Chatd.MsgType.MESSAGE2098 ) {2099 for (high = this.highnum; high > low; high--) {2100 if (2101 this.buf[high] && !this.sending[this.buf[high][Chatd.MsgField.MSGID]] &&2102 this.buf[high][Chatd.MsgField.TYPE] === Chatd.MsgType.MESSAGE2103 ) {2104 break;2105 }2106 }2107 this.chatd.chatIdShard[chatId].cmd(Chatd.Opcode.OPCODE_HANDLEJOINRANGEHIST,2108 handle + this.buf[low][Chatd.MsgField.MSGID] + this.buf[high][Chatd.MsgField.MSGID]);2109 this.chatd.trigger('onMessagesHistoryRequest', {2110 count: Chatd.MESSAGE_HISTORY_LOAD_COUNT,2111 chatId: base64urlencode(chatId)2112 });2113 requested = true;2114 break;2115 }2116 }2117 if (!requested) {2118 // self.chatd.chatIdShard[chatId].cmd(Chatd.Opcode.OPCODE_HANDLEJOINRANGEHIST,2119 // handle + Chatd.Const.UNDEFINED + Chatd.Const.UNDEFINED);2120 self.chatd.chatIdShard[chatId].cmd(2121 Chatd.Opcode.OPCODE_HANDLEJOIN,2122 handle + self.chatd.userId + String.fromCharCode(Chatd.Priv.NOCHANGE)2123 );2124 self.chatd.trigger('onMessagesHistoryRequest', {2125 count: Chatd.MESSAGE_HISTORY_LOAD_COUNT,2126 chatId: base64urlencode(chatId)2127 });2128 }2129};2130// after a reconnect, we tell the chatd the oldest and newest buffered message2131Chatd.Messages.prototype.joinrangehist = function() {2132 var self = this;2133 var chatId = self.chatId;2134 self._setLoginState(LoginState.JOIN_SENT);2135 if (self.chatd.chatdPersist) {2136 // this would be called on reconnect if messages were added to the buff,2137 // so ensure we are using the correct first/last msgIds as from the actual2138 // iDB2139 self.chatd.chatdPersist.getLowHighIds(base64urlencode(chatId))2140 .then(function(result) {2141 var chatIdMessagesObj = self.chatd.chatIdMessages[chatId];2142 if (chatIdMessagesObj) {2143 if (result[2]) {2144 chatIdMessagesObj.lownum = result[2] - 1;2145 }2146 if (result[3]) {2147 chatIdMessagesObj.highnum = result[3];2148 }2149 }2150 // queued this as last to execute after this current .done cb.2151 self.chatd.trigger('onMessagesHistoryRequest', {2152 count: 0xDEAD,2153 chatId: base64urlencode(chatId)2154 });2155 self.markAsJoinRequested();2156 self.chatd.cmd(Chatd.Opcode.JOINRANGEHIST, chatId,2157 base64urldecode(result[0]) + base64urldecode(result[1]));2158 })2159 .catch(function(ex) {2160 if (ex && d) {2161 console.warn(ex);2162 }2163 // This may be triggered by chatdPersist crashing and causing a re-connection + re-hist sync. In this2164 // case, we need to run JOINRANGEHIST otherwise, it would cause the client to (eventually) move2165 // existing msgs to lownum IDs (e.g. back to the top of the list of msgs)2166 self._joinrangehistViaMessagesBuff();2167 });2168 }2169 else {2170 var low;2171 var high;2172 var requested = false;2173 for (low = this.lownum; low <= this.highnum; low++) {2174 if (2175 this.buf[low] && !this.sending[this.buf[low][Chatd.MsgField.MSGID]] &&2176 this.buf[low][Chatd.MsgField.TYPE] === Chatd.MsgType.MESSAGE2177 ) {2178 for (high = this.highnum; high > low; high--) {2179 if (2180 this.buf[high] && !this.sending[this.buf[high][Chatd.MsgField.MSGID]] &&2181 this.buf[high][Chatd.MsgField.TYPE] === Chatd.MsgType.MESSAGE2182 ) {2183 break;2184 }2185 }2186 this.markAsJoinRequested();2187 this.chatd.cmd(Chatd.Opcode.JOINRANGEHIST, chatId,2188 this.buf[low][Chatd.MsgField.MSGID] + this.buf[high][Chatd.MsgField.MSGID]);2189 this.chatd.trigger('onMessagesHistoryRequest', {2190 count: 0xDEAD,2191 chatId: base64urlencode(chatId)2192 });2193 requested = true;2194 break;2195 }2196 }2197 if (!requested) {2198 self._joinrangehistViaMessagesBuff();2199 }2200 }2201};2202// msgid can be false in case of rejections2203Chatd.prototype.msgconfirm = function(msgxid, msgid) {2204 // CHECK: is it more efficient to keep a separate mapping of msgxid to Chatd.Messages?2205 for (var chatId in this.chatIdMessages) {2206 if (this.chatIdMessages[chatId]) {2207 var messagekey = this.chatIdMessages[chatId].getmessagekey(msgxid, Chatd.MsgType.MESSAGE);2208 if (this.chatIdMessages[chatId].sending[messagekey]) {2209 this.chatIdMessages[chatId].confirm(chatId, msgxid, msgid);2210 break;2211 }2212 }2213 }2214};2215// msg is rejected as it have already been sent, and the msgid is the confirmed msg id.2216Chatd.prototype.msgreject = function(msgxid, msgid) {2217 // CHECK: is it more efficient to keep a separate mapping of msgxid to Chatd.Messages?2218 for (var chatId in this.chatIdMessages) {2219 if (this.chatIdMessages[chatId]) {2220 var messagekey = this.chatIdMessages[chatId].getmessagekey(msgxid, Chatd.MsgType.MESSAGE);2221 if (this.chatIdMessages[chatId].sending[messagekey]) {2222 this.chatIdMessages[chatId].reject(msgxid, msgid);2223 break;2224 }2225 }2226 }2227};2228// msgid can be false in case of rejections2229Chatd.prototype.editreject = function(chatId, msgid) {2230 // CHECK: is it more efficient to keep a separate mapping of msgxid to Chatd.Messages?2231 if (this.chatIdMessages[chatId]) {2232 this.chatIdMessages[chatId].rejectedit(msgid);2233 }2234};2235// msgid can be false in case of rejections2236Chatd.prototype.keyconfirm = function(chatId, keyid) {2237 if (this.chatIdMessages[chatId]) {2238 this.chatIdMessages[chatId].confirmkey(keyid);2239 }2240};2241// store a message from chatd to local cache2242Chatd.prototype.msgstore = function(newmsg, chatId, userId, msgid, timestamp, updated, keyid, msg) {2243 if (this.chatIdMessages[chatId]) {2244 var room = this.megaChat.getChatById(base64urlencode(chatId));2245 var isShared = room && room.messagesBuff && room.messagesBuff.isRetrievingSharedFiles;2246 this.chatIdMessages[chatId].store(newmsg, userId, msgid, timestamp, updated, keyid, msg, undefined, isShared);2247 }2248};2249// modify a message2250Chatd.prototype.msgmodify = function(chatId, userid, msgid, ts, updated, keyid, msg) {2251 // an existing message has been modified2252 if (this.chatIdMessages[chatId]) {2253 this.chatIdMessages[chatId].msgmodify(userid, msgid, ts, updated, keyid, msg);2254 }2255};2256// send broadcast2257Chatd.prototype.broadcast = function(chatId, bCastCode) {2258 // an existing message has been modified2259 if (this.chatIdMessages[chatId]) {2260 this.chatIdMessages[chatId].broadcast(bCastCode);2261 }2262};2263// discard a message2264Chatd.prototype.discard = function(msgxid, chatId) {2265 var messagekey;2266 if (!chatId) {2267 for (var cId in this.chatIdMessages) {2268 if (this.chatIdMessages[cId]) {2269 messagekey = this.chatIdMessages[cId].getmessagekey(msgxid, Chatd.MsgType.MESSAGE);2270 if (this.chatIdMessages[cId].sending[messagekey]) {2271 this.chatIdMessages[cId].discard(messagekey, true);2272 break;2273 }2274 }2275 }2276 }2277 else {2278 if (this.chatIdMessages[chatId]) {2279 messagekey = this.chatIdMessages[chatId].getmessagekey(msgxid, Chatd.MsgType.MESSAGE);2280 if (this.chatIdMessages[chatId].sending[messagekey]) {2281 this.chatIdMessages[chatId].discard(messagekey, true);2282 }2283 }2284 }2285};2286// check a message2287Chatd.prototype.msgcheck = function(chatId, msgid) {2288 if (this.chatIdMessages[chatId]) {2289 this.chatIdMessages[chatId].check(chatId, msgid);2290 }2291};2292// get a message reference list2293Chatd.prototype.msgreferencelist = function(chatId) {2294 if (this.chatIdMessages[chatId]) {2295 return this.chatIdMessages[chatId].getreferencelist();2296 }2297};2298// set webrtc message handler2299Chatd.prototype.setRtcHandler = function(handler) {2300 if (handler) {2301 this.rtcHandler = handler;2302 } else {2303 this.rtcHandler = this.defaultRtcHandler;2304 }2305};2306Chatd.prototype._reinitChatIdHistory = function(chatId, resetNums) {2307 var self = this;2308 var chatIdBin = base64urldecode(chatId);2309 var oldChatIdMessages = self.chatIdMessages[chatIdBin];2310 var chatRoom = self.megaChat.getChatById(base64urlencode(chatIdBin));2311 var cdr = self.chatIdMessages[chatIdBin] = new Chatd.Messages(self, self.chatIdShard[chatIdBin], chatIdBin, oldChatIdMessages);2312 self.logger.warn('re-init chat history for "%s"', chatId, chatRoom);2313 chatRoom.messagesBuff.messageOrders = {};2314 chatRoom.messagesBuff.sharedFilesMessageOrders = {};2315 if (chatRoom.messagesBuff.messagesBatchFromHistory && chatRoom.messagesBuff.messagesBatchFromHistory.length > 0) {2316 chatRoom.messagesBuff.messagesBatchFromHistory.clear();2317 }2318 if (chatRoom.messagesBuff && chatRoom.messagesBuff.retrievedAllMessages) {2319 chatRoom.messagesBuff.retrievedAllMessages = false;2320 }2321 if (oldChatIdMessages) {2322 [2323 'lownum',2324 'highnum',2325 'sending',2326 'sentid',2327 'receivedid',2328 'seenid',2329 'sendingbuf',2330 'sending',2331 'sendingList',2332 'expired',2333 ].forEach(function (k) {2334 if (resetNums && (k === "lownum" || k === "highnum")) {2335 // skip and use "initial" nums.2336 return;2337 }2338 cdr[k] = oldChatIdMessages[k];2339 });2340 }2341};2342Chatd.prototype.onJoinRangeHistReject = function(chatIdBin, shardId) {2343 var self = this;2344 var chatIdEnc = base64urlencode(chatIdBin);2345 var promises = [];2346 backgroundNacl.workers.removeTasksByTagName("svlp:" + chatIdEnc);2347 if (self.chatdPersist && ChatdPersist.isMasterTab()) {2348 promises.push(self.chatdPersist.clearChatHistoryForChat(chatIdEnc));2349 }2350 else {2351 var chatRoom = self.megaChat.getChatById(chatIdEnc);2352 var messageKeys = clone(chatRoom.messagesBuff.messages.keys());2353 for (var i = 0; i < messageKeys.length; i++) {2354 var v = chatRoom.messagesBuff.messages[messageKeys[i]];2355 chatRoom.messagesBuff.messages.removeByKey(v.messageId);2356 }2357 }2358 MegaPromise.allDone(promises)2359 .always(function() {2360 // clear any old messages2361 self._reinitChatIdHistory(chatIdEnc, true);2362 var shard = self.shards[shardId];2363 shard._sendHist(chatIdBin, Chatd.MESSAGE_HISTORY_LOAD_COUNT_INITIAL * -1);2364 });2365};2366// msg is rejected and the confirmed msg id is msgid2367Chatd.Messages.prototype.reject = function(msgxid, msgid) {2368 var self = this;2369 var messagekey = self.getmessagekey(msgxid, Chatd.MsgType.MESSAGE);2370 var num = self.sending[messagekey];2371 if (!num) {2372 return ;2373 }2374 self.chatd.trigger('onMessageUpdated', {2375 chatId: base64urlencode(self.chatId),2376 userId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.USERID]),2377 messageId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.MSGID]),2378 id: num >>> 0,2379 state: 'DISCARDED',2380 keyid: self.sendingbuf[num][Chatd.MsgField.KEYID],2381 message: self.sendingbuf[num][Chatd.MsgField.MESSAGE]2382 });2383 var editmessagekey = self.getmessagekey(msgxid, Chatd.MsgType.EDIT);2384 var editmsgnum = self.sending[editmessagekey];2385 // we now have a proper msgid, resend MSGUPDX in case the edit crossed the execution of the command2386 if (self.sendingbuf[editmsgnum]) {2387 var neweditmessagekey = self.getmessagekey(msgid, Chatd.MsgType.EDIT);2388 var msgnum = self.getmessagenum(msgid);2389 var neweditkeyid = msgnum ?2390 self.buf[msgnum][Chatd.MsgField.KEYID] : self.sendingbuf[editmsgnum][Chatd.MsgField.KEYID];2391 self.sendingbuf[++Chatd.sendingnum] =2392 [msgid,2393 self.chatd.userId,2394 self.sendingbuf[editmsgnum][Chatd.MsgField.TIMESTAMP],2395 self.sendingbuf[editmsgnum][Chatd.MsgField.MESSAGE],2396 neweditkeyid,2397 self.sendingbuf[editmsgnum][Chatd.MsgField.UPDATED],2398 self.sendingbuf[editmsgnum][Chatd.MsgField.TYPE]2399 ];2400 self.sending[neweditmessagekey] = Chatd.sendingnum;2401 self.sendingList.push(neweditmessagekey);2402 self.persist(neweditmessagekey);2403 self.chatd.chatIdShard[self.chatId].msgupd(self.chatId, msgid,2404 self.sendingbuf[editmsgnum][Chatd.MsgField.UPDATED],2405 self.sendingbuf[editmsgnum][Chatd.MsgField.MESSAGE], neweditkeyid);2406 self.discard(editmessagekey);2407 }2408 self.discard(messagekey);2409};2410// msgid can be false in case of rejections2411Chatd.Messages.prototype.confirm = function(chatId, msgxid, msgid) {2412 var self = this;2413 var messagekey = self.getmessagekey(msgxid, Chatd.MsgType.MESSAGE);2414 var num = self.sending[messagekey];2415 if (!num) {2416 return ;2417 }2418 if (msgid === false) {2419 self.chatd.trigger('onMessageUpdated', {2420 chatId: base64urlencode(self.chatId),2421 userId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.USERID]),2422 messageId: base64urlencode(msgid),2423 id: num >>> 0,2424 state: 'DISCARDED',2425 keyid: self.sendingbuf[num][Chatd.MsgField.KEYID],2426 message: self.sendingbuf[num][Chatd.MsgField.MESSAGE]2427 });2428 }2429 else {2430 var id = ++self.highnum;2431 self.buf[id] = self.sendingbuf[num];2432 self.buf[id][Chatd.MsgField.MSGID] = msgid;2433 var keyid = self.buf[id][Chatd.MsgField.KEYID];2434 self.chatd.trigger('onMessageStore', {2435 chatId: base64urlencode(self.chatId),2436 id: id,2437 pendingid: num >>> 0,2438 messageId: base64urlencode(msgid),2439 userId: base64urlencode(self.buf[id][Chatd.MsgField.USERID]),2440 ts: self.buf[id][Chatd.MsgField.TIMESTAMP],2441 updated: self.buf[id][Chatd.MsgField.UPDATED],2442 keyid : keyid,2443 message: self.buf[id][Chatd.MsgField.MESSAGE],2444 isNew: true2445 });2446 var editmessagekey = self.getmessagekey(msgxid, Chatd.MsgType.EDIT);2447 // if we have a pending edit, discard it as the NEWMSG should use the edited content.2448 if (self.sending[editmessagekey]) {2449 self.discard(editmessagekey);2450 }2451 }2452 self.discard(messagekey);2453};2454// store message into message buf.2455Chatd.Messages.prototype.store = function(2456 newmsg,2457 userId,2458 msgid,2459 timestamp,2460 updated,2461 keyid,2462 msg,2463 skipPersist,2464 isSharedFile2465) {2466 var id;2467 if (newmsg) {2468 id = ++this.highnum;2469 }2470 else if (isSharedFile) {2471 id = this.lowSharedFiles--;2472 }2473 else {2474 id = this.lownum--;2475 }2476 // store message2477 (isSharedFile ? this.sharedBuf : this.buf)[id] = [2478 msgid,2479 userId,2480 timestamp,2481 msg,2482 keyid,2483 updated,2484 Chatd.MsgType.MESSAGE2485 ];2486 this.chatd.trigger('onMessageStore', {2487 chatId: base64urlencode(this.chatId),2488 id: id,2489 messageId: base64urlencode(msgid),2490 userId: base64urlencode(userId),2491 ts: timestamp,2492 updated: updated,2493 keyid : keyid,2494 message: msg,2495 isNew: newmsg,2496 isSharedFile: !!isSharedFile2497 });2498};2499// modify a message from message buffer2500Chatd.Messages.prototype.msgmodify = function(userid, msgid, ts, updated, keyid, msg, isRetry, isPersist) {2501 var origMsgNum;2502 // retrieve msg from chatdPersist if missing in the buff2503 if (this.chatd.chatdPersist) {2504 origMsgNum = this.getmessagenum(msgid);2505 if (!origMsgNum || !this.buf[origMsgNum]) {2506 if (isRetry) {2507 return;2508 }2509 var encChatId = base64urlencode(this.chatId);2510 var self = this;2511 this.chatd.chatdPersist.getMessageByMessageId(encChatId, base64urlencode(msgid))2512 .then(function(r) {2513 if (!r) {2514 console.error(2515 "Update message failed, msgNum was not found in either .buf, .sendingbuf or messagesBuff",2516 base64urlencode(msgid)2517 );2518 return;2519 }2520 var msgObj = r[0];2521 origMsgNum = msgObj.orderValue;2522 var done = function() {2523 self.buf[origMsgNum] = [2524 // Chatd.MsgField.MSGID,2525 base64urldecode(msgObj.msgId),2526 // Chatd.MsgField.USERID,2527 base64urldecode(msgObj.userId),2528 // Chatd.MsgField.TIMESTAMP,2529 msgObj.msgObject.delay,2530 // message,2531 "",2532 // Chatd.MsgField.KEYID,2533 msgObj.keyId,2534 // mintimestamp - Chatd.MsgField.TIMESTAMP + 1,2535 msgObj.msgObject.updated === false ? 0 : msgObj.msgObject.updated,2536 // Chatd.MsgType.EDIT2537 02538 ];2539 self.msgmodify(userid, msgid, ts, updated, keyid, msg, true);2540 };2541 if (msgObj.keyId !== 0) {2542 self.chatd.chatdPersist.retrieveAndLoadKeysFor(encChatId, msgObj.userId, msgObj.keyId)2543 .always(done);2544 }2545 else {2546 done();2547 }2548 })2549 .catch(function() {2550 if (d) {2551 console.warn(2552 "msgmodify failed, can't find", base64urlencode(msgid), " in chatdPersist, maybe the " +2553 "message was not yet retrieved?"2554 );2555 }2556 });2557 return;2558 }2559 }2560 // CHECK: is it more efficient to maintain a full hash msgid -> num?2561 // FIXME: eliminate namespace clash collision risk2562 var msgnum = this.lownum;2563 var messagekey = this.getmessagekey(msgid, Chatd.MsgType.EDIT);2564 for (var i = this.highnum; i >= this.lownum; i--) {2565 if (this.buf[i] && this.buf[i][Chatd.MsgField.MSGID] === msgid) {2566 if (2567 this.buf[i][Chatd.MsgField.KEYID] === 0 &&2568 this.buf[i][Chatd.MsgField.TYPE] === 0 &&2569 this.buf[i][Chatd.MsgField.UPDATED] === updated2570 ) {2571 // potential duplicate MSGUPD, don't do anything.2572 break;2573 }2574 // if we modified the message, remove from this.modified.2575 this.buf[i][Chatd.MsgField.MESSAGE] = msg;2576 this.buf[i][Chatd.MsgField.UPDATED] = updated;2577 if (ts) {2578 this.buf[i][Chatd.MsgField.TIMESTAMP] = ts;2579 }2580 if (keyid === 0 && base64urlencode(userid) === strongvelope.COMMANDER) {2581 // if this is message truncate2582 this.chatd.trigger('onMessageUpdated', {2583 chatId: base64urlencode(this.chatId),2584 userId: base64urlencode(userid),2585 id: i,2586 state: 'TRUNCATED',2587 keyid: keyid,2588 ts: ts,2589 message: msg,2590 messageId : base64urlencode(msgid),2591 });2592 msgnum = i;2593 } else {2594 this.chatd.trigger('onMessageUpdated', {2595 chatId: base64urlencode(this.chatId),2596 userId: base64urlencode(this.buf[i][Chatd.MsgField.USERID]),2597 id: i,2598 state: 'EDITED',2599 keyid: keyid,2600 message: msg,2601 messageId : base64urlencode(msgid),2602 updated: updated2603 });2604 if (this.sending[messagekey] && this.sendingbuf[this.sending[messagekey]]2605 && updated >= this.sendingbuf[this.sending[messagekey]][Chatd.MsgField.UPDATED]) {2606 this.discard(messagekey);2607 }2608 }2609 if (keyid !== 0 && base64urlencode(userid) !== strongvelope.COMMANDER) {2610 break;2611 }2612 }2613 if (keyid === 0 && base64urlencode(userid) === strongvelope.COMMANDER) {2614 // if this is message truncate2615 if (i < msgnum && this.buf[i]) {2616 // clear pending list if there is any.2617 this.discard(messagekey);2618 delete this.buf[i];2619 }2620 }2621 }2622 if (keyid === 0 && base64urlencode(userid) === strongvelope.COMMANDER && this.chatd.chatdPersist) {2623 // if this is a truncate, trigger the persistTruncate in chatdPersist.2624 var bufMessage = this.buf[origMsgNum];2625 this.chatd.chatdPersist.persistTruncate(base64urlencode(this.chatId), origMsgNum)2626 .always(function () {2627 // update the .buf[i] to contain the proper KEYID = 0 and USERID = api.2628 bufMessage[Chatd.MsgField.KEYID] = keyid;2629 bufMessage[Chatd.MsgField.USERID] = userid;2630 });2631 }2632};2633// discard message from message queue2634Chatd.Messages.prototype.discard = function(messagekey, notify) {2635 notify = typeof notify === 'undefined' ? false : notify;2636 var self = this;2637 var num = self.sending[messagekey];2638 if (!num) {2639 return false;2640 }2641 if (notify) {2642 self.chatd.trigger('onMessageUpdated', {2643 chatId: base64urlencode(self.chatId),2644 userId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.USERID]),2645 messageId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.MSGID]),2646 id: num >>> 0,2647 state: 'DISCARDED',2648 keyid: self.sendingbuf[num][Chatd.MsgField.KEYID],2649 message: self.sendingbuf[num][Chatd.MsgField.MESSAGE]2650 });2651 watchdog.notify('chat_event', {2652 chatId: base64urlencode(self.chatId),2653 userId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.USERID]),2654 messageId: base64urlencode(self.sendingbuf[num][Chatd.MsgField.MSGID]),2655 state: 'DISCARDED'});2656 }2657 self.removefrompersist(messagekey);2658 array.remove(self.sendingList, messagekey);2659 delete self.sending[messagekey];2660 delete self.sendingbuf[num];2661 if (self.expired[messagekey]) {2662 delete self.expired[messagekey];2663 }2664 return true;2665};2666// discard edits from pending list2667Chatd.Messages.prototype.rejectedit = function(msgid) {2668 var messagekey = this.getmessagekey(msgid, Chatd.MsgType.EDIT);2669 this.discard(messagekey);2670};2671// key confirmation in message buffer2672Chatd.Messages.prototype.confirmkey = function(keyid) {2673 var self = this;2674 // when a key is confirmed, it will remove the key from the sending list,2675 // and update the keyid of the confirmed key in persistency list,2676 // in case that the chat messages are truncated and it will not get the key from chatd after a refresh.2677 var firstkeyxkey;2678 this.sendingList.forEach(function(msgxid) {2679 if (2680 !self.expired[msgxid] &&2681 self.sendingbuf[self.sending[msgxid]] &&2682 self.sendingbuf[self.sending[msgxid]][Chatd.MsgField.TYPE] === Chatd.MsgType.KEY2683 ) {2684 firstkeyxkey = msgxid;2685 return;2686 }2687 });2688 if (!firstkeyxkey) {2689 return;2690 }2691 var promises = [];2692 // update keyid of the confirmed key in persistency list.2693 var cacheKey = base64urlencode(self.chatId) + ":" + firstkeyxkey;2694 promises.push(2695 self.chatd.messagesQueueKvStorage.getItem(cacheKey).done(2696 function(v) {2697 if (v) {2698 self.chatd.messagesQueueKvStorage.setItem(cacheKey, {2699 'messageId' : v.messageId,2700 'userId' : v.userId,2701 'timestamp' : v.timestamp,2702 'message' : v.message,2703 'keyId' : keyid,2704 'updated' : v.updated,2705 'type' : v.type2706 });2707 }2708 })2709 );2710 var prefix = base64urlencode(self.chatId);2711 var iskey = false;2712 var previouskeyid;2713 var trivialkeys = [];2714 promises.push(2715 self.chatd.messagesQueueKvStorage.eachPrefixItem(prefix, function(v) {2716 if (v.userId === self.chatd.userId) {2717 if (v.type === Chatd.MsgType.KEY) {2718 // if the previous message is a key message, then the previous key is a trivial key.2719 if (iskey) {2720 trivialkeys.push(self.getmessagekey(previouskeyid, Chatd.MsgType.KEY));2721 }2722 iskey = true;2723 previouskeyid = v.messageId;2724 }2725 else {2726 iskey = false;2727 }2728 }2729 })2730 );2731 var _updatekeyid = function() {2732 for (var keyidmsgid in trivialkeys) {2733 self.removefrompersist(trivialkeys[keyidmsgid]);2734 }2735 // remove the key message from the local pending list.2736 if (self.sendingList.indexOf(firstkeyxkey) >= 0) {2737 array.remove(self.sendingList, firstkeyxkey);2738 }2739 delete self.sendingbuf[self.sending[firstkeyxkey]];2740 delete self.sending[firstkeyxkey];2741 self.updatekeyid(keyid);2742 };2743 MegaPromise.allDone(promises).always(function() {2744 _updatekeyid();2745 });2746};2747// get msg number from msgid, wonder there should be a more efficient way to do this.2748Chatd.Messages.prototype.getmessagenum = function(msgid) {2749 var msgnum = null;2750 for (var i = this.highnum; i >= this.lownum; i--) {2751 if (this.buf[i] && this.buf[i][Chatd.MsgField.MSGID] === msgid) {2752 msgnum = i;2753 break;2754 }2755 }2756 return msgnum;2757};2758// generate a key from message id and message type2759Chatd.Messages.prototype.getmessagekey = function(msgid, type) {2760 return msgid + "-" + type ;2761};2762// persist a message or message buffer2763Chatd.Messages.prototype.persist = function(messagekey) {2764 var self = this;2765 if (!messagekey) {2766 self.sendingList.forEach(function(msgxid) {2767 var cacheKey = base64urlencode(self.chatId) + ":" + msgxid;2768 var num = self.sending[msgxid];2769 if (num) {2770 self.chatd.messagesQueueKvStorage.setItem(cacheKey, {2771 'messageId' : self.sendingbuf[num][Chatd.MsgField.MSGID],2772 'userId' : self.sendingbuf[num][Chatd.MsgField.USERID],2773 'timestamp' : self.sendingbuf[num][Chatd.MsgField.TIMESTAMP],2774 'message' : self.sendingbuf[num][Chatd.MsgField.MESSAGE],2775 'keyId' : self.sendingbuf[num][Chatd.MsgField.KEYID],2776 'updated' : self.sendingbuf[num][Chatd.MsgField.UPDATED],2777 'type' : self.sendingbuf[num][Chatd.MsgField.TYPE]2778 });2779 }2780 });2781 }2782 else {2783 var num = self.sending[messagekey];2784 if (num) {2785 var cacheKey = base64urlencode(self.chatId) + ":" + messagekey;2786 self.chatd.messagesQueueKvStorage.setItem(cacheKey, {2787 'messageId' : self.sendingbuf[num][Chatd.MsgField.MSGID],2788 'userId' : self.sendingbuf[num][Chatd.MsgField.USERID],2789 'timestamp' : self.sendingbuf[num][Chatd.MsgField.TIMESTAMP],2790 'message' : self.sendingbuf[num][Chatd.MsgField.MESSAGE],2791 'keyId' : self.sendingbuf[num][Chatd.MsgField.KEYID],2792 'updated' : self.sendingbuf[num][Chatd.MsgField.UPDATED],2793 'type' : self.sendingbuf[num][Chatd.MsgField.TYPE]2794 });2795 }2796 }2797};2798// remove a message from persistency list2799Chatd.Messages.prototype.removefrompersist = function(messagekey) {2800 var self = this;2801 var cacheKey = base64urlencode(self.chatId) + ":" + messagekey;2802 self.chatd.messagesQueueKvStorage.removeItem(cacheKey);2803};2804Chatd.Messages.prototype.restoreIfNeeded = function() {2805 if (!this.needsRestore) {2806 return;2807 }2808 this.restore();2809 this.needsRestore = false;2810};2811// restore persisted messages to sending buffer2812Chatd.Messages.prototype.restore = function() {2813 var self = this;2814 var prefix = base64urlencode(self.chatId);2815 var count = 0;2816 var tempkeyid = 0xffff0001;2817 var pendingkey = false;2818 var promises = [];2819 var keys = [];2820 var iskey = false;2821 var previouskeyid;2822 var trivialkeys = [];2823 promises.push(2824 self.chatd.messagesQueueKvStorage.eachPrefixItem(prefix, function(v) {2825 if (v.userId === self.chatd.userId) {2826 if (v.type === Chatd.MsgType.KEY) {2827 // if the previous message is a key message, then the previous key is a trivial key.2828 if (iskey) {2829 trivialkeys.push(self.getmessagekey(previouskeyid, v.type));2830 }2831 if (((v.keyId & 0xffff0000) >>> 0) === (0xffff0000 >>> 0)) {2832 pendingkey = true;2833 ++tempkeyid;2834 v.keyId = tempkeyid;2835 } else {2836 pendingkey = false;2837 }2838 var index = 0;2839 var len = v.message.length;2840 while (index < len) {2841 var recipient = v.message.substr(index, 8);2842 var keylen = Chatd.unpack16le(v.message.substr(index + 8, 2));2843 var key = v.message.substr(index + 10, keylen);2844 if (recipient === v.userId) {2845 keys.push({2846 'userId' : base64urlencode(v.userId),2847 'key' : key,2848 'keyid' : v.keyId2849 });2850 break;2851 }2852 index += (10 + keylen);2853 }2854 iskey = true;2855 previouskeyid = v.messageId;2856 }2857 else {2858 iskey = false;2859 }2860 if ((v.type !== Chatd.MsgType.KEY) || ((v.type === Chatd.MsgType.KEY) && pendingkey)) {2861 if (pendingkey) {2862 v.keyId = tempkeyid;2863 }2864 // if the message is not an edit or an edit with the original message not in the2865 // pending list, restore it.2866 var messagekey = self.getmessagekey(v.messageId, v.type);2867 if (!self.sending[messagekey]) {2868 self.sendingbuf[++Chatd.sendingnum] =2869 [v.messageId, v.userId, v.timestamp, v.message, v.keyId, v.updated, v.type];2870 self.sending[messagekey] = Chatd.sendingnum;2871 self.sendingList.push(messagekey);2872 count++;2873 }2874 }2875 }2876 })2877 );2878 var _resendPending = function() {2879 if (iskey) {2880 trivialkeys.push(self.getmessagekey(previouskeyid, Chatd.MsgType.KEY));2881 }2882 for (var keyid in trivialkeys) {2883 self.removefrompersist(trivialkeys[keyid]);2884 }2885 if (count > 0) {2886 self.chatd.trigger('onMessageKeyRestore',2887 {2888 chatId: base64urlencode(self.chatId),2889 keyxid : tempkeyid,2890 keys : keys2891 }2892 );2893 self.resend(true);2894 }2895 };2896 MegaPromise.allDone(promises).always(function() {2897 _resendPending();2898 });2899};2900// check a message2901Chatd.Messages.prototype.check = function(chatId, msgid) {2902 if (Object.keys(this.buf).length === 0) {2903 this.chatd.trigger('onMessageCheck', {2904 chatId: base64urlencode(chatId),2905 messageId: base64urlencode(msgid)2906 });2907 }2908 // If this message does not exist in the history, a HIST should be called. However, this should be handled in the2909 // implementing code (which tracks more info regarding the actual history, messages, last recv/delivered, etc2910};2911Chatd.Messages.prototype.onInCall = function(userid, clientid) {2912 var self = this;2913 // We ignore preliminary INCALLs and CALLDATAs that chatd sends before we join (for fast call answer on mobile)2914 if (self._loginState < LoginState.JOIN_RECEIVED) {2915 return;2916 }2917 var parts = self.callInfo.participants;2918 var endpointId = userid + clientid;2919 var val = parts[endpointId];2920 if (val != null) {2921 if (!isNaN(val)) {2922 if (base64urlencode(userid) !== u_handle) {2923 self.chatd.logger.warn("Received INCALL for a user that has valid A/V flags, but that user is not us");2924 }2925 } else {2926 self.chatd.logger.warn("INCALL received for user that is already known to be in the call");2927 return;2928 }2929 }2930 parts[endpointId] = true; // to be replaced by av flags when RtcModule receives CALLDATA for that peer2931 self.chatd.rtcHandler.onClientJoinedCall(self, userid, clientid);2932};2933Chatd.Messages.prototype.onEndCall = function(userid, clientid) {2934 var self = this;2935 // We ignore preliminary INCALLs and CALLDATAs that chatd sends before we join (for fast call answer on mobile)2936 if (self._loginState < LoginState.JOIN_RECEIVED) {2937 return;2938 }2939 var info = self.callInfo;2940 var parts = info.participants;2941 var endpointId = userid + clientid;2942 if (parts[endpointId] == null) {2943 self.chatd.logger.warn("ENDCALL received for user that is not known to be in the call", JSON.stringify(parts));2944 return; // Don't have it, should not normally happend2945 }2946 delete parts[endpointId];2947 if (info.participantCount() === 0) {2948 self._clearCallInfo();2949 }2950 self.chatd.rtcHandler.onClientLeftCall(self, userid, clientid);2951};2952Chatd.Messages.prototype.onUserLeftRoom = function(userid) {2953 var self = this;2954 var parts = self.callInfo.participants;2955 for (var k in parts) {2956 if (k.substr(0, 8) === userid) {2957 delete parts[k];2958 self.chatd.rtcHandler.onClientLeftCall(self, userid, k.substr(8, 4));2959 }2960 }2961};2962// get a list of reference messages2963Chatd.Messages.prototype.getreferencelist = function() {2964 var ranges = [0,1,2,3,4,5,6];2965 var refs = [];2966 var index = 0;2967 var min = 0;2968 var max = 0;2969 var pendinglen = this.sendingList.length;2970 var num;2971 var index2;2972 for (index = 0;index < ranges.length; index++) {2973 max = 1 << ranges[index];2974 // if there are not enough buffered messages, bail out.2975 if (max > (this.highnum - this.lownum) + pendinglen) {2976 break;2977 }2978 num = Math.floor(Math.random() * (max - min)) + min;2979 if (num < pendinglen) {2980 var msgkey = this.sendingList[pendinglen - num - 1];2981 if (this.sending[msgkey]) {2982 refs.push((this.sending[msgkey] >>> 0));2983 }2984 }2985 else {2986 index2 = this.highnum - (num - pendinglen);2987 if (this.buf[index2]) {2988 refs.push(base64urlencode(this.buf[index2][Chatd.MsgField.MSGID]));2989 }2990 }2991 min = max;2992 }2993 max = (this.highnum - this.lownum);2994 if (max > min) {2995 num = Math.floor(Math.random() * (max - min)) + min;2996 index2 = this.highnum - num;2997 if (this.buf[index2]) {2998 refs.push(base64urlencode(this.buf[index2][Chatd.MsgField.MSGID]));2999 }3000 }3001 return refs;3002};3003// utility functions3004Chatd.pack32le = function(x) {3005 var r = '';3006 for (var i = 4; i--;) {3007 r += String.fromCharCode(x & 255);3008 x >>>= 8;3009 }3010 return r;3011};3012Chatd.unpack32le = function(x) {3013 var r = 0;3014 for (var i = 4; i--;) {3015 r = ((r << 8) >>> 0) + x.charCodeAt(i);3016 }3017 return r;3018};3019Chatd.pack16le = function(x) {3020 var r = '';3021 for (var i = 2; i--;) {3022 r += String.fromCharCode(x & 255);3023 x >>>= 8;3024 }3025 return r;3026};3027Chatd.unpack16le = function(x) {3028 var r = 0;3029 for (var i = 2; i--;) {3030 r = ((r << 8) >>> 0) + x.charCodeAt(i);3031 }3032 return r;3033};3034Chatd.dumpToHex = function(buf, offset, cnt, nospace) {3035 var len = buf.length;3036 if (typeof offset === 'undefined') {3037 offset = 0;3038 }3039 if (offset >= len) {3040 return;3041 }3042 var end;3043 if (!cnt) {3044 end = len - 1;3045 } else {3046 end = offset + cnt - 1;3047 if (end >= len) {3048 end = len - 1;3049 }3050 }3051 var ret = '';3052 for (var i = offset; i <= end; i++) {3053 var hex = buf.charCodeAt(i).toString(16);3054 if (hex.length < 2) {3055 ret += '0';3056 }3057 ret += hex;3058 if (!nospace && i < end) {3059 ret += ' ';3060 }3061 }3062 return ret;...
base64UrlEncode.test.ts
Source:base64UrlEncode.test.ts
1import { base64UrlEncode } from "./base64UrlEncode"2describe("base64UrlEncode", () => {3 it("encodes url safe base 64", () => {4 expect(base64UrlEncode("hello")).toEqual("aGVsbG8")5 expect(base64UrlEncode(">>>")).toEqual("Pj4-")6 expect(base64UrlEncode(">>?")).toEqual("Pj4_")7 })...
base64urlencode.spec.ts
Source:base64urlencode.spec.ts
1import {expect} from 'chai';2import base64urlencode from '../src/base64urlencode';3describe('base64urlencode tests', () => {4 it('base64urlencode should be a function', () => {5 expect(base64urlencode).to.exist;6 expect(base64urlencode).to.be.a('function');7 });...
Using AI Code Generation
1var wptools = require('wptools');2var base64urlEncode = wptools.base64urlEncode;3var base64urlDecode = wptools.base64urlDecode;4var encoded = base64urlEncode('This is a test');5console.log(encoded);6var decoded = base64urlDecode(encoded);7console.log(decoded);8var wptools = require('wptools');9var base64urlEncode = wptools.base64urlEncode;10var base64urlDecode = wptools.base64urlDecode;11var encoded = base64urlEncode('This is a test');12console.log(encoded);13var decoded = base64urlDecode(encoded);14console.log(decoded);15var wptools = require('wptools');16var base64urlEncode = wptools.base64urlEncode;17var base64urlDecode = wptools.base64urlDecode;18var encoded = base64urlEncode('This is a test');19console.log(encoded);20var decoded = base64urlDecode(encoded);21console.log(decoded);22var wptools = require('wptools');23var base64urlEncode = wptools.base64urlEncode;24var base64urlDecode = wptools.base64urlDecode;25var encoded = base64urlEncode('This is a test');26console.log(encoded);27var decoded = base64urlDecode(encoded);28console.log(decoded);29var wptools = require('wptools');30var base64urlEncode = wptools.base64urlEncode;31var base64urlDecode = wptools.base64urlDecode;32var encoded = base64urlEncode('This is a test');
Using AI Code Generation
1var wptools = require('wptools');2var base64urlEncode = wptools.base64urlEncode;3console.log(base64urlEncode('test'));4var wptools = require('wptools');5var base64urlEncode = wptools.base64urlEncode;6console.log(base64urlEncode('test'));7var wptools = require('wptools');8var base64urlEncode = wptools.base64urlEncode;9console.log(base64urlEncode('test'));10var wptools = require('wptools');11var base64urlEncode = wptools.base64urlEncode;12console.log(base64urlEncode('test'));13var wptools = require('wptools');14var base64urlEncode = wptools.base64urlEncode;15console.log(base64urlEncode('test'));16var wptools = require('wptools');17var base64urlEncode = wptools.base64urlEncode;18console.log(base64urlEncode('test'));19var wptools = require('wptools');20var base64urlEncode = wptools.base64urlEncode;21console.log(base64urlEncode('test'));22var wptools = require('wptools');23var base64urlEncode = wptools.base64urlEncode;24console.log(base64urlEncode('test'));25var wptools = require('wptools');26var base64urlEncode = wptools.base64urlEncode;27console.log(base64urlEncode('test'));28var wptools = require('wptools');
Using AI Code Generation
1var wptools = require('wptools');2console.log(encoded);3var wptools = require('wptools');4var decoded = wptools.base64urlDecode('aHR0cDovL2V4YW1wbGUuY29t');5console.log(decoded);6var wptools = require('wptools');7console.log(encoded);8var wptools = require('wptools');9var decoded = wptools.base64urlDecode('aHR0cDovL2V4YW1wbGUuY29t');10console.log(decoded);11var wptools = require('wptools');12console.log(encoded);13var wptools = require('wptools');14var decoded = wptools.base64urlDecode('aHR0cDovL2V4YW1wbGUuY29t');15console.log(decoded);16var wptools = require('wptools');17console.log(encoded);18var wptools = require('wptools');19var decoded = wptools.base64urlDecode('aHR0cDovL2V4YW1wbGUuY29t');20console.log(decoded);21var wptools = require('wptools');22console.log(encoded);
Using AI Code Generation
1var wptools = require('wptools');2var str = 'This is a test';3var encoded = wptools.base64urlEncode(str);4console.log(encoded);5var wptools = require('wptools');6var str = 'VGhpcyBpcyBhIHRlc3Q=';7var decoded = wptools.base64urlDecode(str);8console.log(decoded);9var wptools = require('wptools');10var str = 'This is a test';11var encoded = wptools.base64urlEncode(str);12console.log(encoded);13var wptools = require('wptools');14var str = 'VGhpcyBpcyBhIHRlc3Q=';15var decoded = wptools.base64urlDecode(str);16console.log(decoded);17var wptools = require('wptools');18var str = 'This is a test';19var encoded = wptools.base64urlEncode(str);20console.log(encoded);21var wptools = require('wptools');22var str = 'VGhpcyBpcyBhIHRlc3Q=';23var decoded = wptools.base64urlDecode(str);24console.log(decoded);25var wptools = require('wptools');26var str = 'This is a test';27var encoded = wptools.base64urlEncode(str);28console.log(encoded);
Using AI Code Generation
1var wptools = require('wptools');2var encoded = wptools.base64urlEncode('some text to encode');3console.log(encoded);4var wptools = require('wptools');5var decoded = wptools.base64urlDecode('c29tZSB0ZXh0IHRvIGVuY29kZQ==');6console.log(decoded);7var wptools = require('wptools');8var random = wptools.randomString(10);9console.log(random);10var wptools = require('wptools');11var random = wptools.randomString(10, '0123456789');12console.log(random);13var wptools = require('wptools');14var random = wptools.randomString(10, '0123456789', 'ABCDEF');15console.log(random);16var wptools = require('wptools');17var random = wptools.randomString(10, '0123456789', 'ABCDEF', 'abcdef');18console.log(random);19var wptools = require('wptools');20var random = wptools.randomString(10, '0123456789', 'ABCDEF', 'abcdef', 'ghijklmnopqrstuvwxyz');21console.log(random);22var wptools = require('wptools');23var random = wptools.randomString(10, '0123456789', 'ABCDEF', 'abcdef', 'ghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');24console.log(random);25var wptools = require('wptools');26var random = wptools.randomString(10, '0123456789', 'ABCDEF', 'abcdef', 'ghijklmnop
Using AI Code Generation
1var wp = require('wptools');2var encoded = wp.base64urlEncode('test');3console.log(encoded);4var wp = require('wptools');5var decoded = wp.base64urlDecode('dGVzdA==');6console.log(decoded);7var wp = require('wptools');8var encoded = wp.base64urlEncode('test');9console.log(encoded);10var wp = require('wptools');11var decoded = wp.base64urlDecode('dGVzdA==');12console.log(decoded);13var wp = require('wptools');14var encoded = wp.base64urlEncode('test');15console.log(encoded);16var wp = require('wptools');17var decoded = wp.base64urlDecode('dGVzdA==');18console.log(decoded);19var wp = require('wptools');20var encoded = wp.base64urlEncode('test');21console.log(encoded);22var wp = require('wptools');23var decoded = wp.base64urlDecode('dGVzdA==');24console.log(decoded);25var wp = require('wptools');26var encoded = wp.base64urlEncode('test');27console.log(encoded);28var wp = require('wptools');29var decoded = wp.base64urlDecode('dGV
Using AI Code Generation
1const wpt = require('wpt');2const base64Url = wpt.base64urlEncode('Hello World');3console.log(base64Url);4const wpt = require('wpt');5const base64Url = wpt.base64urlDecode('SGVsbG8gV29ybGQ');6console.log(base64Url);7const wpt = require('wpt');8wpt.getLocations(function (err, data) {9 if (err) {10 console.log(err);11 } else {12 console.log(data);13 }14});15/*{16 {17 },18 {
Using AI Code Generation
1base64urlEncode(input)2base64urlEncode("Hello World");3base64urlEncode("Hello World!");4base64urlEncode("Hello World!!");5base64urlEncode("Hello World!!!");6base64urlEncode("Hello World!!!!");7base64urlEncode("Hello World!!!!!");8base64urlEncode("Hello World!!!!!!");9base64urlEncode("Hello World!!!!!!!");10base64urlEncode("Hello World!!!!!!!!");11base64urlEncode("Hello World!!!!!!!!!");12base64urlEncode("Hello World!!!!!!!!!!");13base64urlEncode("Hello World!!!!!!!!!!!");14base64urlEncode("Hello World!!!!!!!!!!!!");
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!