UNPKG

@ycryptx/tiktok-live-connector

Version:

Node.js module to receive live stream chat events like comments and gifts from TikTok LIVE

744 lines (556 loc) 27.3 kB
"use strict"; function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); } function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); } function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } } function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); } function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; } function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); } function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } } function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; } const EventEmitter = require('events'); const TikTokHttpClient = require('./lib/tiktokHttpClient.js'); const WebcastWebsocket = require('./lib/webcastWebsocket.js'); const { getRoomIdFromMainPageHtml, validateAndNormalizeUniqueId } = require('./lib/tiktokUtils.js'); const { simplifyObject } = require('./lib/webcastDataConverter.js'); const { deserializeMessage, deserializeWebsocketMessage } = require('./lib/webcastProtobuf.js'); const Config = require('./lib/webcastConfig.js'); const ControlEvents = { CONNECTED: 'connected', DISCONNECTED: 'disconnected', ERROR: 'error', RAWDATA: 'rawData', DECODEDDATA: 'decodedData', STREAMEND: 'streamEnd', WSCONNECTED: 'websocketConnected' }; const MessageEvents = { CHAT: 'chat', MEMBER: 'member', GIFT: 'gift', ROOMUSER: 'roomUser', SOCIAL: 'social', LIKE: 'like', QUESTIONNEW: 'questionNew', LINKMICBATTLE: 'linkMicBattle', LINKMICARMIES: 'linkMicArmies', LIVEINTRO: 'liveIntro', EMOTE: 'emote', ENVELOPE: 'envelope', SUBSCRIBE: 'subscribe' }; const CustomEvents = { FOLLOW: 'follow', SHARE: 'share' }; /** * Wrapper class for TikTok's internal Webcast Push Service */ var _options = /*#__PURE__*/new WeakMap(); var _uniqueStreamerId = /*#__PURE__*/new WeakMap(); var _roomId = /*#__PURE__*/new WeakMap(); var _roomInfo = /*#__PURE__*/new WeakMap(); var _clientParams = /*#__PURE__*/new WeakMap(); var _httpClient = /*#__PURE__*/new WeakMap(); var _availableGifts = /*#__PURE__*/new WeakMap(); var _websocket = /*#__PURE__*/new WeakMap(); var _isConnecting = /*#__PURE__*/new WeakMap(); var _isConnected = /*#__PURE__*/new WeakMap(); var _isPollingEnabled = /*#__PURE__*/new WeakMap(); var _isWsUpgradeDone = /*#__PURE__*/new WeakMap(); var _setOptions = /*#__PURE__*/new WeakSet(); var _setUnconnected = /*#__PURE__*/new WeakSet(); var _retrieveRoomId = /*#__PURE__*/new WeakSet(); var _fetchRoomInfo = /*#__PURE__*/new WeakSet(); var _fetchAvailableGifts = /*#__PURE__*/new WeakSet(); var _startFetchRoomPolling = /*#__PURE__*/new WeakSet(); var _fetchRoomData = /*#__PURE__*/new WeakSet(); var _tryUpgradeToWebsocket = /*#__PURE__*/new WeakSet(); var _setupWebsocket = /*#__PURE__*/new WeakSet(); var _processWebcastResponse = /*#__PURE__*/new WeakSet(); var _handleError = /*#__PURE__*/new WeakSet(); class WebcastPushConnection extends EventEmitter { // Websocket // State /** * Create a new WebcastPushConnection instance * @param {string} uniqueId TikTok username (from URL) * @param {object} [options] Connection options * @param {boolean} [options[].processInitialData=true] Process the initital data which includes messages of the last minutes * @param {boolean} [options[].fetchRoomInfoOnConnect=true] Fetch the room info (room status, streamer info, etc.) on connect (will be returned when calling connect()) * @param {boolean} [options[].enableExtendedGiftInfo=false] Enable this option to get extended information on 'gift' events like gift name and cost * @param {boolean} [options[].enableWebsocketUpgrade=true] Use WebSocket instead of request polling if TikTok offers it * @param {boolean} [options[].enableRequestPolling=true] Use request polling if no WebSocket upgrade is offered. If `false` an exception will be thrown if TikTok does not offer a WebSocket upgrade. * @param {number} [options[].requestPollingIntervalMs=1000] Request polling interval if WebSocket is not used * @param {string} [options[].sessionId=null] The session ID from the "sessionid" cookie is required if you want to send automated messages in the chat. * @param {object} [options[].clientParams={}] Custom client params for Webcast API * @param {object} [options[].requestHeaders={}] Custom request headers for axios * @param {object} [options[].websocketHeaders={}] Custom request headers for websocket.client * @param {object} [options[].requestOptions={}] Custom request options for axios. Here you can specify an `httpsAgent` to use a proxy and a `timeout` value for example. * @param {object} [options[].websocketOptions={}] Custom request options for websocket.client. Here you can specify an `agent` to use a proxy and a `timeout` value for example. */ constructor(uniqueId, options) { super(); _classPrivateMethodInitSpec(this, _handleError); _classPrivateMethodInitSpec(this, _processWebcastResponse); _classPrivateMethodInitSpec(this, _setupWebsocket); _classPrivateMethodInitSpec(this, _tryUpgradeToWebsocket); _classPrivateMethodInitSpec(this, _fetchRoomData); _classPrivateMethodInitSpec(this, _startFetchRoomPolling); _classPrivateMethodInitSpec(this, _fetchAvailableGifts); _classPrivateMethodInitSpec(this, _fetchRoomInfo); _classPrivateMethodInitSpec(this, _retrieveRoomId); _classPrivateMethodInitSpec(this, _setUnconnected); _classPrivateMethodInitSpec(this, _setOptions); _classPrivateFieldInitSpec(this, _options, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _uniqueStreamerId, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _roomId, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _roomInfo, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _clientParams, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _httpClient, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _availableGifts, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _websocket, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _isConnecting, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _isConnected, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _isPollingEnabled, { writable: true, value: void 0 }); _classPrivateFieldInitSpec(this, _isWsUpgradeDone, { writable: true, value: void 0 }); _classPrivateMethodGet(this, _setOptions, _setOptions2).call(this, options || {}); _classPrivateFieldSet(this, _uniqueStreamerId, validateAndNormalizeUniqueId(uniqueId)); _classPrivateFieldSet(this, _httpClient, new TikTokHttpClient(_classPrivateFieldGet(this, _options).requestHeaders, _classPrivateFieldGet(this, _options).requestOptions, _classPrivateFieldGet(this, _options).sessionId)); _classPrivateFieldSet(this, _clientParams, { ...Config.DEFAULT_CLIENT_PARAMS, ..._classPrivateFieldGet(this, _options).clientParams }); _classPrivateMethodGet(this, _setUnconnected, _setUnconnected2).call(this); } /** * Connects to the current live stream room * @param {string} [roomId] If you want to connect to a specific roomId. Otherwise the current roomId will be retrieved. * @returns {Promise} Promise that will be resolved when the connection is established. */ async connect(roomId = null) { if (_classPrivateFieldGet(this, _isConnecting)) { throw new Error('Already connecting!'); } if (_classPrivateFieldGet(this, _isConnected)) { throw new Error('Already connected!'); } _classPrivateFieldSet(this, _isConnecting, true); try { // roomId already specified? if (roomId) { _classPrivateFieldSet(this, _roomId, roomId); _classPrivateFieldGet(this, _clientParams).room_id = roomId; } else { await _classPrivateMethodGet(this, _retrieveRoomId, _retrieveRoomId2).call(this); } // Fetch room info if option enabled if (_classPrivateFieldGet(this, _options).fetchRoomInfoOnConnect) { await _classPrivateMethodGet(this, _fetchRoomInfo, _fetchRoomInfo2).call(this); // Prevent connections to finished rooms if (_classPrivateFieldGet(this, _roomInfo).status === 4) { throw new Error('LIVE has ended'); } } // Fetch all available gift info if option enabled if (_classPrivateFieldGet(this, _options).enableExtendedGiftInfo) { await _classPrivateMethodGet(this, _fetchAvailableGifts, _fetchAvailableGifts2).call(this); } await _classPrivateMethodGet(this, _fetchRoomData, _fetchRoomData2).call(this, true); // Sometimes no upgrade to WebSocket is offered by TikTok // In that case we use request polling (if enabled and possible) if (!_classPrivateFieldGet(this, _isWsUpgradeDone)) { if (!_classPrivateFieldGet(this, _options).enableRequestPolling) { throw new Error('TikTok does not offer a websocket upgrade and request polling is disabled (`enableRequestPolling` option).'); } if (!_classPrivateFieldGet(this, _options).sessionId) { // We cannot use request polling if the user has no sessionid defined. // The reason for this is that TikTok needs a valid signature if the user is not logged in. // Signing a request every second would generate too much traffic to the signing server. // If a sessionid is present a signature is not required. throw new Error('TikTok does not offer a websocket upgrade. Please provide a valid `sessionId` to use request polling instead.'); } _classPrivateMethodGet(this, _startFetchRoomPolling, _startFetchRoomPolling2).call(this); } _classPrivateFieldSet(this, _isConnected, true); let state = this.getState(); this.emit(ControlEvents.CONNECTED, state); return state; } catch (err) { _classPrivateMethodGet(this, _handleError, _handleError2).call(this, err, 'Error while connecting'); throw err; } finally { _classPrivateFieldSet(this, _isConnecting, false); } } /** * Disconnects the connection to the live stream */ disconnect() { if (_classPrivateFieldGet(this, _isConnected)) { if (_classPrivateFieldGet(this, _isWsUpgradeDone) && _classPrivateFieldGet(this, _websocket).connection.connected) { _classPrivateFieldGet(this, _websocket).connection.close(); } // Reset state _classPrivateMethodGet(this, _setUnconnected, _setUnconnected2).call(this); this.emit(ControlEvents.DISCONNECTED); } } /** * Get the current connection state including the cached room info and all available gifts (if `enableExtendedGiftInfo` option enabled) * @returns {object} current state object */ getState() { return { isConnected: _classPrivateFieldGet(this, _isConnected), upgradedToWebsocket: _classPrivateFieldGet(this, _isWsUpgradeDone), roomId: _classPrivateFieldGet(this, _roomId), roomInfo: _classPrivateFieldGet(this, _roomInfo), availableGifts: _classPrivateFieldGet(this, _availableGifts) }; } /** * Get the current room info (including streamer info, room status and statistics) * @returns {Promise} Promise that will be resolved when the room info has been retrieved from the API */ async getRoomInfo() { // Retrieve current room_id if not connected if (!_classPrivateFieldGet(this, _isConnected)) { await _classPrivateMethodGet(this, _retrieveRoomId, _retrieveRoomId2).call(this); } await _classPrivateMethodGet(this, _fetchRoomInfo, _fetchRoomInfo2).call(this); return _classPrivateFieldGet(this, _roomInfo); } /** * Get a list of all available gifts including gift name, image url, diamont cost and a lot of other information * @returns {Promise} Promise that will be resolved when all available gifts has been retrieved from the API */ async getAvailableGifts() { await _classPrivateMethodGet(this, _fetchAvailableGifts, _fetchAvailableGifts2).call(this); return _classPrivateFieldGet(this, _availableGifts); } /** * Sends a chat message into the current live room using the provided session cookie * @param {string} text Message Content * @param {string} [sessionId] The "sessionid" cookie value from your TikTok Website if not provided via the constructor options * @returns {Promise} Promise that will be resolved when the chat message has been submitted to the API */ async sendMessage(text, sessionId) { var _response$data; if (sessionId) { // Update sessionId _classPrivateFieldGet(this, _options).sessionId = sessionId; } if (!_classPrivateFieldGet(this, _options).sessionId) { throw new Error('Missing SessionId. Please provide your current SessionId to use this feature.'); } try { // Retrieve current room_id if not connected if (!_classPrivateFieldGet(this, _isConnected)) { await _classPrivateMethodGet(this, _retrieveRoomId, _retrieveRoomId2).call(this); } // Add the session cookie to the CookieJar _classPrivateFieldGet(this, _httpClient).setSessionId(_classPrivateFieldGet(this, _options).sessionId); // Submit the chat request let requestParams = { ..._classPrivateFieldGet(this, _clientParams), content: text }; let response = await _classPrivateFieldGet(this, _httpClient).postFormDataToWebcastApi('room/chat/', requestParams, null); // Success? if ((response === null || response === void 0 ? void 0 : response.status_code) === 0) { return response.data; } // Handle errors switch (response === null || response === void 0 ? void 0 : response.status_code) { case 20003: throw new Error('Your SessionId has expired. Please provide a new one.'); default: throw new Error(`TikTok responded with status code ${response === null || response === void 0 ? void 0 : response.status_code}: ${response === null || response === void 0 ? void 0 : (_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.message}`); } } catch (err) { throw new Error(`Failed to send chat message. ${err.message}`); } } /** * Decodes and processes a binary webcast data package that you have received via the `rawData` event (for debugging purposes only) * @param {string} messageType * @param {Buffer} messageBuffer */ async decodeProtobufMessage(messageType, messageBuffer) { switch (messageType) { case 'WebcastResponse': { let decodedWebcastResponse = deserializeMessage(messageType, messageBuffer); _classPrivateMethodGet(this, _processWebcastResponse, _processWebcastResponse2).call(this, decodedWebcastResponse); break; } case 'WebcastWebsocketMessage': { let decodedWebcastWebsocketMessage = await deserializeWebsocketMessage(messageBuffer); if (typeof decodedWebcastWebsocketMessage.webcastResponse === 'object') { _classPrivateMethodGet(this, _processWebcastResponse, _processWebcastResponse2).call(this, decodedWebcastWebsocketMessage.webcastResponse); } break; } default: { let webcastMessage = deserializeMessage(messageType, messageBuffer); _classPrivateMethodGet(this, _processWebcastResponse, _processWebcastResponse2).call(this, { messages: [{ decodedData: webcastMessage, type: messageType }] }); } } } } function _setOptions2(providedOptions) { _classPrivateFieldSet(this, _options, Object.assign({ // Default processInitialData: true, fetchRoomInfoOnConnect: true, enableExtendedGiftInfo: false, enableWebsocketUpgrade: true, enableRequestPolling: true, requestPollingIntervalMs: 1000, sessionId: null, clientParams: {}, requestHeaders: {}, websocketHeaders: {}, requestOptions: {}, websocketOptions: {} }, providedOptions)); } function _setUnconnected2() { _classPrivateFieldSet(this, _roomInfo, null); _classPrivateFieldSet(this, _isConnecting, false); _classPrivateFieldSet(this, _isConnected, false); _classPrivateFieldSet(this, _isPollingEnabled, false); _classPrivateFieldSet(this, _isWsUpgradeDone, false); _classPrivateFieldGet(this, _clientParams).cursor = ''; _classPrivateFieldGet(this, _clientParams).internal_ext = ''; } async function _retrieveRoomId2() { try { let mainPageHtml = await _classPrivateFieldGet(this, _httpClient).getMainPage(`@${_classPrivateFieldGet(this, _uniqueStreamerId)}/live`); try { let roomId = getRoomIdFromMainPageHtml(mainPageHtml); _classPrivateFieldSet(this, _roomId, roomId); _classPrivateFieldGet(this, _clientParams).room_id = roomId; } catch (err) { // Use fallback method let roomData = await _classPrivateFieldGet(this, _httpClient).getJsonObjectFromTiktokApi('api-live/user/room/', { ..._classPrivateFieldGet(this, _clientParams), uniqueId: _classPrivateFieldGet(this, _uniqueStreamerId), sourceType: 54 }); _classPrivateFieldSet(this, _roomId, roomData.data.user.roomId); _classPrivateFieldGet(this, _clientParams).room_id = roomData.data.user.roomId; } } catch (err) { throw new Error(`Failed to retrieve room_id from page source. ${err.message}`); } } async function _fetchRoomInfo2() { try { let response = await _classPrivateFieldGet(this, _httpClient).getJsonObjectFromWebcastApi('room/info/', _classPrivateFieldGet(this, _clientParams)); _classPrivateFieldSet(this, _roomInfo, response.data); } catch (err) { throw new Error(`Failed to fetch room info. ${err.message}`); } } async function _fetchAvailableGifts2() { try { let response = await _classPrivateFieldGet(this, _httpClient).getJsonObjectFromWebcastApi('gift/list/', _classPrivateFieldGet(this, _clientParams)); _classPrivateFieldSet(this, _availableGifts, response.data.gifts); } catch (err) { throw new Error(`Failed to fetch available gifts. ${err.message}`); } } async function _startFetchRoomPolling2() { _classPrivateFieldSet(this, _isPollingEnabled, true); let sleepMs = ms => new Promise(resolve => setTimeout(resolve, ms)); while (_classPrivateFieldGet(this, _isPollingEnabled)) { try { await _classPrivateMethodGet(this, _fetchRoomData, _fetchRoomData2).call(this, false); } catch (err) { _classPrivateMethodGet(this, _handleError, _handleError2).call(this, err, 'Error while fetching webcast data via request polling'); } await sleepMs(_classPrivateFieldGet(this, _options).requestPollingIntervalMs); } } async function _fetchRoomData2(isInitial) { let webcastResponse = await _classPrivateFieldGet(this, _httpClient).getDeserializedObjectFromWebcastApi('im/fetch/', _classPrivateFieldGet(this, _clientParams), 'WebcastResponse', isInitial); let upgradeToWsOffered = !!webcastResponse.wsUrl && !!webcastResponse.wsParam; if (!webcastResponse.cursor) { if (isInitial) { throw new Error('Missing cursor in initial fetch response.'); } else { _classPrivateMethodGet(this, _handleError, _handleError2).call(this, null, 'Missing cursor in fetch response.'); } } // Set cursor and internal_ext param to continue with the next request if (webcastResponse.cursor) _classPrivateFieldGet(this, _clientParams).cursor = webcastResponse.cursor; if (webcastResponse.internalExt) _classPrivateFieldGet(this, _clientParams).internal_ext = webcastResponse.internalExt; if (isInitial) { // Upgrade to Websocket offered? => Try upgrade if (_classPrivateFieldGet(this, _options).enableWebsocketUpgrade && upgradeToWsOffered) { await _classPrivateMethodGet(this, _tryUpgradeToWebsocket, _tryUpgradeToWebsocket2).call(this, webcastResponse); } } // Skip processing initial data if option disabled if (isInitial && !_classPrivateFieldGet(this, _options).processInitialData) { return; } _classPrivateMethodGet(this, _processWebcastResponse, _processWebcastResponse2).call(this, webcastResponse); } async function _tryUpgradeToWebsocket2(webcastResponse) { try { // Websocket specific params let wsParams = { imprp: webcastResponse.wsParam.value, compress: 'gzip' }; // Wait until ws connected, then stop request polling await _classPrivateMethodGet(this, _setupWebsocket, _setupWebsocket2).call(this, webcastResponse.wsUrl, wsParams); _classPrivateFieldSet(this, _isWsUpgradeDone, true); _classPrivateFieldSet(this, _isPollingEnabled, false); this.emit(ControlEvents.WSCONNECTED, _classPrivateFieldGet(this, _websocket)); } catch (err) { _classPrivateMethodGet(this, _handleError, _handleError2).call(this, err, 'Upgrade to websocket failed'); } } async function _setupWebsocket2(wsUrl, wsParams) { return new Promise((resolve, reject) => { _classPrivateFieldSet(this, _websocket, new WebcastWebsocket(wsUrl, _classPrivateFieldGet(this, _httpClient).cookieJar, _classPrivateFieldGet(this, _clientParams), wsParams, _classPrivateFieldGet(this, _options).websocketHeaders, _classPrivateFieldGet(this, _options).websocketOptions)); _classPrivateFieldGet(this, _websocket).on('connect', wsConnection => { resolve(); wsConnection.on('error', err => _classPrivateMethodGet(this, _handleError, _handleError2).call(this, err, 'Websocket Error')); wsConnection.on('close', () => { this.disconnect(); }); }); _classPrivateFieldGet(this, _websocket).on('connectFailed', err => reject(`Websocket connection failed, ${err}`)); _classPrivateFieldGet(this, _websocket).on('webcastResponse', msg => _classPrivateMethodGet(this, _processWebcastResponse, _processWebcastResponse2).call(this, msg)); _classPrivateFieldGet(this, _websocket).on('messageDecodingFailed', err => _classPrivateMethodGet(this, _handleError, _handleError2).call(this, err, 'Websocket message decoding failed')); // Hard timeout if the WebSocketClient library does not handle connect errors correctly. setTimeout(() => reject('Websocket not responding'), 30000); }); } function _processWebcastResponse2(webcastResponse) { // Emit raw (protobuf encoded) data for a use case specific processing webcastResponse.messages.forEach(message => { this.emit(ControlEvents.RAWDATA, message.type, message.binary); }); // Process and emit decoded data depending on the the message type webcastResponse.messages.filter(x => x.decodedData).forEach(message => { var _simplifiedObj$displa, _simplifiedObj$displa2; let simplifiedObj = simplifyObject(message.decodedData); this.emit(ControlEvents.DECODEDDATA, message.type, simplifiedObj, message.binary); switch (message.type) { case 'WebcastControlMessage': // Known control actions: // 3 = Stream terminated by user // 4 = Stream terminated by platform moderator (ban) const action = message.decodedData.action; if ([3, 4].includes(action)) { this.emit(ControlEvents.STREAMEND, { action }); this.disconnect(); } break; case 'WebcastRoomUserSeqMessage': this.emit(MessageEvents.ROOMUSER, simplifiedObj); break; case 'WebcastChatMessage': this.emit(MessageEvents.CHAT, simplifiedObj); break; case 'WebcastMemberMessage': this.emit(MessageEvents.MEMBER, simplifiedObj); break; case 'WebcastGiftMessage': // Add extended gift info if option enabled if (Array.isArray(_classPrivateFieldGet(this, _availableGifts)) && simplifiedObj.giftId) { simplifiedObj.extendedGiftInfo = _classPrivateFieldGet(this, _availableGifts).find(x => x.id === simplifiedObj.giftId); } this.emit(MessageEvents.GIFT, simplifiedObj); break; case 'WebcastSocialMessage': this.emit(MessageEvents.SOCIAL, simplifiedObj); if ((_simplifiedObj$displa = simplifiedObj.displayType) !== null && _simplifiedObj$displa !== void 0 && _simplifiedObj$displa.includes('follow')) { this.emit(CustomEvents.FOLLOW, simplifiedObj); } if ((_simplifiedObj$displa2 = simplifiedObj.displayType) !== null && _simplifiedObj$displa2 !== void 0 && _simplifiedObj$displa2.includes('share')) { this.emit(CustomEvents.SHARE, simplifiedObj); } break; case 'WebcastLikeMessage': this.emit(MessageEvents.LIKE, simplifiedObj); break; case 'WebcastQuestionNewMessage': this.emit(MessageEvents.QUESTIONNEW, simplifiedObj); break; case 'WebcastLinkMicBattle': this.emit(MessageEvents.LINKMICBATTLE, simplifiedObj); break; case 'WebcastLinkMicArmies': this.emit(MessageEvents.LINKMICARMIES, simplifiedObj); break; case 'WebcastLiveIntroMessage': this.emit(MessageEvents.LIVEINTRO, simplifiedObj); break; case 'WebcastEmoteChatMessage': this.emit(MessageEvents.EMOTE, simplifiedObj); break; case 'WebcastEnvelopeMessage': this.emit(MessageEvents.ENVELOPE, simplifiedObj); break; case 'WebcastSubNotifyMessage': this.emit(MessageEvents.SUBSCRIBE, simplifiedObj); break; } }); } function _handleError2(exception, info) { if (this.listenerCount(ControlEvents.ERROR) > 0) { this.emit(ControlEvents.ERROR, { info, exception }); } } module.exports = { WebcastPushConnection, signatureProvider: require('./lib/tiktokSignatureProvider'), webcastProtobuf: require('./lib/webcastProtobuf.js') };