Source code

Revision control

Copy as Markdown

Other Tools

/* 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 {
SELECT_REQUEST,
MSG_ADD,
MSG_SELECT,
MSG_OPEN_DETAILS,
MSG_CLEAR,
MSG_TOGGLE_FILTER_TYPE,
MSG_TOGGLE_CONTROL,
MSG_SET_FILTER_TEXT,
MSG_TOGGLE_COLUMN,
MSG_RESET_COLUMNS,
MSG_CLOSE_CONNECTION,
CHANNEL_TYPE,
SET_EVENT_STREAM_FLAG,
} = require("resource://devtools/client/netmonitor/src/constants.js");
/**
* The default column states for the MessageListItem component.
*/
const defaultColumnsState = {
data: true,
size: false,
time: true,
};
const defaultWSColumnsState = {
...defaultColumnsState,
opCode: false,
maskBit: false,
finBit: false,
};
const defaultSSEColumnsState = {
...defaultColumnsState,
eventName: false,
lastEventId: false,
retry: false,
};
/**
* Returns a new object of default cols.
*/
function getMessageDefaultColumnsState(channelType) {
let columnsState = defaultColumnsState;
const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
if (channelType === WEB_SOCKET) {
columnsState = defaultWSColumnsState;
} else if (channelType === EVENT_STREAM) {
columnsState = defaultSSEColumnsState;
}
return Object.assign({}, columnsState);
}
/**
* This structure stores list of all WebSocket and EventSource messages received
* from the backend.
*/
function Messages(initialState = {}) {
const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
return {
// Map with all requests (key = resourceId, value = array of message objects)
messages: new Map(),
messageFilterText: "",
// Default filter type is "all",
messageFilterType: "all",
showControlFrames: false,
selectedMessage: null,
messageDetailsOpen: false,
currentChannelId: null,
currentChannelType: null,
currentRequestId: null,
closedConnections: new Map(),
columns: null,
sseColumns: getMessageDefaultColumnsState(EVENT_STREAM),
wsColumns: getMessageDefaultColumnsState(WEB_SOCKET),
...initialState,
};
}
/**
* When a network request is selected,
* set the current resourceId affiliated with the connection.
*/
function setCurrentChannel(state, action) {
if (!action.request) {
return state;
}
const { id, cause, resourceId, isEventStream } = action.request;
const { EVENT_STREAM, WEB_SOCKET } = CHANNEL_TYPE;
let currentChannelType = null;
let columnsKey = "columns";
if (cause.type === "websocket") {
currentChannelType = WEB_SOCKET;
columnsKey = "wsColumns";
} else if (isEventStream) {
currentChannelType = EVENT_STREAM;
columnsKey = "sseColumns";
}
return {
...state,
columns:
currentChannelType === state.currentChannelType
? { ...state.columns }
: { ...state[columnsKey] },
currentChannelId: resourceId,
currentChannelType,
currentRequestId: id,
// Default filter text is empty string for a new connection
messageFilterText: "",
};
}
/**
* If the request is already selected and isEventStream flag
* is added later, we need to update currentChannelType & columns.
*/
function updateCurrentChannel(state, action) {
if (state.currentRequestId === action.id) {
const currentChannelType = CHANNEL_TYPE.EVENT_STREAM;
return {
...state,
columns: { ...state.sseColumns },
currentChannelType,
};
}
return state;
}
/**
* Appending new message into the map.
*/
function addMessage(state, action) {
const { httpChannelId } = action;
const nextState = { ...state };
const newMessage = {
httpChannelId,
...action.data,
};
nextState.messages = mapSet(
nextState.messages,
newMessage.httpChannelId,
newMessage
);
return nextState;
}
/**
* Select specific message.
*/
function selectMessage(state, action) {
return {
...state,
selectedMessage: action.message,
messageDetailsOpen: action.open,
};
}
/**
* Shows/Hides the MessagePayload component.
*/
function openMessageDetails(state, action) {
return {
...state,
messageDetailsOpen: action.open,
};
}
/**
* Clear messages of the request from the state.
*/
function clearMessages(state) {
const nextState = { ...state };
const defaultState = Messages();
nextState.messages = new Map(state.messages);
nextState.messages.delete(nextState.currentChannelId);
// Reset fields to default state.
nextState.selectedMessage = defaultState.selectedMessage;
nextState.messageDetailsOpen = defaultState.messageDetailsOpen;
return nextState;
}
/**
* Toggle the message filter type of the connection.
*/
function toggleMessageFilterType(state, action) {
return {
...state,
messageFilterType: action.filter,
};
}
/**
* Toggle control frames for the WebSocket connection.
*/
function toggleControlFrames(state) {
return {
...state,
showControlFrames: !state.showControlFrames,
};
}
/**
* Set the filter text of the current channelId.
*/
function setMessageFilterText(state, action) {
return {
...state,
messageFilterText: action.text,
};
}
/**
* Toggle the user specified column view state.
*/
function toggleColumn(state, action) {
const { column } = action;
let columnsKey = null;
if (state.currentChannelType === CHANNEL_TYPE.WEB_SOCKET) {
columnsKey = "wsColumns";
} else {
columnsKey = "sseColumns";
}
const newColumnsState = {
...state[columnsKey],
[column]: !state[columnsKey][column],
};
return {
...state,
columns: newColumnsState,
[columnsKey]: newColumnsState,
};
}
/**
* Reset back to default columns view state.
*/
function resetColumns(state) {
let columnsKey = null;
if (state.currentChannelType === CHANNEL_TYPE.WEB_SOCKET) {
columnsKey = "wsColumns";
} else {
columnsKey = "sseColumns";
}
const newColumnsState = getMessageDefaultColumnsState(
state.currentChannelType
);
return {
...state,
[columnsKey]: newColumnsState,
columns: newColumnsState,
};
}
function closeConnection(state, action) {
const { httpChannelId, code, reason } = action;
const nextState = { ...state };
nextState.closedConnections.set(httpChannelId, {
code,
reason,
});
return nextState;
}
/**
* Append new item into existing map and return new map.
*/
function mapSet(map, key, value) {
const newMap = new Map(map);
if (newMap.has(key)) {
const messagesArray = [...newMap.get(key)];
messagesArray.push(value);
newMap.set(key, messagesArray);
return newMap;
}
return newMap.set(key, [value]);
}
/**
* This reducer is responsible for maintaining list of
* messages within the Network panel.
*/
function messages(state = Messages(), action) {
switch (action.type) {
case SELECT_REQUEST:
return setCurrentChannel(state, action);
case SET_EVENT_STREAM_FLAG:
return updateCurrentChannel(state, action);
case MSG_ADD:
return addMessage(state, action);
case MSG_SELECT:
return selectMessage(state, action);
case MSG_OPEN_DETAILS:
return openMessageDetails(state, action);
case MSG_CLEAR:
return clearMessages(state);
case MSG_TOGGLE_FILTER_TYPE:
return toggleMessageFilterType(state, action);
case MSG_TOGGLE_CONTROL:
return toggleControlFrames(state, action);
case MSG_SET_FILTER_TEXT:
return setMessageFilterText(state, action);
case MSG_TOGGLE_COLUMN:
return toggleColumn(state, action);
case MSG_RESET_COLUMNS:
return resetColumns(state);
case MSG_CLOSE_CONNECTION:
return closeConnection(state, action);
default:
return state;
}
}
module.exports = {
Messages,
messages,
getMessageDefaultColumnsState,
};