diff --git a/.eslintrc.js b/.eslintrc.js index b2f77e5..e3a49a6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,7 @@ module.exports = { "plugin:import/warnings", "plugin:prettier/recommended", "prettier/react", + "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", ], globals: { @@ -21,6 +22,8 @@ module.exports = { ignorePatterns: ["node_modules/"], parser: '@typescript-eslint/parser', parserOptions: { + project: "tsconfig.json", + tsconfigRootDir: ".", ecmaFeatures: { jsx: true, }, diff --git a/config/webpack.dev.config.js b/config/webpack.dev.config.js index 9607242..22488f7 100644 --- a/config/webpack.dev.config.js +++ b/config/webpack.dev.config.js @@ -15,7 +15,7 @@ var srcDir = path.join(appDir, 'src'); var options = { entry: { background: path.join(srcDir, 'background.js'), - inspector: path.join(srcDir, 'inspector.js'), + inspector: path.join(srcDir, 'inspector.tsx'), }, output: { path: path.join(appDir, 'build'), diff --git a/config/webpack.prod.config.js b/config/webpack.prod.config.js index 4693018..d36f4c7 100644 --- a/config/webpack.prod.config.js +++ b/config/webpack.prod.config.js @@ -15,7 +15,7 @@ var srcDir = path.join(appDir, "src"); var options = { entry: { background: path.join(srcDir, "background.js"), - inspector: path.join(srcDir, "inspector.js"), + inspector: path.join(srcDir, "inspector.tsx"), }, output: { path: path.join(appDir, "build"), diff --git a/package-lock.json b/package-lock.json index e054b57..eb788a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3148,6 +3148,15 @@ "to-fast-properties": "^2.0.0" } }, + "@types/chrome": { + "version": "0.0.92", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.92.tgz", + "integrity": "sha512-bTv1EljZ03bexRJwS5FwSZmrudtw+QNbzwUY2sxVtXWgtxk752G4I2owhZ+Mlzbf3VKvG+rBYSw/FnvzuZ4xOA==", + "dev": true, + "requires": { + "@types/filesystem": "*" + } + }, "@types/classnames": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz", @@ -3160,6 +3169,21 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/filesystem": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz", + "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==", + "dev": true, + "requires": { + "@types/filewriter": "*" + } + }, + "@types/filewriter": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.28.tgz", + "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=", + "dev": true + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", diff --git a/package.json b/package.json index b2eafc2..5d52538 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "node scripts/build.js", "start": "node scripts/webserver.js", + "eslint": "eslint --cache --ext .js,.jsx,.ts,.tsx src", "es": "eslint ./src " }, "devDependencies": { @@ -14,6 +15,7 @@ "@babel/preset-env": "^7.8.3", "@babel/preset-react": "^7.7.4", "@babel/preset-typescript": "^7.8.3", + "@types/chrome": "0.0.92", "@types/classnames": "^2.2.9", "@types/react": "^16.9.17", "@types/react-dom": "^16.9.4", diff --git a/src/inspector.js b/src/inspector.tsx similarity index 87% rename from src/inspector.js rename to src/inspector.tsx index 30f0759..f47c9f8 100644 --- a/src/inspector.js +++ b/src/inspector.tsx @@ -1,14 +1,15 @@ +/// import React from 'react'; import ReactDOM from 'react-dom'; import './reset.css'; import App from './viewer/App'; const tabId = parseInt(window.location.search.substr(1), 10); - -const handlers = {}; +// TODO create +const handlers: any = {}; function startDebugging() { - chrome.debugger.sendCommand({ tabId }, 'Network.enable', null, () => { + chrome.debugger.sendCommand({ tabId }, 'Network.enable', undefined, () => { if (chrome.runtime.lastError) { console.error(chrome.runtime.lastError.message); } else { diff --git a/src/viewer/App.js b/src/viewer/App.tsx similarity index 70% rename from src/viewer/App.js rename to src/viewer/App.tsx index 11c7425..37be7cd 100644 --- a/src/viewer/App.js +++ b/src/viewer/App.tsx @@ -1,21 +1,34 @@ -import React from 'react'; +import React, { ChangeEvent } from 'react'; import Panel from 'react-flex-panel'; import ControlPanel from './ControlPanel/ControlPanel'; import FrameList from './FrameTable/FrameTable'; -import FrameView from './PanelView/PanelView'; +import PanelView from './PanelView/PanelView'; import { stringToBuffer } from './Helpers/Helper'; import './App.scss'; +import { IFrame, IFrameType, Response } from './types'; -export default class App extends React.Component { +interface AppProps { + handlers: any; +} +interface AppState { + [key: string]: any; + frames: IFrame[]; + activeId: number | null; + isCapturing: boolean; + regName: string; + filter: string; + isFilterInverse: boolean; +} +export default class App extends React.Component { frameUniqueId = 0; - frameIssueTime = null; + frameIssueTime: number; - frameIssueWallTime = null; + frameIssueWallTime: number; cacheKey = ['isCapturing', 'regName', 'filter', 'isFilterInverse']; - constructor(props) { + constructor(props: AppProps) { super(props); this.props.handlers['Network.webSocketFrameReceived'] = this.webSocketFrameReceived.bind(this); this.props.handlers['Network.webSocketFrameSent'] = this.webSocketFrameSent.bind(this); @@ -30,10 +43,16 @@ export default class App extends React.Component { } componentDidMount() { - const cacheState = this.cacheKey.reduce((acc, key) => { - const value = localStorage.getItem(key); + const cacheState = this.cacheKey.reduce>((acc, key) => { + const value = window.localStorage.getItem(key); if (value !== null && value !== undefined) { acc[key] = value; + if (value === 'true') { + acc[key] = true; + } + if (value === 'false') { + acc[key] = false; + } } return acc; }, {}); @@ -41,7 +60,7 @@ export default class App extends React.Component { // TODO Boolean values turns to strings. } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: AppProps, prevState: AppState) { this.cacheKey.forEach((key) => { if (prevState[key] !== this.state[key]) { localStorage.setItem(key, this.state[key]); @@ -49,7 +68,7 @@ export default class App extends React.Component { }); } - getTime(timestamp) { + getTime(timestamp: number) { if (this.frameIssueTime == null) { this.frameIssueTime = timestamp; this.frameIssueWallTime = new Date().getTime(); @@ -85,7 +104,7 @@ export default class App extends React.Component { {active != null ? ( - + ) : ( Select a frame to view its contents )} @@ -94,7 +113,7 @@ export default class App extends React.Component { ); } - selectFrame = (id) => { + selectFrame = (id: number) => { this.setState({ activeId: id }); }; @@ -106,11 +125,11 @@ export default class App extends React.Component { this.setState({ isCapturing: !this.state.isCapturing }); }; - setRegName = (e) => { + setRegName = (e: ChangeEvent) => { this.setState({ regName: e.target.value }); }; - setFilter = (e) => { + setFilter = (e: ChangeEvent) => { this.setState({ filter: e.target.value }); }; @@ -118,9 +137,9 @@ export default class App extends React.Component { this.setState({ isFilterInverse: !this.state.isFilterInverse }); }; - addFrame(type, timestamp, response) { + addFrame(type: IFrameType, timestamp: number, response: Response) { if (response.opcode === 1 || response.opcode === 2) { - const frame = { + const frame: IFrame = { type, name: type, id: this.frameUniqueId, @@ -137,16 +156,14 @@ export default class App extends React.Component { } } - webSocketFrameReceived({ timestamp, response }) { - if (this.state.isCapturing === true || this.state.isCapturing === 'true') { - // see componentDidMount() + webSocketFrameReceived({ timestamp, response }: { timestamp: number; response: Response }) { + if (this.state.isCapturing) { this.addFrame('incoming', timestamp, response); } } - webSocketFrameSent({ timestamp, response }) { - if (this.state.isCapturing === true || this.state.isCapturing === 'true') { - // see componentDidMount() + webSocketFrameSent({ timestamp, response }: { timestamp: number; response: Response }) { + if (this.state.isCapturing) { this.addFrame('outgoing', timestamp, response); } } diff --git a/src/viewer/ControlPanel/ControlPanel.tsx b/src/viewer/ControlPanel/ControlPanel.tsx index fe7db61..17d5699 100644 --- a/src/viewer/ControlPanel/ControlPanel.tsx +++ b/src/viewer/ControlPanel/ControlPanel.tsx @@ -1,23 +1,24 @@ -import React, { ChangeEvent } from 'react'; +import React, { ChangeEvent, MouseEvent } from 'react'; import FontAwesome from 'react-fontawesome'; import cx from 'classnames'; import './ControlPanel.scss'; import { EFilter } from '../types'; - -type ControlPanelProps = { +// TODO not to return events +type ControlPanelMode = { isCapturing: boolean; - onClear: { (event: MouseEvent): void }; - onFilterModeToggle: { (event: MouseEvent): void }; - onCapturingToggle: { (event: MouseEvent): void }; - onRegName: { (event: ChangeEvent): void }; - onFilter: { (event: ChangeEvent): void }; + onClear: (event: MouseEvent) => void; + onFilterModeToggle: (event: MouseEvent) => void; + onCapturingToggle: (event: MouseEvent) => void; + onRegName: (event: ChangeEvent) => void; + onFilter: (event: ChangeEvent) => void; }; -interface EProps extends ControlPanelProps, EFilter {} -interface EState { +interface ControlPanelProps extends ControlPanelMode, EFilter {} +interface ControlPanelState { openInput?: null | 'filter' | 'name'; } -export default class ControlPanel extends React.Component { - constructor(props: EProps) { + +export default class ControlPanel extends React.Component { + constructor(props: ControlPanelProps) { super(props); this.state = { openInput: null, // 'filter' | 'name' diff --git a/src/viewer/FrameTable/FrameTable.tsx b/src/viewer/FrameTable/FrameTable.tsx index f902867..64afea5 100644 --- a/src/viewer/FrameTable/FrameTable.tsx +++ b/src/viewer/FrameTable/FrameTable.tsx @@ -1,23 +1,15 @@ /* eslint max-classes-per-file: 0 */ -import React from 'react'; +import React, { MouseEvent } from 'react'; import cx from 'classnames'; import FontAwesome from 'react-fontawesome'; import { checkViable, getName, TimeStamp } from '../Helpers/Helper'; import './FrameTable.scss'; import { EFilter, IFrame } from '../types'; -class Filter implements EFilter { - public regName: RegExp; - public filter: RegExp; - public isFilterInverse: boolean; -} -type FrameListProps = { frames: IFrame[]; activeId: number; onSelect: any }; -type FrameEntryProps = { - key: number; - frame: IFrame; - selected: boolean; - onSelect: void; - filterData: EFilter; +type FrameListProps = { + frames: IFrame[]; + activeId: number | null; + onSelect: (id: number | null) => void; }; export default class FrameList extends React.Component { @@ -43,11 +35,20 @@ export default class FrameList extends React.Component } } +type FrameEntryProps = { + key: number; + frame: IFrame; + selected: boolean; + onSelect: (id: number | null) => void; + filterData: EFilter; +}; + class FrameEntry extends React.PureComponent { - handlerSelect = (e) => { + handlerSelect = (e: MouseEvent) => { e.stopPropagation(); this.props.onSelect(this.props.frame.id); }; + render() { const { frame, selected, filterData } = this.props; if (checkViable(frame, filterData as EFilter)) return null; diff --git a/src/viewer/Helpers/Helper.ts b/src/viewer/Helpers/Helper.ts index ce6d920..35f0e05 100644 --- a/src/viewer/Helpers/Helper.ts +++ b/src/viewer/Helpers/Helper.ts @@ -32,7 +32,7 @@ export const getName = (frame: IFrame, filterData: EFilter): string => { }; export const checkViable = (frame: IFrame, filterData: EFilter): boolean => { - if (filterData.filter) { + if (filterData.filter && frame.text) { if (filterData.isFilterInverse) { return !!grep(frame.text, filterData.filter); } diff --git a/src/viewer/PanelView/HexViewer.js b/src/viewer/PanelView/HexViewer.tsx similarity index 91% rename from src/viewer/PanelView/HexViewer.js rename to src/viewer/PanelView/HexViewer.tsx index 7e25288..a0de4b7 100644 --- a/src/viewer/PanelView/HexViewer.js +++ b/src/viewer/PanelView/HexViewer.tsx @@ -2,7 +2,11 @@ import React from 'react'; import classNames from 'classnames'; import './HexViewer.scss'; -export default function HexViewer(props) { +interface HexViewerProps { + className: string; + data: Uint8Array; +} +export default function HexViewer(props: HexViewerProps) { const { data, className } = props; let numDigits = 4; /* eslint-disable-next-line no-bitwise */ diff --git a/src/viewer/PanelView/PanelView.tsx b/src/viewer/PanelView/PanelView.tsx index a92099f..7f52f13 100644 --- a/src/viewer/PanelView/PanelView.tsx +++ b/src/viewer/PanelView/PanelView.tsx @@ -1,22 +1,31 @@ -import { ObjectInspector } from 'react-inspector'; +import { ObjectInspector, ObjectInspectorProps } from 'react-inspector'; import React from 'react'; import cx from 'classnames'; import HexViewer from './HexViewer'; import './PanelView.scss'; +import { IFrame } from '../types'; -const TextViewer = ({ data }) =>
{data}
; +const TextViewer = ({ data }: { data: string | undefined }) => ( +
{data}
+); -const JsonViewer = ({ data }) => ( +const JsonViewer = (data: ObjectInspectorProps) => (
- +
); +type PanelName = 'json' | 'hex' | 'text'; +interface PanelViewState { + panel?: PanelName | PanelName[] | null; +} +interface PanelViewProps { + frame: IFrame; +} - -export default class FrameView extends React.Component { +export default class PanelView extends React.Component { state = { panel: null }; - static getDerivedStateFromProps(props, state) { + static getDerivedStateFromProps(props: PanelViewProps, state: PanelViewState) { const { frame } = props; const panels = []; if (frame.binary) { @@ -39,13 +48,13 @@ export default class FrameView extends React.Component { panels.push('text'); } - if (!panels.includes(state.panel)) { + if (!panels.includes(state.panel as string)) { return { panel: panels[0] }; } return null; } - makePanel(name, title) { + makePanel(name: PanelName, title: string) { return (
  • {panel === 'text' && } {panel === 'json' && } - {panel === 'hex' && } + {panel === 'hex' && } ); } diff --git a/src/viewer/types.ts b/src/viewer/types.ts index 6268a84..9b36ddb 100644 --- a/src/viewer/types.ts +++ b/src/viewer/types.ts @@ -1,4 +1,4 @@ -type IFrameType = 'incoming' | 'outgoing'; +export type IFrameType = 'incoming' | 'outgoing'; export type IFrame = { type: IFrameType; name: string; @@ -6,11 +6,16 @@ export type IFrame = { time: Date; length: number; text?: string; - binary?: Uint8Array; //TODO string to buffer type + binary?: Uint8Array; + json?: object; }; export interface EFilter { regName: string; - filter: ; + filter: string; isFilterInverse: boolean; } +export interface Response { + opcode: number; + payloadData: string; +} diff --git a/tsconfig.json b/tsconfig.json index 1195e2c..6a5efee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "lib": ["es2018", "es2020.string"], - "strictNullChecks": false, // TODO change before release + "lib": ["es2018", "es2020.string", "DOM"], + "strictNullChecks": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "module": "ESNext",