tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

messages.js (8155B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  SELECT_REQUEST,
      9  MSG_ADD,
     10  MSG_SELECT,
     11  MSG_OPEN_DETAILS,
     12  MSG_CLEAR,
     13  MSG_TOGGLE_FILTER_TYPE,
     14  MSG_TOGGLE_CONTROL,
     15  MSG_SET_FILTER_TEXT,
     16  MSG_TOGGLE_COLUMN,
     17  MSG_RESET_COLUMNS,
     18  MSG_CLOSE_CONNECTION,
     19  CHANNEL_TYPE,
     20  SET_EVENT_STREAM_FLAG,
     21 } = require("resource://devtools/client/netmonitor/src/constants.js");
     22 
     23 /**
     24 * The default column states for the MessageListItem component.
     25 */
     26 const defaultColumnsState = {
     27  data: true,
     28  size: false,
     29  time: true,
     30 };
     31 
     32 const defaultWSColumnsState = {
     33  ...defaultColumnsState,
     34  opCode: false,
     35  maskBit: false,
     36  finBit: false,
     37 };
     38 
     39 const defaultSSEColumnsState = {
     40  ...defaultColumnsState,
     41  eventName: false,
     42  lastEventId: false,
     43  retry: false,
     44 };
     45 
     46 /**
     47 * Returns a new object of default cols.
     48 */
     49 function getMessageDefaultColumnsState(channelType) {
     50  let columnsState = defaultColumnsState;
     51  const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
     52 
     53  if (channelType === WEB_SOCKET) {
     54    columnsState = defaultWSColumnsState;
     55  } else if (channelType === EVENT_STREAM) {
     56    columnsState = defaultSSEColumnsState;
     57  }
     58 
     59  return Object.assign({}, columnsState);
     60 }
     61 
     62 /**
     63 * This structure stores list of all WebSocket and EventSource messages received
     64 * from the backend.
     65 */
     66 function Messages(initialState = {}) {
     67  const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
     68 
     69  return {
     70    // Map with all requests (key = resourceId, value = array of message objects)
     71    messages: new Map(),
     72    messageFilterText: "",
     73    // Default filter type is "all",
     74    messageFilterType: "all",
     75    showControlFrames: false,
     76    selectedMessage: null,
     77    messageDetailsOpen: false,
     78    currentChannelId: null,
     79    currentChannelType: null,
     80    currentRequestId: null,
     81    closedConnections: new Map(),
     82    columns: null,
     83    sseColumns: getMessageDefaultColumnsState(EVENT_STREAM),
     84    wsColumns: getMessageDefaultColumnsState(WEB_SOCKET),
     85    ...initialState,
     86  };
     87 }
     88 
     89 /**
     90 * When a network request is selected,
     91 * set the current resourceId affiliated with the connection.
     92 */
     93 function setCurrentChannel(state, action) {
     94  if (!action.request) {
     95    return state;
     96  }
     97 
     98  const { id, cause, resourceId, isEventStream } = action.request;
     99  const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
    100  let currentChannelType = null;
    101  let columnsKey = "columns";
    102  if (cause.type === "websocket") {
    103    currentChannelType = WEB_SOCKET;
    104    columnsKey = "wsColumns";
    105  } else if (isEventStream) {
    106    currentChannelType = EVENT_STREAM;
    107    columnsKey = "sseColumns";
    108  }
    109 
    110  return {
    111    ...state,
    112    columns:
    113      currentChannelType === state.currentChannelType
    114        ? { ...state.columns }
    115        : { ...state[columnsKey] },
    116    currentChannelId: resourceId,
    117    currentChannelType,
    118    currentRequestId: id,
    119    // Default filter text is empty string for a new connection
    120    messageFilterText: "",
    121  };
    122 }
    123 
    124 /**
    125 * If the request is already selected and isEventStream flag
    126 * is added later, we need to update currentChannelType & columns.
    127 */
    128 function updateCurrentChannel(state, action) {
    129  if (state.currentRequestId === action.id) {
    130    const currentChannelType = CHANNEL_TYPE.EVENT_STREAM;
    131    return {
    132      ...state,
    133      columns: { ...state.sseColumns },
    134      currentChannelType,
    135    };
    136  }
    137  return state;
    138 }
    139 
    140 /**
    141 * Appending new message into the map.
    142 */
    143 function addMessage(state, action) {
    144  const { httpChannelId } = action;
    145  const nextState = { ...state };
    146 
    147  const newMessage = {
    148    httpChannelId,
    149    ...action.data,
    150  };
    151 
    152  nextState.messages = mapSet(
    153    nextState.messages,
    154    newMessage.httpChannelId,
    155    newMessage
    156  );
    157 
    158  return nextState;
    159 }
    160 
    161 /**
    162 * Select specific message.
    163 */
    164 function selectMessage(state, action) {
    165  if (
    166    state.selectedMessage == action.message &&
    167    state.messageDetailsOpen == action.open
    168  ) {
    169    return state;
    170  }
    171  return {
    172    ...state,
    173    selectedMessage: action.message,
    174    messageDetailsOpen: action.open,
    175  };
    176 }
    177 
    178 /**
    179 * Shows/Hides the MessagePayload component.
    180 */
    181 function openMessageDetails(state, action) {
    182  if (state.messageDetailsOpen == action.open) {
    183    return state;
    184  }
    185  return {
    186    ...state,
    187    messageDetailsOpen: action.open,
    188  };
    189 }
    190 
    191 /**
    192 * Clear messages of the request from the state.
    193 */
    194 function clearMessages(state) {
    195  const nextState = { ...state };
    196  const defaultState = Messages();
    197  nextState.messages = new Map(state.messages);
    198  nextState.messages.delete(nextState.currentChannelId);
    199 
    200  // Reset fields to default state.
    201  nextState.selectedMessage = defaultState.selectedMessage;
    202  nextState.messageDetailsOpen = defaultState.messageDetailsOpen;
    203 
    204  return nextState;
    205 }
    206 
    207 /**
    208 * Toggle the message filter type of the connection.
    209 */
    210 function toggleMessageFilterType(state, action) {
    211  if (state.messageFilterType == action.filter) {
    212    return state;
    213  }
    214  return {
    215    ...state,
    216    messageFilterType: action.filter,
    217  };
    218 }
    219 
    220 /**
    221 * Toggle control frames for the WebSocket connection.
    222 */
    223 function toggleControlFrames(state) {
    224  return {
    225    ...state,
    226    showControlFrames: !state.showControlFrames,
    227  };
    228 }
    229 
    230 /**
    231 * Set the filter text of the current channelId.
    232 */
    233 function setMessageFilterText(state, action) {
    234  if (state.messageFilterText == action.text) {
    235    return state;
    236  }
    237  return {
    238    ...state,
    239    messageFilterText: action.text,
    240  };
    241 }
    242 
    243 /**
    244 * Toggle the user specified column view state.
    245 */
    246 function toggleColumn(state, action) {
    247  const { column } = action;
    248  let columnsKey = null;
    249  if (state.currentChannelType === CHANNEL_TYPE.WEB_SOCKET) {
    250    columnsKey = "wsColumns";
    251  } else {
    252    columnsKey = "sseColumns";
    253  }
    254  const newColumnsState = {
    255    ...state[columnsKey],
    256    [column]: !state[columnsKey][column],
    257  };
    258  return {
    259    ...state,
    260    columns: newColumnsState,
    261    [columnsKey]: newColumnsState,
    262  };
    263 }
    264 
    265 /**
    266 * Reset back to default columns view state.
    267 */
    268 function resetColumns(state) {
    269  let columnsKey = null;
    270  if (state.currentChannelType === CHANNEL_TYPE.WEB_SOCKET) {
    271    columnsKey = "wsColumns";
    272  } else {
    273    columnsKey = "sseColumns";
    274  }
    275  const newColumnsState = getMessageDefaultColumnsState(
    276    state.currentChannelType
    277  );
    278  return {
    279    ...state,
    280    [columnsKey]: newColumnsState,
    281    columns: newColumnsState,
    282  };
    283 }
    284 
    285 function closeConnection(state, action) {
    286  const { httpChannelId, code, reason } = action;
    287  const nextState = { ...state };
    288 
    289  nextState.closedConnections.set(httpChannelId, {
    290    code,
    291    reason,
    292  });
    293 
    294  return nextState;
    295 }
    296 
    297 /**
    298 * Append new item into existing map and return new map.
    299 */
    300 function mapSet(map, key, value) {
    301  const newMap = new Map(map);
    302  if (newMap.has(key)) {
    303    const messagesArray = [...newMap.get(key)];
    304    messagesArray.push(value);
    305    newMap.set(key, messagesArray);
    306    return newMap;
    307  }
    308  return newMap.set(key, [value]);
    309 }
    310 
    311 /**
    312 * This reducer is responsible for maintaining list of
    313 * messages within the Network panel.
    314 */
    315 function messages(state = Messages(), action) {
    316  switch (action.type) {
    317    case SELECT_REQUEST:
    318      return setCurrentChannel(state, action);
    319    case SET_EVENT_STREAM_FLAG:
    320      return updateCurrentChannel(state, action);
    321    case MSG_ADD:
    322      return addMessage(state, action);
    323    case MSG_SELECT:
    324      return selectMessage(state, action);
    325    case MSG_OPEN_DETAILS:
    326      return openMessageDetails(state, action);
    327    case MSG_CLEAR:
    328      return clearMessages(state);
    329    case MSG_TOGGLE_FILTER_TYPE:
    330      return toggleMessageFilterType(state, action);
    331    case MSG_TOGGLE_CONTROL:
    332      return toggleControlFrames(state, action);
    333    case MSG_SET_FILTER_TEXT:
    334      return setMessageFilterText(state, action);
    335    case MSG_TOGGLE_COLUMN:
    336      return toggleColumn(state, action);
    337    case MSG_RESET_COLUMNS:
    338      return resetColumns(state);
    339    case MSG_CLOSE_CONNECTION:
    340      return closeConnection(state, action);
    341    default:
    342      return state;
    343  }
    344 }
    345 
    346 module.exports = {
    347  Messages,
    348  messages,
    349  getMessageDefaultColumnsState,
    350 };