/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { createSelector, } = require("resource://devtools/client/shared/vendor/reselect.js"); /** * Returns list of messages that are visible to the user. * Filtered messages by types and text are factored in. */ const getDisplayedMessages = createSelector( state => state.messages, ({ messages, messageFilterType, showControlFrames, messageFilterText, currentChannelId, }) => { if (!currentChannelId || !messages.get(currentChannelId)) { return []; } const messagesArray = messages.get(currentChannelId); if (messageFilterType === "all" && messageFilterText.length === 0) { return messagesArray.filter(message => typeFilter(message, messageFilterType, showControlFrames) ); } const filter = searchFilter(messageFilterText); // If message payload is > 10,000 characters long, we check the LongStringActor payload string return messagesArray.filter( message => (message.payload.initial ? filter(message.payload.initial) : filter(message.payload)) && typeFilter(message, messageFilterType, showControlFrames) ); } ); function typeFilter(message, messageFilterType, showControlFrames) { const controlFrames = [0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf]; const isControlFrame = controlFrames.includes(message.opCode); if (messageFilterType === "all" || messageFilterType === message.type) { return showControlFrames || !isControlFrame; } return false; } function searchFilter(messageFilterText) { let regex; if (looksLikeRegex(messageFilterText)) { try { regex = regexFromText(messageFilterText); } catch (e) {} } return regex ? payload => regex.test(payload) : payload => payload.includes(messageFilterText); } function looksLikeRegex(text) { return text.startsWith("/") && text.endsWith("/") && text.length > 2; } function regexFromText(text) { return new RegExp(text.slice(1, -1), "im"); } /** * Checks if the selected message is visible. * If the selected message is not visible, the SplitBox component * should not show the MessagePayload component. */ const isSelectedMessageVisible = createSelector( state => state.messages, getDisplayedMessages, ({ selectedMessage }, displayedMessages) => displayedMessages.some(message => message === selectedMessage) ); /** * Returns the current selected message. */ const getSelectedMessage = createSelector( state => state.messages, ({ selectedMessage }) => (selectedMessage ? selectedMessage : undefined) ); /** * Returns summary data of the list of messages that are visible to the user. * Filtered messages by types and text are factored in. */ const getDisplayedMessagesSummary = createSelector( getDisplayedMessages, displayedMessages => { let firstStartedMs = +Infinity; let lastEndedMs = -Infinity; let sentSize = 0; let receivedSize = 0; let totalSize = 0; displayedMessages.forEach(message => { if (message.type == "received") { receivedSize += message.payload.length; } else if (message.type == "sent") { sentSize += message.payload.length; } totalSize += message.payload.length; if (message.timeStamp < firstStartedMs) { firstStartedMs = message.timeStamp; } if (message.timeStamp > lastEndedMs) { lastEndedMs = message.timeStamp; } }); return { count: displayedMessages.length, totalMs: (lastEndedMs - firstStartedMs) / 1000, sentSize, receivedSize, totalSize, }; } ); /** * Returns if the currentChannelId is closed */ const isCurrentChannelClosed = createSelector( state => state.messages, ({ closedConnections, currentChannelId }) => closedConnections.has(currentChannelId) ); /** * Returns the closed connection details of the currentChannelId * Null, if the connection is still open */ const getClosedConnectionDetails = createSelector( state => state.messages, ({ closedConnections, currentChannelId }) => closedConnections.get(currentChannelId) ); module.exports = { getSelectedMessage, isSelectedMessageVisible, getDisplayedMessages, getDisplayedMessagesSummary, isCurrentChannelClosed, getClosedConnectionDetails, };