UNPKG

@taptalk.io/web-sdk

Version:

TapTalk.io Chat SDK for Web

1,418 lines (1,160 loc) 263 kB
/* 22-04-2026 00:00 v1.41.0 */ // Changes: // 1. fix compress image file var define, CryptoJS; var CryptoJS = require('./lib/crypto-js'); var md5 = require('./lib/md5'); var tapTalkRooms = {}; //room list with array of messages var tapTalkRoomListHashmap = {}; //room list last message var tapTalkRoomListHashmapPinned = {}; //room list last message - pinned var tapTalkRoomListHashmapUnPinned = {}; //room list last message - unpinned var tapTalkRoomListIDPinned = {}; //room list id pinned // var tapTalkEmitMessageQueue = {}; //room list undelivered message var tapRoomStatusListeners = []; var tapMessageListeners = []; var tapRoomListListeners = []; var tapContactListeners = []; var tapListener = []; var taptalkContact = {}; var tapTalkRandomColors = ['#f99181', '#a914db', '#f26046', '#fb76ab', '#c4c9d1', '#4239be', '#9c89f1', '#f4c22c']; var projectConfigs = null; var expiredKey = []; var refreshAccessTokenCallbackArray = []; var isConnectRunning = false; var isDoneFirstSetupRoomList = false; var isWithoutRoomList = false; var isNeedToCallApiUpdateRoomList = true; var isFirstConnectedToWebSocket = false; var taptalkStarMessageHashmap = {}; var taptalkUnreadMessageList = {}; var taptalkPinnedMessageHashmap = {}; var taptalkPinnedMessageIDHashmap = {}; var taptalkIndexedDBNotSupport = "Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available."; var taptalkMessageReadCount = {}; const MAX_PINNED_ROOM = 10; var db; // window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; //initiate index db for local file(image, video, file) function addFileToDB(fileID, base64, fileType) { if (db) { let tx = db.transaction(['files'], 'readwrite'); let store = tx.objectStore('files'); var objectStoreRequest = store.get(fileID); objectStoreRequest.onsuccess = function (event) { if (!objectStoreRequest.result) { let file = { file: base64, type: fileType, timestamp: new Date().valueOf() }; store.add(file, fileID) } }; // tx.oncomplete = function() { // } tx.onerror = function (event) { console.log('error storing note files' + event.target.errorCode); } } else { console.log(taptalkIndexedDBNotSupport); } } function deleteExpiredFileKey() { if (db) { let tx = db.transaction(['files'], 'readwrite'); let store = tx.objectStore('files'); if (expiredKey.length > 0) { for (let i in expiredKey) { store.delete(expiredKey[i]) } } } else { console.log(taptalkIndexedDBNotSupport); } } (function () { if (!window.indexedDB) { console.log(taptalkIndexedDBNotSupport); } else { var dbTapTalk = indexedDB.open('tapFiles', 1); dbTapTalk.onupgradeneeded = function (event) { db = event.target.result; let notes = db.createObjectStore('files'); } dbTapTalk.onsuccess = function (event) { db = event.target.result; let tx = db.transaction(['files'], 'readwrite'); let store = tx.objectStore('files'); var objectStoreRequest = store.getAll(); var objectKeyRequest = store.getAllKeys(); objectStoreRequest.onsuccess = function (event) { if (!objectStoreRequest.result) { let file = { file: base64, type: fileType, timestamp: new Date().valueOf() }; store.add(file, fileID) } }; objectKeyRequest.onsuccess = function (event) { for (let i in objectKeyRequest.result) { module.exports.tapCoreChatRoomManager.getFileFromDB(objectKeyRequest.result[i], function (data) { //two weeks from now will be deleted if ((new Date().valueOf() - data.timestamp) > 1576155138) { expiredKey.push(objectKeyRequest.result[i]); } if (i === ((objectKeyRequest.result.length - 1).toString())) { deleteExpiredFileKey(); } }) } }; } dbTapTalk.onerror = function (event) { console.log('error opening database ' + event.target.errorCode); } } })(); var authenticationHeader = { // "Content-Type": "application/json", "App-Key": "", "Authorization": "", "Device-Identifier": "", "Device-Model": navigator.appName, "Device-Platform": "web", // "Server-Key": "" }; var baseApiUrl = ""; var webSocket = null; const ROOM_TYPE = { PERSONAL: 1, GROUP: 2, CHANNEL: 3 } const KEY_PASSWORD_ENCRYPTOR = "kHT0sVGIKKpnlJE5BNkINYtuf19u6+Kk811iMuWQ5tM"; //listen connection status window.addEventListener('offline', function () { isNeedToCallApiUpdateRoomList = true; }); //listen connection status function bytesToSize(bytes) { var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes == 0) return '0 Byte'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(2).replace('.00', '') + ' ' + sizes[i]; } function getDeviceID() { let localDeviceID = localStorage.getItem('tapTalk.DeviceID'); let md5DeviceID = md5(navigator.userAgent + "@" + new Date().valueOf()); let generateDeviceID = md5DeviceID.substring(0, 16) + "-" + guid(); if (localDeviceID !== null) { return localDeviceID; } localStorage.setItem('tapTalk.DeviceID', generateDeviceID); return generateDeviceID; } class WebWorker { constructor(worker) { const code = worker.toString(); const blob = new Blob(["(" + code + ")()"]); return new Worker(URL.createObjectURL(blob)); } } // var reader = new FileReader(); const SOCKET_START_TYPING = "chat/startTyping"; const SOCKET_STOP_TYPING = "chat/stopTyping"; const EVENT_OPEN_ROOM = "chat/openRoom"; const SOCKET_CLOSE_ROOM = "chat/closeRoom"; const SOCKET_NEW_MESSAGE = "chat/sendMessage"; const SOCKET_UPDATE_MESSAGE = "chat/updateMessage"; const SOCKET_DELETE_MESSAGE = "chat/deleteMessage"; const SOCKET_OPEN_MESSAGE = "chat/openMessage"; const SOCKET_AUTHENTICATION = "user/authentication"; const SOCKET_USER_ONLINE_STATUS = "user/status"; const SOCKET_USER_UPDATED = "user/updated"; const SOCKET_USER_BLOCKED = "user/block"; const SOCKET_USER_UNBLOCKED = "user/unblock"; const SOCKET_CLEAR_CHAT_ROOM = "room/clearChat"; const SOCKET_SCHEDULED_MESSAGE = "room/scheduleMessage"; const CHAT_MESSAGE_TYPE_TEXT = 1001; const CHAT_MESSAGE_TYPE_IMAGE = 1002; const CHAT_MESSAGE_TYPE_VIDEO = 1003; const CHAT_MESSAGE_TYPE_FILE = 1004; const CHAT_MESSAGE_TYPE_LOCATION = 1005; const CHAT_MESSAGE_TYPE_CONTACT = 1006; const CHAT_MESSAGE_TYPE_STICKER = 1007; const CHAT_MESSAGE_TYPE_VOICE = 1008; const CHAT_MESSAGE_TYPE_AUDIO = 1009; const CHAT_MESSAGE_TYPE_LINK = 1010; const CHAT_MESSAGE_TYPE_PRODUCT = 2001; const CHAT_MESSAGE_TYPE_CATEORY = 2002; const CHAT_MESSAGE_TYPE_PAYMENT_CONFIRMATION = 2004; const CHAT_MESSAGE_TYPE_SYSTEM_MESSAGE = 9001; const CHAT_MESSAGE_TYPE_UNREAD_MESSAGE_IDENTIFIER = 9002; const MESSAGE_ID = "0"; function doXMLHTTPRequest(method, header, url, data, isMultipart = false) { return new Promise(function (resolve, reject) { let xhr = new XMLHttpRequest(); xhr.open(method, url, true); for (let headerVal in header) { xhr.setRequestHeader(headerVal, header[headerVal]); } xhr.send(method === 'POST' && isMultipart ? data : JSON.stringify(data)); xhr.onload = function () { if (xhr.status === 200) { resolve(JSON.parse(xhr.response)); } else { reject({ status: xhr.status, statusText: xhr.statusText }); } }; xhr.onerror = function () { reject({ status: xhr.status, statusText: xhr.statusText }); }; }); } function doXMLHTTPRequestToBase64(method, header, url, data, message, onProgress) { let sendProgressDownload = async (oEvent) => { if (oEvent.lengthComputable) { var percentComplete = oEvent.loaded / oEvent.total * 100; onProgress(message, Math.round(percentComplete * 10) / 10, oEvent.loaded); } } return new Promise(function (resolve, reject) { let xhrBase64 = new XMLHttpRequest(); xhrBase64.addEventListener("progress", sendProgressDownload); xhrBase64.open(method, url, true); for (let headerVal in header) { xhrBase64.setRequestHeader(headerVal, header[headerVal]); } xhrBase64.responseType = 'arraybuffer'; xhrBase64.send(JSON.stringify(data)); xhrBase64.onload = function () { if (xhrBase64.status === 200) { let convertToBase64 = () => { let uInt8Array = new Uint8Array(xhrBase64.response); let i = uInt8Array.length; let binaryString = new Array(i); while (i--) { binaryString[i] = String.fromCharCode(uInt8Array[i]); } let data = binaryString.join(''); let base64 = window.btoa(data); return base64; }; if (xhrBase64.getResponseHeader('content-type') === "application/json") { var enc = new TextDecoder("utf-8"); resolve(JSON.parse(enc.decode(xhrBase64.response))); } else { resolve({ base64: convertToBase64(), contentType: xhrBase64.getResponseHeader('content-type') }); } } else { reject({ status: xhrBase64.status, statusText: xhrBase64.statusText }); } }; xhrBase64.onerror = function () { reject({ status: xhrBase64.status, statusText: xhrBase64.statusText }); }; }); } function doXMLHTTPRequestUpload(method, header, url, data, onProgress) { let sendProgressUpload = async (oEvent) => { if (oEvent.lengthComputable) { var percentComplete = Math.round((oEvent.loaded / oEvent.total * 100) * 10) / 10; onProgress(percentComplete, oEvent.loaded); } } return new Promise(function (resolve, reject) { let xhrUpload = new XMLHttpRequest(); xhrUpload.open(method, url, true); for (let headerVal in header) { xhrUpload.setRequestHeader(headerVal, header[headerVal]); } xhrUpload.upload.addEventListener("progress", sendProgressUpload); xhrUpload.send(data); xhrUpload.onload = function () { if (xhrUpload.status === 200) { resolve(JSON.parse(xhrUpload.response)); } else { reject({ status: xhrUpload.status, statusText: xhrUpload.statusText }); } }; xhrUpload.onerror = function () { reject({ status: xhrUpload.status, statusText: xhrUpload.statusText }); }; }); } function getLocalStorageObject(storage) { return JSON.parse(decryptKey(localStorage.getItem(storage), KEY_PASSWORD_ENCRYPTOR)); } function generateHeaderQuerystring() { let keys = { "content_type": authenticationHeader["Content-Type"], "app_key": authenticationHeader["App-Key"], "authorization": `Bearer ${getLocalStorageObject('TapTalk.UserData').accessToken}`, "device_identifier": authenticationHeader["Device-Identifier"], "device_model": authenticationHeader["Device-Model"], "device_platform": "web", } var s = []; for (var i in keys) { s.push(i + "=" + encodeURIComponent(keys[i])); } return s.join("&"); } function setUserDataStorage(response) { let data = response; data.logout = false; return localStorage.setItem('TapTalk.UserData', encryptKey(JSON.stringify(data), KEY_PASSWORD_ENCRYPTOR)); } function guid() { let guidChar = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; let result = ""; let guidCharLength = guidChar.length; for (var i = 0; i < 32; i++) { result += guidChar.charAt(Math.floor(Math.random() * guidCharLength)); } return result; } function isFileAllowed(fileType, file) { let fileTypeAllowed = false; for (let type in fileType) { if (fileType[type] === file) { fileTypeAllowed = true; } } return fileTypeAllowed; } var tapReader = new FileReader(); tapReader.onload = function () { var messages = this.result.split('\n'); for (let i in messages) { var m = JSON.parse(messages[i]); // console.log(m) if (isDoneFirstSetupRoomList) { handleEmit(m); } switch (m.eventName) { case "chat/sendMessage": for (let i in tapMessageListeners) { tapMessageListeners[i].onReceiveNewMessage(m.data, m.isScheduled); } break; case "chat/updateMessage": for (let i in tapMessageListeners) { tapMessageListeners[i].onReceiveUpdateMessage(m.data); } break; case "chat/startTyping": for (let i in tapRoomStatusListeners) { tapRoomStatusListeners[i].onReceiveStartTyping(m.data.roomID, m.data.user); } break; case "chat/stopTyping": for (let i in tapRoomStatusListeners) { tapRoomStatusListeners[i].onReceiveStopTyping(m.data.roomID, m.data.user); } break; case "user/status": for (let i in tapRoomStatusListeners) { tapRoomStatusListeners[i].onReceiveOnlineStatus(m.data.user, m.data.isOnline, m.data.lastActive); } break; case SOCKET_SCHEDULED_MESSAGE: for (let i in tapRoomStatusListeners) { tapRoomStatusListeners[i].onReceiveScheduledMessage(m.data.room, m.data.timestamp); } break; case SOCKET_CLEAR_CHAT_ROOM: for (let i in tapRoomListListeners) { tapRoomListListeners[i].onChatRoomDeleted(m.data.room.roomID); } break; case SOCKET_USER_BLOCKED: for (let i in tapContactListeners) { tapContactListeners[i].onContactBlocked(m.data.user); } break; case SOCKET_USER_UNBLOCKED: for (let i in tapContactListeners) { tapContactListeners[i].onContactUnblocked(m.data.user); } break; } } tapMsgQueue.processNext(); }; function handleEmit(emit) { switch (emit.eventName) { case "chat/sendMessage": handleNewMessage(emit.data); break; case "chat/updateMessage": handleUpdateMessage(emit.data); break; case SOCKET_CLEAR_CHAT_ROOM: module.exports.tapCoreChatRoomManager.deleteMessageByRoomID(emit.data.room.roomID); break; } } var handleNewMessage = (message) => { let _this = this; let user = this.taptalk.getTaptalkActiveUser(); let removeRoom = (roomID) => { delete tapTalkRoomListHashmap[roomID]; if (tapTalkRoomListHashmapPinned[roomID]) { delete tapTalkRoomListHashmapPinned[roomID]; } if (tapTalkRoomListHashmapUnPinned[roomID]) { delete tapTalkRoomListHashmapUnPinned[roomID]; } if (taptalkStarMessageHashmap[roomID]) { delete taptalkStarMessageHashmap[roomID]; } delete tapTalkRooms[roomID]; } let mergeTaptalkRooms = (obj, src) => { for (var key in src) { if (src.hasOwnProperty(key)) obj[key] = src[key]; } return obj; } if (user.userID !== message.user.userID) { this.tapCoreMessageManager.markMessageAsDelivered([message.messageID]); } message.body = decryptKey(message.body, message.localID); if (message.data !== "") { message.data = JSON.parse(decryptKey(message.data, message.localID)); } if (message.quote.content !== "") { message.quote.content = decryptKey(message.quote.content, message.localID); } let isRoomExist = tapTalkRooms[message.room.roomID]; if (isRoomExist) { if (!isRoomExist.messages[message.localID]) { tapTalkRooms[message.room.roomID].messages = Object.assign({ [message.localID]: message }, tapTalkRooms[message.room.roomID].messages); var currentIndex = tapTalkRooms[message.room.roomID]; delete tapTalkRooms[message.room.roomID]; tapTalkRooms = Object.assign({ [message.room.roomID]: currentIndex }, tapTalkRooms); } else { isRoomExist.messages[message.localID] = message; } } else { var roomID = message.room.roomID; var newRoom = { [roomID]: { messages: {}, hasMore: true, lastUpdated: 0 } } newRoom[roomID].messages[message.localID] = message; tapTalkRooms = mergeTaptalkRooms(newRoom, tapTalkRooms); } module.exports.tapCoreRoomListManager.setRoomListLastMessage(message, 'new emit'); //if delete room if (message.action === 'room/delete' && message.type === CHAT_MESSAGE_TYPE_SYSTEM_MESSAGE) { removeRoom(message.room.roomID); } //if leave group if ((message.action === 'room/leave' && message.type === 9001) && module.exports.taptalk.getTaptalkActiveUser().userID === message.user.userID) { removeRoom(message.room.roomID); } //handle pin - unpin if (window.Worker) { //new pinned if (message.action === "message/pin" && taptalkPinnedMessageIDHashmap[message.room.roomID] && !taptalkPinnedMessageIDHashmap[message.room.roomID][message.data.messageID]) { let _messagePin = { ...message }; let newMes = _messagePin.data; newMes.body = decryptKey(newMes.body, newMes.localID); newMes.created = newMes.createdTime; if (newMes.data !== "") { newMes.data = decryptKey(newMes.data, newMes.localID); } var newPinMessagePinned = new WebWorker(() => self.addEventListener('message', function (e) { let { _pinnedMessage, _pinnedMessageID, _message, _roomID, isClose } = e.data; if (!isClose) { if (_pinnedMessageID[_roomID]) { _pinnedMessageID[_roomID][_message.messageID] = true; } if (_pinnedMessage[_roomID]) { _pinnedMessage[_roomID].messages.push(_message); } else { _pinnedMessage[_roomID] = { hasMore: false, messages: [_message], pageNumber: 1, totalItems: 1, totalPages: 1 }; } self.postMessage({ result: { _taptalkPinnedMessageHashmap: _pinnedMessage, _taptalkPinnedMessageIDHashmap: _pinnedMessageID } }) } else { self.close(); } })); newPinMessagePinned.postMessage({ _pinnedMessage: taptalkPinnedMessageHashmap, _pinnedMessageID: taptalkPinnedMessageIDHashmap, _message: newMes, _roomID: message.room.roomID }); newPinMessagePinned.addEventListener('message', (e) => { let { result } = e.data; taptalkPinnedMessageIDHashmap = result._taptalkPinnedMessageIDHashmap; taptalkPinnedMessageHashmap = result._taptalkPinnedMessageHashmap; module.exports.taptalkHelper.orderArrayFromLargestToSmallest(taptalkPinnedMessageHashmap[message.room.roomID].messages, "created", "desc", (new_arr) => { taptalkPinnedMessageHashmap[message.room.roomID].messages = new_arr; }); newPinMessagePinned.postMessage({ isClose: true }); }); } //new pinend //new unpinned if (message.action === "message/unpin" && taptalkPinnedMessageIDHashmap[message.room.roomID] && taptalkPinnedMessageIDHashmap[message.room.roomID][message.data.messageID]) { var newUnpinMessagePinned = new WebWorker(() => self.addEventListener('message', function (e) { let { _pinnedMessage, _pinnedMessageID, _message, _roomID, isClose } = e.data; if (!isClose) { let actionRemove = () => { let indexMes = _pinnedMessage[_message.room.roomID].messages.findIndex(val => val.messageID === _message.data.messageID); delete _pinnedMessageID[_message.room.roomID][_message.data.messageID]; if (indexMes !== -1) { _pinnedMessage[_message.room.roomID].messages.splice(indexMes, 1); } } if (_pinnedMessageID[_message.room.roomID]) { actionRemove(); } self.postMessage({ result: { _taptalkPinnedMessageHashmap: _pinnedMessage, _taptalkPinnedMessageIDHashmap: _pinnedMessageID } }) } else { self.close(); } })); newUnpinMessagePinned.postMessage({ _pinnedMessage: taptalkPinnedMessageHashmap, _pinnedMessageID: taptalkPinnedMessageIDHashmap, _message: message }); newUnpinMessagePinned.addEventListener('message', (e) => { let { result } = e.data; taptalkPinnedMessageIDHashmap = result._taptalkPinnedMessageIDHashmap; taptalkPinnedMessageHashmap = result._taptalkPinnedMessageHashmap; newUnpinMessagePinned.postMessage({ isClose: true }); }); } //new unpinned } else { console.log("Worker is not supported"); } //handle pin - unpin if (message.room && tapTalkRoomListHashmap[message.room.roomID] && (message.action === "user/update" || message.action === "room/update")) { // Handle room update tapTalkRoomListHashmap[message.room.roomID].lastMessage.room = message.room; } } var handleUpdateMessage = (message) => { let isRoomExist = tapTalkRooms[message.room.roomID]; message.body = decryptKey(message.body, message.localID); if (message.data !== "") { message.data = JSON.parse(decryptKey(message.data, message.localID)); } if (message.quote.content !== "") { message.quote.content = decryptKey(message.quote.content, message.localID); } if (isRoomExist) { tapTalkRooms[message.room.roomID].messages[message.localID] = message; if (message.isRead) { // for(var i in tapTalkRooms[message.room.roomID].messages) { // tapTalkRooms[message.room.roomID].messages[i].isRead = true; // } tapTalkRooms[message.room.roomID].messages[message.localID].isRead = true } module.exports.tapCoreRoomListManager.setRoomListLastMessage(message, 'update emit', true); } if (window.Worker) { //delete message pinned listener if (message.isDeleted) { var deleteMessagePinned = new WebWorker(() => self.addEventListener('message', function (e) { let { _pinnedMessage, _pinnedMessageID, _message, isClose } = e.data; if (!isClose) { if (_pinnedMessageID[_message.room.roomID]) { delete _pinnedMessageID[_message.room.roomID][_message.messageID]; } if (_pinnedMessage[_message.room.roomID]) { let _idx = _pinnedMessage[_message.room.roomID].messages.findIndex(v => v.messageID === _message.messageID); if (_idx !== -1) { _pinnedMessage[_message.room.roomID].messages.splice(_idx, 1); } } self.postMessage({ result: { _taptalkPinnedMessageHashmap: _pinnedMessage, _taptalkPinnedMessageIDHashmap: _pinnedMessageID } }) } else { self.close(); } })); deleteMessagePinned.postMessage({ _pinnedMessage: taptalkPinnedMessageHashmap, _pinnedMessageID: taptalkPinnedMessageIDHashmap, _message: message }); deleteMessagePinned.addEventListener('message', (e) => { let { result } = e.data; taptalkPinnedMessageHashmap = result._taptalkPinnedMessageHashmap; taptalkPinnedMessageIDHashmap = result._taptalkPinnedMessageIDHashmap; deleteMessagePinned.postMessage({ isClose: true }); }); } //delete message pinned listener //edit message pinned listener if (Object.keys(taptalkPinnedMessageHashmap).length > 0 && Object.keys(taptalkPinnedMessageIDHashmap).length > 0) { var editMessagePinned = new WebWorker(() => self.addEventListener('message', function (e) { let { _pinnedMessage, _pinnedMessageID, _message, isClose } = e.data; if (!isClose) { if (_pinnedMessage[_message.room.roomID]) { let _idx = _pinnedMessage[_message.room.roomID].messages.findIndex(v => v.messageID === _message.messageID); if (_idx !== -1) { _pinnedMessage[_message.room.roomID].messages[_idx] = _message; } } self.postMessage({ result: { _taptalkPinnedMessageHashmap: _pinnedMessage, _taptalkPinnedMessageIDHashmap: _pinnedMessageID } }) } else { self.close(); } })); editMessagePinned.postMessage({ _pinnedMessage: taptalkPinnedMessageHashmap, _pinnedMessageID: taptalkPinnedMessageIDHashmap, _message: message }); editMessagePinned.addEventListener('message', (e) => { let { result } = e.data; taptalkPinnedMessageHashmap = result._taptalkPinnedMessageHashmap; taptalkPinnedMessageIDHashmap = result._taptalkPinnedMessageIDHashmap; editMessagePinned.postMessage({ isClose: true }); }); } //edit message pinned listener } else { console.log("Worker is not supported"); } } class TapMessageQueue { constructor() { this.queue = []; this.isRunning = false; this.callback = null; } setCallback(callback) { if (typeof (callback) !== "function") { throw new Error("callback must be function"); } this.callback = callback; } addToQueue(item) { this.queue.push(item); if (!this.isRunning) { this.isRunning = true; this.processNext(); } } processNext(stopIfEmpty) { if (this.queue.length != 0) { this.callback(this.queue.shift()); } else if (!stopIfEmpty) { setTimeout(() => { if (this.queue.length > 0) { this.processNext(); } else { this.isRunning = false; } }, 100); } // else { // this.isRunning = false; // } } } var tapMsgQueue = new TapMessageQueue(); tapMsgQueue.setCallback((emit) => { tapReader.readAsText(emit); }); class TapEmitMessageQueue { constructor() { this.emitQueue = []; this.isRunningMessageQueue = false; } runEmitQueue() { if (!navigator.onLine || !module.exports.taptalk.isConnected()) { this.isRunningMessageQueue = false; } else { this.isRunningMessageQueue = true; } if (this.emitQueue.length > 0 && this.isRunningMessageQueue) { webSocket.send(this.emitQueue[0]); this.emitQueue.shift(); this.runEmitQueue(); } else { this.isRunningMessageQueue = false; return; } } pushEmitQueue(emit) { this.emitQueue.push(emit); if (!this.isRunningMessageQueue) { this.runEmitQueue(); } } } var tapEmitMsgQueue = new TapEmitMessageQueue(); class TapScheduledMessageQueue { constructor() { this.scheduledItemQueue = []; this.isRunningScheduledMessageQueue = false; } runScheduledMessageQueue() { if (!navigator.onLine || !module.exports.taptalk.isConnected()) { this.isRunningScheduledMessageQueue = false; } else { this.isRunningScheduledMessageQueue = true; } if (this.scheduledItemQueue.length > 0 && this.isRunningScheduledMessageQueue) { const message = this.scheduledItemQueue[0].message; const scheduledTime = this.scheduledItemQueue[0].scheduledTime; const callback = this.scheduledItemQueue[0].callback; let handleScheduledMessageFinished = () => { this.scheduledItemQueue.shift(); this.runScheduledMessageQueue(); } if (scheduledTime > new Date().valueOf()) { module.exports.tapCoreMessageManager.createScheduledMessage(message, scheduledTime, { onSuccess: (response) => { callback.onSuccess(response); handleScheduledMessageFinished(); }, onError: (errCode, errMes) => { callback.onError(errCode, errMes); handleScheduledMessageFinished(); } }, false); } else { module.exports.tapCoreMessageManager.sendCustomMessage(message, () => { callback.onSuccess({ success: false, message: "Message was sent", createdItem: message }); handleScheduledMessageFinished(); }); } } else { this.isRunningScheduledMessageQueue = false; return; } } pushScheduledMessageQueue(message, scheduledTime, callback) { const scheduledItem = { message: message, scheduledTime: scheduledTime, callback: callback }; this.scheduledItemQueue.push(scheduledItem); if (!this.isRunningScheduledMessageQueue) { this.runScheduledMessageQueue(); } } } var scheduledMessageQueue = new TapScheduledMessageQueue(); //image compress var urlToFile = (url, filename, mimeType) => { return ( fetch(url) .then(function (res) { return res.arrayBuffer(); }) .then(function (buf) { return new File([buf], filename, { type: mimeType }); }) ); }; let compressImageFile = (file, widthVal, heightVal) => { return new Promise(function (resolve, reject) { let fileName = file.name; let reader = new FileReader(); let readerCanvasImage = new FileReader(); reader.readAsDataURL(file); reader.onload = event => { let img = new Image(); img.src = event.target.result; img.onload = () => { let elem = document.createElement('canvas'); elem.width = widthVal; elem.height = heightVal; let ctx = elem.getContext('2d'); ctx.drawImage(img, 0, 0, widthVal, heightVal); ctx.canvas.toBlob((blob) => { let finalFile; if (!blob) { console.warn("Compression failed, using original file"); // 🔥 fallback to original file finalFile = file; } else { finalFile = new File([blob], fileName, { type: file.type, lastModified: Date.now() }); } readerCanvasImage.readAsDataURL(finalFile); }, file.type, 0.6); }, reader.onerror = error => console.log(error); }; readerCanvasImage.onload = event => { urlToFile(event.target.result, file.name, file.type) .then((file) => { resolve({ file: file, src: event.target.result }) }); } }) } exports.taptalkHelper = { orderArrayFromLargestToSmallest: (array, key, dir, callback) => { if (window.Worker) { var orderArrayFromLargestToSmallestWorker = new WebWorker(() => self.addEventListener('message', function (e) { let { _array, _key, _dir, isClose } = e.data; if (!isClose) { let sortArray = (a, k) => { var temp = 0; for (var i = 0; i < a.length; i++) { for (var j = i; j < a.length; j++) { if (_dir === "desc") { if (a[j][k] > a[i][k]) { temp = a[j]; a[j] = a[i]; a[i] = temp; } } else { if (a[j][k] < a[i][k]) { temp = a[j]; a[j] = a[i]; a[i] = temp; } } } } return a; } let resultNewArray = sortArray(_array, _key); self.postMessage({ result: { newArray: resultNewArray, error: "" } }) } else { self.close(); } })); orderArrayFromLargestToSmallestWorker.postMessage({ _array: array, _key: key, _dir: dir }); orderArrayFromLargestToSmallestWorker.addEventListener('message', (e) => { let { result } = e.data; callback(result.newArray); if (result.error !== "") { console.log("Room not found") } orderArrayFromLargestToSmallestWorker.postMessage({ isClose: true }); }); } else { console.log("Worker is not supported"); } }, helperDecryptKey(str, key) { return decryptKey(str, key); }, helperEncryptKey(str, key) { return encryptKey(str, key); }, getUrlsFromString(string) { // const expression = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi; // const expression = /(\b((https?|ftp|file):\/\/)?[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; const expression = /(\b((https?|ftp|file):\/\/)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?)/gi; const regex = new RegExp(expression); const urls = string.match(regex); if (urls !== null && urls !== undefined) { return urls; } return []; } } exports.taptalk = { forTesting: () => { let data = { // _tapTalkEmitMessageQueue: tapTalkEmitMessageQueue, _taptalkRooms: tapTalkRooms, _tapTalkRoomListHashmap: tapTalkRoomListHashmap, _tapTalkRoomListHashmapPinned: tapTalkRoomListHashmapPinned, _tapTalkRoomListHashmapUnPinned: tapTalkRoomListHashmapUnPinned, _emitQueuue: tapEmitMsgQueue.emitQueue } return data; }, init: (appID, appSecret, baseUrlApi, withOutRoomList = false) => { authenticationHeader["App-Key"] = btoa(`${appID}:${appSecret}`); // authenticationHeader["Server-Key"] = btoa(`${serverID}:${serverSecret}`); authenticationHeader["Device-Identifier"] = getDeviceID(); baseApiUrl = baseUrlApi; if (withOutRoomList) { isDoneFirstSetupRoomList = true; isWithoutRoomList = true; } this.taptalk.refreshProjectConfigs(); }, getDeviceID: () => { let localDeviceID = localStorage.getItem('tapTalk.DeviceID'); let md5DeviceID = md5(navigator.userAgent + "@" + new Date().valueOf()); let generateDeviceID = md5DeviceID.substring(0, 16) + "-" + guid(); if (localDeviceID !== null) { return localDeviceID; } localStorage.setItem('tapTalk.DeviceID', generateDeviceID); return generateDeviceID; }, addTapListener: (callback) => { tapListener.push(callback); }, checkErrorResponse: (response, callbackOnMethod = null, callbackAfterRefresh = null) => { if (response.status !== 200) { if (response.status === 401) { if (response.error.code === "40104") { this.taptalk.refreshAccessToken(callbackAfterRefresh); } else { refreshAccessTokenCallbackArray = []; for (let i in tapListener) { Object.keys(tapListener[i]).map((_callback) => { if (_callback === 'onTapTalkRefreshTokenExpired') { tapListener[i][_callback](); } }) } } } else { if (callbackOnMethod !== null) { callbackOnMethod.onError(response.error.code, response.error.message) } } } }, authenticateWithAuthTicket: (authTicket, connectOnSuccess, callback) => { let url = `${baseApiUrl}/v1/auth/access_token/request`; let _this = this; setTimeout(() => { authenticationHeader["Authorization"] = `Bearer ${authTicket}`; doXMLHTTPRequest('POST', authenticationHeader, url, "") .then(function (response) { if (response.error.code === "") { setUserDataStorage(response.data); callback.onSuccess('Request access token success'); connectOnSuccess && _this.taptalk.testAccessToken(callback); } else { callback.onError(response.error.code, response.error.message); } }) .catch(function (err) { console.error('there was an error!', err); }); }, 300); }, testAccessToken: (callback) => { authenticationHeader["Authorization"] = `Bearer ${getLocalStorageObject('TapTalk.UserData').accessToken}`; let url = `${baseApiUrl}/connect?check=1`; let _this = this; doXMLHTTPRequest('GET', authenticationHeader, url, "") .then(function (response) { if (response.error.code === "") { // _this.connect(callback); callback.onSuccess(); } else { _this.taptalk.checkErrorResponse(response, callback, () => { _this.taptalk.testAccessToken(callback) }); } }) .catch(function (err) { console.error('Augh, there was an error!', err); setTimeout(() => { _this.taptalk.testAccessToken(callback); }, 1000) }); }, connect: (callback) => { if (!isConnectRunning) { isConnectRunning = true; this.taptalk.testAccessToken({ onSuccess: () => { if (window["WebSocket"]) { authenticationHeader["Authorization"] = `Bearer ${getLocalStorageObject('TapTalk.UserData').accessToken}`; var protocol = ""; protocol = baseApiUrl.includes("https") ? "https" : "http"; var url = ``; if (protocol === "https") { url = `wss://${baseApiUrl.replace('https://', '')}/connect?${generateHeaderQuerystring()}` } else { url = `ws://${baseApiUrl.replace('http://', '')}/connect?${generateHeaderQuerystring()}`; } webSocket = new WebSocket(url); webSocket.onopen = function () { callback.onSuccess('Successfully connected to TapTalk.io server'); tapEmitMsgQueue.runEmitQueue(); if (tapUplQueue.queue.length > 0) { tapUplQueue.processNext(); } scheduledMessageQueue.runScheduledMessageQueue(); isFirstConnectedToWebSocket = true; } webSocket.onclose = function () { callback.onClose('Disconnected from TapTalk.io server'); }; webSocket.onerror = function () { callback.onError('Error while connecting to web socket'); } webSocket.onmessage = function (evt) { if (isFirstConnectedToWebSocket) { tapMsgQueue.addToQueue(evt.data); } }; isConnectRunning = false; } else { isConnectRunning = false; alert("Your browser does not support WebSockets."); callback(null, 'cannot connect to websocket'); } }, onError: (errorCode, errorMessage) => { isConnectRunning = false; callback.onError((errorCode, errorMessage)); } }) } }, disconnect: () => { return webSocket ? webSocket.close() : false; }, isConnected: () => { return webSocket ? webSocket.readyState === 1 : false; }, refreshAccessToken: (callback) => { let runCallbackRefreshToken = () => { if (refreshAccessTokenCallbackArray.length > 0) { refreshAccessTokenCallbackArray[0](); refreshAccessTokenCallbackArray.shift(); runCallbackRefreshToken(); } else { return; } }; refreshAccessTokenCallbackArray.push(callback); if (this.taptalk.isAuthenticated()) { if (refreshAccessTokenCallbackArray.length < 2) { let url = `${baseApiUrl}/v1/auth/access_token/refresh`; setTimeout(() => { authenticationHeader["Authorization"] = `Bearer ${getLocalStorageObject('TapTalk.UserData').refreshToken}`; doXMLHTTPRequest('POST', authenticationHeader, url, "") .then(function (response) { if (response.error.code === "") { setUserDataStorage(response.data); runCallbackRefreshToken(); } else { refreshAccessTokenCallbackArray = []; for (let i in tapListener) { Object.keys(tapListener[i]).map((callback) => { if (callback === 'onTapTalkRefreshTokenExpired') { tapListener[i][callback](); } }) } } }) .catch(function (err) { console.error('there was an error!', err); }); }, 300); } } else { return; } }, isAuthenticated: () => { return ( getLocalStorageObject("TapTalk.UserData") ? getLocalStorageObject("TapTalk.UserData").accessToken ? true : false : false ) }, clearTaptalkChatData: () => { localStorage.removeItem('TapTalk.UserData'); tapTalkRooms = {}; //room list with array of messages tapTalkRoomListHashmap = {}; //room list last message tapTalkRoomListHashmapPinned = {}; //room list last message - pinned tapTalkRoomListHashmapUnPinned = {}; //room list last message - unpinned tapRoomStatusListeners = []; tapMessageListeners = []; tapRoomListListeners = []; tapContactListeners = []; tapListener = []; taptalkContact = {}; projectConfigs = null; expiredKey = []; refreshAccessTokenCallbackArray = []; isConnectRunning = false; isDoneFirstSetupRoomList = false; isNeedToCallApiUpdateRoomList = true; isFirstConnectedToWebSocket = false; }, logoutAndClearAllTapTalkData: (callback) => { let url = `${baseApiUrl}/v1/client/logout`; let _this = this; if (this.taptalk.isAuthenticated()) { authenticationHeader["Authorization"] = `Bearer ${getLocalStorageObject('TapTalk.UserData').accessToken}`; doXMLHTTPRequest('POST', authenticationHeader, url, "") .then(function (response) { // if(response.error.code === "") { // callback.onSuccess("Logged out successfully"); // }else {