diff --git a/src/plugins/network/RequestDetails.js b/src/plugins/network/RequestDetails.tsx
similarity index 92%
rename from src/plugins/network/RequestDetails.js
rename to src/plugins/network/RequestDetails.tsx
index 34de689b4..ea373a0da 100644
--- a/src/plugins/network/RequestDetails.js
+++ b/src/plugins/network/RequestDetails.tsx
@@ -5,13 +5,7 @@
* @format
*/
-import type {
- Request,
- Response,
- Header,
- Insights,
- RetryInsights,
-} from './types.tsx';
+import {Request, Response, Header, Insights, RetryInsights} from './types';
import {
Component,
@@ -24,11 +18,11 @@ import {
styled,
colors,
} from 'flipper';
-import {decodeBody, getHeaderValue} from './utils.tsx';
-import {formatBytes} from './index.js';
+import {decodeBody, getHeaderValue} from './utils';
+import {formatBytes} from './index';
+import React from 'react';
import querystring from 'querystring';
-// $FlowFixMe
import xmlBeautifier from 'xml-beautifier';
const WrappingText = styled(Text)({
@@ -55,17 +49,17 @@ const KeyValueColumns = {
};
type RequestDetailsProps = {
- request: Request,
- response: ?Response,
+ request: Request;
+ response: Response | null | undefined;
};
type RequestDetailsState = {
- bodyFormat: string,
+ bodyFormat: string;
};
export default class RequestDetails extends Component<
RequestDetailsProps,
- RequestDetailsState,
+ RequestDetailsState
> {
static Container = styled(FlexColumn)({
height: '100%',
@@ -219,21 +213,21 @@ class QueryInspector extends Component<{queryParams: URLSearchParams}> {
render() {
const {queryParams} = this.props;
- const rows = [];
- for (const kv of queryParams.entries()) {
+ const rows: any = [];
+ queryParams.forEach((value: string, key: string) => {
rows.push({
columns: {
key: {
- value: {kv[0]},
+ value: {key},
},
value: {
- value: {kv[1]},
+ value: {value},
},
},
- copyText: kv[1],
- key: kv[0],
+ copyText: value,
+ key: key,
});
- }
+ });
return rows.length > 0 ? (
{
}
type HeaderInspectorProps = {
- headers: Array,
+ headers: Array;
};
type HeaderInspectorState = {
- computedHeaders: Object,
+ computedHeaders: Object;
};
class HeaderInspector extends Component<
HeaderInspectorProps,
- HeaderInspectorState,
+ HeaderInspectorState
> {
render() {
- const computedHeaders = this.props.headers.reduce((sum, header) => {
- return {...sum, [header.key]: header.value};
- }, {});
+ const computedHeaders: Map = this.props.headers.reduce(
+ (sum, header) => {
+ return sum.set(header.key, header.value);
+ },
+ new Map(),
+ );
- const rows = [];
- for (const key in computedHeaders) {
+ const rows: any = [];
+ computedHeaders.forEach((value: string, key: string) => {
rows.push({
columns: {
key: {
value: {key},
},
value: {
- value: {computedHeaders[key]},
+ value: {value},
},
},
- copyText: computedHeaders[key],
+ copyText: value,
key,
});
- }
+ });
return rows.length > 0 ? (
any,
- formatResponse?: (request: Request, response: Response) => any,
+ formatRequest?: (request: Request) => any;
+ formatResponse?: (request: Request, response: Response) => any;
};
class RequestBodyInspector extends Component<{
- request: Request,
- formattedText: boolean,
+ request: Request;
+ formattedText: boolean;
}> {
render() {
const {request, formattedText} = this.props;
@@ -337,9 +334,9 @@ class RequestBodyInspector extends Component<{
}
class ResponseBodyInspector extends Component<{
- response: Response,
- request: Request,
- formattedText: boolean,
+ response: Response;
+ request: Request;
+ formattedText: boolean;
}> {
render() {
const {request, response, formattedText} = this.props;
@@ -374,12 +371,12 @@ const MediaContainer = styled(FlexColumn)({
});
type ImageWithSizeProps = {
- src: string,
+ src: string;
};
type ImageWithSizeState = {
- width: number,
- height: number,
+ width: number;
+ height: number;
};
class ImageWithSize extends Component {
@@ -394,7 +391,7 @@ class ImageWithSize extends Component {
fontSize: 14,
});
- constructor(props, context) {
+ constructor(props: ImageWithSizeProps, context: any) {
super(props, context);
this.state = {
width: 0,
@@ -595,7 +592,7 @@ class LogEventFormatter {
formatRequest = (request: Request) => {
if (request.url.indexOf('logging_client_event') > 0) {
const data = querystring.parse(decodeBody(request));
- if (data.message) {
+ if (typeof data.message === 'string') {
data.message = JSON.parse(data.message);
}
return ;
@@ -607,7 +604,7 @@ class GraphQLBatchFormatter {
formatRequest = (request: Request) => {
if (request.url.indexOf('graphqlbatch') > 0) {
const data = querystring.parse(decodeBody(request));
- if (data.queries) {
+ if (typeof data.queries === 'string') {
data.queries = JSON.parse(data.queries);
}
return ;
@@ -643,10 +640,10 @@ class GraphQLFormatter {
formatRequest = (request: Request) => {
if (request.url.indexOf('graphql') > 0) {
const data = querystring.parse(decodeBody(request));
- if (data.variables) {
+ if (typeof data.variables === 'string') {
data.variables = JSON.parse(data.variables);
}
- if (data.query_params) {
+ if (typeof data.query_params === 'string') {
data.query_params = JSON.parse(data.query_params);
}
return ;
@@ -745,7 +742,11 @@ class InsightsInspector extends Component<{insights: Insights}> {
} ${timesWord} out of ${retry.limit})`;
}
- buildRow(name: string, value: ?T, formatter: T => string): any {
+ buildRow(
+ name: string,
+ value: T | null | undefined,
+ formatter: (value: T) => string,
+ ): any {
return value
? {
columns: {
diff --git a/src/plugins/network/index.js b/src/plugins/network/index.tsx
similarity index 63%
rename from src/plugins/network/index.js
rename to src/plugins/network/index.tsx
index 71541821e..9a5acd24d 100644
--- a/src/plugins/network/index.js
+++ b/src/plugins/network/index.tsx
@@ -5,13 +5,10 @@
* @format
*/
-import type {
- TableHighlightedRows,
- TableRows,
- TableBodyRow,
- MetricType,
-} from 'flipper';
+import {TableHighlightedRows, TableRows, TableBodyRow} from 'flipper';
import {padStart} from 'lodash';
+import React from 'react';
+import {MenuItemConstructorOptions} from 'electron';
import {
ContextMenu,
@@ -26,25 +23,21 @@ import {
SearchableTable,
FlipperPlugin,
} from 'flipper';
-import type {Request, RequestId, Response} from './types.tsx';
-import {
- convertRequestToCurlCommand,
- getHeaderValue,
- decodeBody,
-} from './utils.tsx';
-import RequestDetails from './RequestDetails.js';
+import {Request, RequestId, Response} from './types';
+import {convertRequestToCurlCommand, getHeaderValue, decodeBody} from './utils';
+import RequestDetails from './RequestDetails';
import {clipboard} from 'electron';
import {URL} from 'url';
-import type {Notification} from '../../plugin.tsx';
+import {DefaultKeyboardAction} from 'src/MenuBar';
-type PersistedState = {|
- requests: {[id: RequestId]: Request},
- responses: {[id: RequestId]: Response},
-|};
+type PersistedState = {
+ requests: Map;
+ responses: Map;
+};
-type State = {|
- selectedIds: Array,
-|};
+type State = {
+ selectedIds: Array;
+};
const COLUMN_SIZE = {
requestTimestamp: 100,
@@ -67,27 +60,13 @@ const COLUMN_ORDER = [
];
const COLUMNS = {
- requestTimestamp: {
- value: 'Request Time',
- },
- responseTimestamp: {
- value: 'Response Time',
- },
- domain: {
- value: 'Domain',
- },
- method: {
- value: 'Method',
- },
- status: {
- value: 'Status',
- },
- size: {
- value: 'Size',
- },
- duration: {
- value: 'Duration',
- },
+ requestTimestamp: {value: 'Request Time'},
+ responseTimestamp: {value: 'Response Time'},
+ domain: {value: 'Domain'},
+ method: {value: 'Method'},
+ status: {value: 'Status'},
+ size: {value: 'Size'},
+ duration: {value: 'Duration'},
};
export function formatBytes(count: number): string {
@@ -108,66 +87,75 @@ const TextEllipsis = styled(Text)({
paddingTop: 4,
});
-export default class extends FlipperPlugin {
- static keyboardActions = ['clear'];
+export default class extends FlipperPlugin {
+ static keyboardActions: Array = ['clear'];
static subscribed = [];
static defaultPersistedState = {
- requests: {},
- responses: {},
+ requests: new Map(),
+ responses: new Map(),
};
- static metricsReducer = (
- persistedState: PersistedState,
- ): Promise => {
- const failures = Object.keys(persistedState.responses).reduce(function(
+ static metricsReducer(persistedState: PersistedState) {
+ const failures = Object.values(persistedState.responses).reduce(function(
previous,
- key,
+ values,
) {
- return previous + (persistedState.responses[key].status >= 400);
+ return previous + (values.status >= 400 ? 1 : 0);
},
0);
return Promise.resolve({NUMBER_NETWORK_FAILURES: failures});
- };
+ }
- static persistedStateReducer = (
+ static persistedStateReducer(
persistedState: PersistedState,
method: string,
data: Request | Response,
- ): PersistedState => {
- const dataType: 'requests' | 'responses' = data.url
- ? 'requests'
- : 'responses';
- return {
- ...persistedState,
- [dataType]: {
- ...persistedState[dataType],
- [data.id]: data,
- },
- };
- };
+ ) {
+ switch (method) {
+ case 'newRequest':
+ return Object.assign({}, persistedState, {
+ requests: new Map(persistedState.requests).set(
+ data.id,
+ data as Request,
+ ),
+ });
+ case 'newResponse':
+ return Object.assign({}, persistedState, {
+ responses: new Map(persistedState.responses).set(
+ data.id,
+ data as Response,
+ ),
+ });
+ default:
+ return persistedState;
+ }
+ }
- static getActiveNotifications = (
- persistedState: PersistedState,
- ): Array => {
- const responses = persistedState ? persistedState.responses || [] : [];
- const r: Array = Object.values(responses);
+ static getActiveNotifications(persistedState: PersistedState) {
+ const responses = persistedState
+ ? persistedState.responses || new Map()
+ : new Map();
+ const r: Array = Array.from(responses.values());
return (
r
// Show error messages for all status codes indicating a client or server error
.filter((response: Response) => response.status >= 400)
- .map((response: Response) => ({
- id: response.id,
- title: `HTTP ${response.status}: Network request failed`,
- message: `Request to "${persistedState.requests[response.id]?.url ||
- '(URL missing)'}" failed. ${response.reason}`,
- severity: 'error',
- timestamp: response.timestamp,
- category: `HTTP${response.status}`,
- action: response.id,
- }))
+ .map((response: Response) => {
+ const request = persistedState.requests.get(response.id);
+ const url: string = (request && request.url) || '(URL missing)';
+ return {
+ id: response.id,
+ title: `HTTP ${response.status}: Network request failed`,
+ message: `Request to ${url} failed. ${response.reason}`,
+ severity: 'error' as 'error',
+ timestamp: response.timestamp,
+ category: `HTTP${response.status}`,
+ action: response.id,
+ };
+ })
);
- };
+ }
onKeyboardAction = (action: string) => {
if (action === 'clear') {
@@ -190,14 +178,17 @@ export default class extends FlipperPlugin {
return;
}
- const request = requests[selectedIds[0]];
+ const request = requests.get(selectedIds[0]);
+ if (!request) {
+ return;
+ }
const command = convertRequestToCurlCommand(request);
clipboard.writeText(command);
};
clearLogs = () => {
this.setState({selectedIds: []});
- this.props.setPersistedState({responses: {}, requests: {}});
+ this.props.setPersistedState({responses: new Map(), requests: new Map()});
};
renderSidebar = () => {
@@ -205,13 +196,20 @@ export default class extends FlipperPlugin {
const {selectedIds} = this.state;
const selectedId = selectedIds.length === 1 ? selectedIds[0] : null;
- return selectedId != null ? (
+ if (!selectedId) {
+ return null;
+ }
+ const requestWithId = requests.get(selectedId);
+ if (!requestWithId) {
+ return null;
+ }
+ return (
- ) : null;
+ );
};
render() {
@@ -220,8 +218,8 @@ export default class extends FlipperPlugin {
return (
{
}
type NetworkTableProps = {
- requests: {[id: RequestId]: Request},
- responses: {[id: RequestId]: Response},
- clear: () => void,
- copyRequestCurlCommand: () => void,
- onRowHighlighted: (keys: TableHighlightedRows) => void,
- highlightedRows: ?Set,
+ requests: Map;
+ responses: Map;
+ clear: () => void;
+ copyRequestCurlCommand: () => void;
+ onRowHighlighted: (keys: TableHighlightedRows) => void;
+ highlightedRows: Set | null | undefined;
};
-type NetworkTableState = {|
- sortedRows: TableRows,
-|};
+type NetworkTableState = {
+ sortedRows: TableRows;
+};
function formatTimestamp(timestamp: number): string {
const date = new Date(timestamp);
@@ -261,9 +259,12 @@ function formatTimestamp(timestamp: number): string {
)}`;
}
-function buildRow(request: Request, response: ?Response): ?TableBodyRow {
+function buildRow(
+ request: Request,
+ response: Response | null | undefined,
+): TableBodyRow | null | undefined {
if (request == null) {
- return;
+ return null;
}
const url = new URL(request.url);
const domain = url.host + url.pathname;
@@ -273,7 +274,10 @@ function buildRow(request: Request, response: ?Response): ?TableBodyRow {
## Request
HTTP ${request.method} ${request.url}
${request.headers
- .map(({key, value}) => `${key}: ${String(value)}`)
+ .map(
+ ({key, value}: {key: string; value: string}): string =>
+ `${key}: ${String(value)}`,
+ )
.join('\n')}`;
if (request.data) {
@@ -286,7 +290,10 @@ ${request.headers
## Response
HTTP ${response.status} ${response.reason}
${response.headers
- .map(({key, value}) => `${key}: ${String(value)}`)
+ .map(
+ ({key, value}: {key: string; value: string}): string =>
+ `${key}: ${String(value)}`,
+ )
.join('\n')}`;
}
@@ -341,50 +348,49 @@ ${response.headers
function calculateState(
props: {
- requests: {[id: RequestId]: Request},
- responses: {[id: RequestId]: Response},
+ requests: Map;
+ responses: Map;
},
nextProps: NetworkTableProps,
rows: TableRows = [],
): NetworkTableState {
rows = [...rows];
- if (Object.keys(nextProps.requests).length === 0) {
+ // if (nextProps.requests.size === undefined || nextProps.requests.size === 0) {
+ if (nextProps.requests.size === 0) {
// cleared
rows = [];
} else if (props.requests !== nextProps.requests) {
// new request
- for (const requestId in nextProps.requests) {
- if (props.requests[requestId] == null) {
- const newRow = buildRow(
- nextProps.requests[requestId],
- nextProps.responses[requestId],
- );
+ nextProps.requests.forEach((request: Request, requestId: RequestId) => {
+ if (props.requests.get(requestId) == null) {
+ const newRow = buildRow(request, nextProps.responses.get(requestId));
if (newRow) {
rows.push(newRow);
}
}
- }
+ });
} else if (props.responses !== nextProps.responses) {
// new response
- for (const responseId in nextProps.responses) {
- if (props.responses[responseId] == null) {
- const newRow = buildRow(
- nextProps.requests[responseId],
- nextProps.responses[responseId],
- );
- const index = rows.findIndex(
- r => r.key === nextProps.requests[responseId]?.id,
- );
+ const resId = Array.from(nextProps.responses.keys()).find(
+ (responseId: RequestId) => !props.responses.get(responseId),
+ );
+ if (resId) {
+ const request = nextProps.requests.get(resId);
+ // sanity check; to pass null check
+ if (request) {
+ const newRow = buildRow(request, nextProps.responses.get(resId));
+ const index = rows.findIndex((r: TableBodyRow) => r.key === request.id);
if (index > -1 && newRow) {
rows[index] = newRow;
}
- break;
}
}
}
- rows.sort((a, b) => Number(a.sortKey) - Number(b.sortKey));
+ rows.sort(
+ (a: TableBodyRow, b: TableBodyRow) => Number(a.sortKey) - Number(b.sortKey),
+ );
return {
sortedRows: rows,
@@ -400,8 +406,8 @@ class NetworkTable extends PureComponent {
super(props);
this.state = calculateState(
{
- requests: {},
- responses: {},
+ requests: new Map(),
+ responses: new Map(),
},
props,
);
@@ -411,13 +417,20 @@ class NetworkTable extends PureComponent {
this.setState(calculateState(this.props, nextProps, this.state.sortedRows));
}
- contextMenuItems() {
+ contextMenuItems(): Array {
+ type ContextMenuType =
+ | 'normal'
+ | 'separator'
+ | 'submenu'
+ | 'checkbox'
+ | 'radio';
+ const separator: ContextMenuType = 'separator';
const {clear, copyRequestCurlCommand, highlightedRows} = this.props;
const highlightedMenuItems =
highlightedRows && highlightedRows.size === 1
? [
{
- type: 'separator',
+ type: separator,
},
{
label: 'Copy as cURL',
@@ -428,7 +441,7 @@ class NetworkTable extends PureComponent {
return highlightedMenuItems.concat([
{
- type: 'separator',
+ type: separator,
},
{
label: 'Clear all',
@@ -439,7 +452,9 @@ class NetworkTable extends PureComponent {
render() {
return (
-
+
{
render() {
const {children} = this.props;
@@ -488,8 +503,8 @@ class StatusColumn extends PureComponent<{
}
class DurationColumn extends PureComponent<{
- request: Request,
- response: ?Response,
+ request: Request;
+ response: Response | null | undefined;
}> {
static Text = styled(Text)({
flex: 1,
@@ -511,7 +526,7 @@ class DurationColumn extends PureComponent<{
}
class SizeColumn extends PureComponent<{
- response: ?Response,
+ response: Response | null | undefined;
}> {
static Text = styled(Text)({
flex: 1,
@@ -529,7 +544,11 @@ class SizeColumn extends PureComponent<{
}
}
- getResponseLength(response) {
+ getResponseLength(response: Response | null | undefined) {
+ if (!response) {
+ return 0;
+ }
+
let length = 0;
const lengthString = response.headers
? getHeaderValue(response.headers, 'content-length')
diff --git a/src/plugins/network/package.json b/src/plugins/network/package.json
index 7b4492d49..c6070ce91 100644
--- a/src/plugins/network/package.json
+++ b/src/plugins/network/package.json
@@ -1,7 +1,7 @@
{
"name": "Network",
"version": "1.0.0",
- "main": "index.js",
+ "main": "index.tsx",
"license": "MIT",
"dependencies": {
"pako": "^1.0.6",
diff --git a/types/XmlBeautifier.d.tsx b/types/XmlBeautifier.d.tsx
new file mode 100644
index 000000000..38ba1b3a1
--- /dev/null
+++ b/types/XmlBeautifier.d.tsx
@@ -0,0 +1,10 @@
+/**
+ * Copyright 2018-present Facebook.
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ * @format
+ */
+
+declare module 'xml-beautifier' {
+ export default function(xml: string, indent?: string): string;
+}