diff --git a/config/webpack.dev.config.js b/config/webpack.dev.config.js index 22488f7..8769f9b 100644 --- a/config/webpack.dev.config.js +++ b/config/webpack.dev.config.js @@ -14,7 +14,7 @@ var srcDir = path.join(appDir, 'src'); var options = { entry: { - background: path.join(srcDir, 'background.js'), + background: path.join(srcDir, 'background.ts'), inspector: path.join(srcDir, 'inspector.tsx'), }, output: { diff --git a/config/webpack.prod.config.js b/config/webpack.prod.config.js index d36f4c7..044bfaf 100644 --- a/config/webpack.prod.config.js +++ b/config/webpack.prod.config.js @@ -14,7 +14,7 @@ var srcDir = path.join(appDir, "src"); var options = { entry: { - background: path.join(srcDir, "background.js"), + background: path.join(srcDir, "background.ts"), inspector: path.join(srcDir, "inspector.tsx"), }, output: { diff --git a/src/FrameData.ts b/src/FrameData.ts new file mode 100644 index 0000000..178a832 --- /dev/null +++ b/src/FrameData.ts @@ -0,0 +1,133 @@ +import { FrameEntryType, WebSocketFrame } from './viewer/types'; +import { checkViable, newGetName, stringToBuffer } from './viewer/Helpers/Helper'; +type INC = 'incoming'; +type OUT = 'outgoing'; +export type frameSendingType = INC | OUT; +type JSON_TYPE = 'json'; +type BINARY_TYPE = 'binary'; +type TEXT_TYPE = 'text'; +const JSON_TYPE: JSON_TYPE = 'json', + BINARY_TYPE: BINARY_TYPE = 'binary', + TEXT_TYPE: TEXT_TYPE = 'text'; +type contentType = JSON_TYPE | BINARY_TYPE | TEXT_TYPE; + +interface FrameAddingProps { + id: string; + sendingType: frameSendingType; + contentType: contentType; + time: Date; + length: number; + text: string; + content?: string | Uint8Array | object; +} +// TODO to simplify by using _public_ in constructor +export class FrameEntry { + id: string; + sendingType: frameSendingType; + contentType: contentType; + time: Date; + length: number; + text: string; + content?: string | Uint8Array | object; + constructor(args: FrameAddingProps) { + this.id = args.id; + this.sendingType = args.sendingType; + this.contentType = args.contentType; + this.time = args.time; + this.length = args.length; + this.text = args.text; + this.content = args.content; + // Here can be added keys of JSON object or other data + } +} +// Uses parameters from Network.WebSocket method to add FrameEntry to array +export const getFrame = ( + sendingType: frameSendingType, + requestId: string, + timestamp: number, + response: WebSocketFrame +) => { + // Checks ContentType and assigns Content + const isDataTextOrObject = response.opcode === 1; + const isDataBinary = response.opcode === 2; + if (isDataTextOrObject || isDataBinary) { + let assignedContentType: contentType, assignedContent: string | Uint8Array | object; // TODO Default value? + + if (isDataBinary) { + assignedContent = stringToBuffer(response.payloadData); + assignedContentType = BINARY_TYPE; + } + if (isDataTextOrObject) { + try { + assignedContent = JSON.parse(response.payloadData); + assignedContentType = JSON_TYPE; + } catch { + assignedContent = response.payloadData; + assignedContentType = TEXT_TYPE; + } + } + // Creates a new Frame Entry + const FrameAddingProps: FrameAddingProps = { + id: Date.now().toString(), + sendingType: sendingType, + contentType: assignedContentType, + time: timestamp, // FIXME Dependency from App + length: response.payloadData.length, + text: response.payloadData, + content: assignedContent, + }; + const frameEntry = new FrameEntry(FrameAddingProps); + return frameEntry; // TODO change to FrameAddingProps? + } +}; +// FrameDataArray is an array that has all registered and processed(altered) frames +export default class FrameDataArray { + constructor() { + this.frames = []; + } + + frames: FrameEntry[] = []; + // TODO Method shouldn't rely on extensional parameters + addFrameEntry( + sendingType: frameSendingType, + requestId: string, + timestamp: number, + response: WebSocketFrame + ): void { + const newFrame = getFrame(sendingType, requestId, timestamp, response); + this.frames.push(newFrame); + } + + deleteAllFrameEntries(): void { + this.frames = []; + } + // FrameViewArray represents all frames that had been filtered and grepped + getFrameViewArray(): FrameEntry[] { + // TODO make connection with control panel + const MAX_STRING_LENGTH = 275; + const regName = '', + filter = '', + isFilterInverse = false; + const frameViewArray = []; + this.frames.map((frameEntry: FrameEntry) => { + // TODO refactor checkViable() newGetName to comply with new types + if (!checkViable(frameEntry as FrameEntryType, { regName, filter, isFilterInverse })) { + const greppedText = newGetName(frameEntry, { regName, filter, isFilterInverse }); + const processedFrameEntry = Object.assign({}, frameEntry); //copy = JSON.parse(JSON.stringify(original)); + processedFrameEntry.text = greppedText.slice(0, MAX_STRING_LENGTH); + processedFrameEntry.content = undefined; + frameViewArray.push(processedFrameEntry); + } + }); + return frameViewArray; + } +} + + +/* +FrameDataObject = { + frames: {id as string: FrameEntry object}; + ids: array of id as string +} + + */ diff --git a/src/background.js b/src/background.ts similarity index 63% rename from src/background.js rename to src/background.ts index 8b98b40..373b0cc 100644 --- a/src/background.js +++ b/src/background.ts @@ -1,7 +1,14 @@ import './img/icon-128.png'; +import Window from 'chrome'; -const inspectors = []; - +type inspectorMask = { + id: number; + popup: Window; // https://developer.chrome.com/extensions/windows#type-Window + active: boolean; +}; +// a list of created inspector windows +const inspectors: inspectorMask[] = []; +// Deletes removed inspectors mask from array chrome.windows.onRemoved.addListener((id) => { const pos = inspectors.findIndex(({ popup }) => popup.id === id); if (pos >= 0) { @@ -11,6 +18,7 @@ chrome.windows.onRemoved.addListener((id) => { inspectors.splice(pos, 1); } }); +// Listens to possible Detachment of existing inspector windows chrome.debugger.onDetach.addListener(({ tabId }) => { const inspector = inspectors.find(({ id }) => id === tabId); if (inspector) { @@ -18,18 +26,26 @@ chrome.debugger.onDetach.addListener(({ tabId }) => { } }); +// Listens to clicks on extension icon chrome.browserAction.onClicked.addListener((tab) => { + // Checks if there is an existing inspector window const inspector = inspectors.find(({ id }) => id === tab.id); + // if true it gets focus if (inspector && inspector.active) { chrome.windows.update(inspector.popup.id, { focused: true }); } else { + // else attaches debugger to the given target chrome.debugger.attach({ tabId: tab.id }, '1.0', () => { + // All module of callback executes after attachment attempt + // signalize if attachment attempt failed if (chrome.runtime.lastError) { alert(chrome.runtime.lastError.message); return; } + // inspector Local and inspector should be identical TODO delete inspectorLocal? const inspectorLocal = inspectors.find(({ id }) => id === tab.id); + // if inspector window exists it get reattached and focused if (inspectorLocal) { inspectorLocal.active = true; chrome.runtime.sendMessage({ @@ -37,6 +53,7 @@ chrome.browserAction.onClicked.addListener((tab) => { tabId: tab.id, }); chrome.windows.update(inspectorLocal.popup.id, { focused: true }); + // else inspector windows created and new object pushed to inspectors array } else { chrome.windows.create( { diff --git a/src/inspector.tsx b/src/inspector.tsx index f47c9f8..179269b 100644 --- a/src/inspector.tsx +++ b/src/inspector.tsx @@ -3,12 +3,17 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './reset.css'; import App from './viewer/App'; +import { NetworkWebSocketParams } from './viewer/types'; +import FrameDataArray from './FrameData'; +// gets tabId of inspected window from URL const tabId = parseInt(window.location.search.substr(1), 10); // TODO create const handlers: any = {}; +const frameDataArray = new FrameDataArray(); function startDebugging() { + // Command Debugger to use Network inspector module chrome.debugger.sendCommand({ tabId }, 'Network.enable', undefined, () => { if (chrome.runtime.lastError) { console.error(chrome.runtime.lastError.message); @@ -16,34 +21,56 @@ function startDebugging() { console.log('Network enabled'); } }); - + // Creates title for inspector page chrome.tabs.get(tabId, (tab) => { if (tab.title) { - document.title = `WebSocket Inspector - ${tab.title}`; + document.title = `(tab.id = ${tabId}) WebSocket Inspector - ${tab.title} `; } else { document.title = 'WebSocket Inspector'; } }); } - +// Restarts Network debugging on command chrome.runtime.onMessage.addListener((message) => { if (message.message === 'reattach' && message.tabId === tabId) { startDebugging(); } }); - +// Starts Network debugging on load +window.addEventListener('load', () => { + startDebugging(); +}); +// Old function to listen Network events TODO refactor to core chrome.debugger.onEvent.addListener((debuggee, message, params) => { if (debuggee.tabId !== tabId) { return; } + // What does this do? if (handlers[message]) { handlers[message](params); } }); - -window.addEventListener('load', () => { - startDebugging(); +// New function to listen Network events +chrome.debugger.onEvent.addListener((source, method, params) => { + const METHOD_FRAME_IN = 'Network.webSocketFrameReceived', + METHOD_FRAME_OUT = 'Network.webSocketFrameSent'; + if (source.tabId !== tabId) { + return; + } + if (method === METHOD_FRAME_IN || method === METHOD_FRAME_OUT) { + // Get Frame + const sendingType = method === METHOD_FRAME_IN ? 'incoming' : 'outgoing'; + const { requestId, timestamp, response } = params as NetworkWebSocketParams; + frameDataArray.addFrameEntry(sendingType, requestId, timestamp, response); + console.log({ frameDataArray }); + let abs = frameDataArray.getFrameViewArray(); + console.log({ abs }); + } }); -ReactDOM.render(, document.getElementById('root')); +const frameViewArray = frameDataArray.getFrameViewArray(); +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/src/viewer/App.tsx b/src/viewer/App.tsx index 37be7cd..782f10e 100644 --- a/src/viewer/App.tsx +++ b/src/viewer/App.tsx @@ -1,26 +1,28 @@ -import React, { ChangeEvent } from 'react'; +import React from 'react'; import Panel from 'react-flex-panel'; import ControlPanel from './ControlPanel/ControlPanel'; import FrameList from './FrameTable/FrameTable'; import PanelView from './PanelView/PanelView'; import { stringToBuffer } from './Helpers/Helper'; import './App.scss'; -import { IFrame, IFrameType, Response } from './types'; +import { FrameEntryType, frameSendingType, WebSocketFrame } from './types'; +import { FrameEntry } from '../FrameData'; interface AppProps { handlers: any; + frameViewArray: FrameEntry[]; } interface AppState { [key: string]: any; - frames: IFrame[]; - activeId: number | null; + frameViewArray: FrameEntry[]; + activeId: string | null; isCapturing: boolean; regName: string; filter: string; isFilterInverse: boolean; } export default class App extends React.Component { - frameUniqueId = 0; + // frameUniqueId = 0; frameIssueTime: number; @@ -30,11 +32,11 @@ export default class App extends React.Component { constructor(props: AppProps) { super(props); - this.props.handlers['Network.webSocketFrameReceived'] = this.webSocketFrameReceived.bind(this); - this.props.handlers['Network.webSocketFrameSent'] = this.webSocketFrameSent.bind(this); + // this.props.handlers['Network.webSocketFrameReceived'] = this.webSocketFrameReceived.bind(this); + // this.props.handlers['Network.webSocketFrameSent'] = this.webSocketFrameSent.bind(this); this.state = { - frames: [], - activeId: null, + frameViewArray: props.frameViewArray, + activeId: '', isCapturing: true, regName: '', filter: '', @@ -57,7 +59,6 @@ export default class App extends React.Component { return acc; }, {}); this.setState(cacheState); - // TODO Boolean values turns to strings. } componentDidUpdate(prevProps: AppProps, prevState: AppState) { @@ -77,8 +78,8 @@ export default class App extends React.Component { } render() { - const { frames, activeId, regName, filter, isFilterInverse, isCapturing } = this.state; - const active = frames.find((f) => f.id === activeId); + const { frameViewArray, activeId, regName, filter, isFilterInverse, isCapturing } = this.state; + const active = frameViewArray.find((frameEntry) => frameEntry.id === activeId); return ( @@ -87,19 +88,16 @@ export default class App extends React.Component { onCapturingToggle={this.onCapturingToggle} isCapturing={isCapturing} regName={regName} - onRegName={this.setRegName} + handleRegName={this.setRegName} filter={filter} - onFilter={this.setFilter} - isFilterInverse={this.state.isFilterInverse} + handleFilter={this.setFilter} + isFilterInverse={isFilterInverse} onFilterModeToggle={this.onFilterModeToggle} /> @@ -113,7 +111,7 @@ export default class App extends React.Component { ); } - selectFrame = (id: number) => { + selectFrame = (id: string) => { this.setState({ activeId: id }); }; @@ -125,23 +123,23 @@ export default class App extends React.Component { this.setState({ isCapturing: !this.state.isCapturing }); }; - setRegName = (e: ChangeEvent) => { - this.setState({ regName: e.target.value }); + setRegName = (regName: string) => { + this.setState({ regName }); }; - setFilter = (e: ChangeEvent) => { - this.setState({ filter: e.target.value }); + setFilter = (filter: string) => { + this.setState({ filter }); }; onFilterModeToggle = () => { this.setState({ isFilterInverse: !this.state.isFilterInverse }); }; - addFrame(type: IFrameType, timestamp: number, response: Response) { - if (response.opcode === 1 || response.opcode === 2) { - const frame: IFrame = { - type, - name: type, + /* addFrame(sendingType: frameSendingType, timestamp: number, response: WebSocketFrame) { + if ((response.opcode === 1 || response.opcode === 2) && this.state.isCapturing) { + const frame: FrameEntryType = { + sendingType: sendingType, + name: sendingType, id: this.frameUniqueId, time: this.getTime(timestamp), length: response.payloadData.length, @@ -156,15 +154,15 @@ export default class App extends React.Component { } } - webSocketFrameReceived({ timestamp, response }: { timestamp: number; response: Response }) { + webSocketFrameReceived({ timestamp, response }: { timestamp: number; response: WebSocketFrame }) { if (this.state.isCapturing) { this.addFrame('incoming', timestamp, response); } } - webSocketFrameSent({ timestamp, response }: { timestamp: number; response: Response }) { + webSocketFrameSent({ timestamp, response }: { timestamp: number; response: WebSocketFrame }) { if (this.state.isCapturing) { this.addFrame('outgoing', timestamp, response); } - } + }*/ } diff --git a/src/viewer/ControlPanel/ControlPanel.tsx b/src/viewer/ControlPanel/ControlPanel.tsx index 17d5699..f9b6364 100644 --- a/src/viewer/ControlPanel/ControlPanel.tsx +++ b/src/viewer/ControlPanel/ControlPanel.tsx @@ -3,14 +3,14 @@ import FontAwesome from 'react-fontawesome'; import cx from 'classnames'; import './ControlPanel.scss'; import { EFilter } from '../types'; -// 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; + handleRegName: (arg0: string) => void; + handleFilter: (arg0: string) => void; }; interface ControlPanelProps extends ControlPanelMode, EFilter {} interface ControlPanelState { @@ -46,13 +46,22 @@ export default class ControlPanel extends React.Component) => { + handleRegName(e.target.value); + }; + + const onFilter = (e: ChangeEvent) => { + handleFilter(e.target.value); + }; + return (
void; -}; +interface FrameListProps { + frameViewArray: FrameEntry[]; + activeId: string | null; + onSelect: (id: string | null) => void; +} -export default class FrameList extends React.Component { +export default class FrameList extends React.Component { handlerClearSelect = () => { this.props.onSelect(null); }; render() { - const { frames, activeId, onSelect, ...filterData } = this.props; + const { frameViewArray, activeId, onSelect } = this.props; return (
    - {frames.map((frame) => ( - ( + ))}
@@ -35,32 +34,32 @@ export default class FrameList extends React.Component } } -type FrameEntryProps = { - key: number; - frame: IFrame; +interface FrameEntryProps { + key: string; + frameEntry: FrameEntry; selected: boolean; - onSelect: (id: number | null) => void; - filterData: EFilter; -}; + onSelect: (id: string | null) => void; +} -class FrameEntry extends React.PureComponent { +class FrameEntryComponent extends React.PureComponent { handlerSelect = (e: MouseEvent) => { e.stopPropagation(); - this.props.onSelect(this.props.frame.id); + this.props.onSelect(this.props.frameEntry.id); }; render() { - const { frame, selected, filterData } = this.props; - if (checkViable(frame, filterData as EFilter)) return null; + const { frameEntry, selected } = this.props; return (
  • - - {TimeStamp(frame.time)} - {getName(frame, filterData)} - {frame.length} + + /* {TimeStamp(frameEntry.time)} */ + {frameEntry.text} + {frameEntry.length}
  • ); } diff --git a/src/viewer/Helpers/Helper.ts b/src/viewer/Helpers/Helper.ts index 35f0e05..8abb7ed 100644 --- a/src/viewer/Helpers/Helper.ts +++ b/src/viewer/Helpers/Helper.ts @@ -1,4 +1,5 @@ -import { EFilter, IFrame } from '../types'; +import { EFilter, FrameEntryType } from '../types'; +import { FrameEntry } from '../../FrameData'; export function grep(text: string, regexp: string) { if (!(text && regexp)) { @@ -24,14 +25,20 @@ export const TimeStamp = (time: Date): string => { const ms = time.getMilliseconds(); return `${padded(h, 2)}:${padded(m, 2)}:${padded(s, 2)}.${padded(ms, 3)}`; }; -export const getName = (frame: IFrame, filterData: EFilter): string => { +export const getName = (frame: FrameEntryType, filterData: EFilter): string => { if (frame.text != null) { return grep(frame.text, filterData.regName) || frame.text; } return 'Binary Frame'; }; -export const checkViable = (frame: IFrame, filterData: EFilter): boolean => { +export const newGetName = (frame: FrameEntry, filterData: EFilter): string => { + if (frame.contentType !== 'binary') { + return grep(frame.text, filterData.regName) || frame.text; + } + return 'Binary Frame'; +}; +export const checkViable = (frame: FrameEntryType, filterData: EFilter): boolean => { if (filterData.filter && frame.text) { if (filterData.isFilterInverse) { return !!grep(frame.text, filterData.filter); diff --git a/src/viewer/PanelView/PanelView.tsx b/src/viewer/PanelView/PanelView.tsx index 7f52f13..7a9ee23 100644 --- a/src/viewer/PanelView/PanelView.tsx +++ b/src/viewer/PanelView/PanelView.tsx @@ -3,7 +3,7 @@ import React from 'react'; import cx from 'classnames'; import HexViewer from './HexViewer'; import './PanelView.scss'; -import { IFrame } from '../types'; +import { FrameEntryType } from '../types'; const TextViewer = ({ data }: { data: string | undefined }) => (
    {data}
    @@ -19,7 +19,7 @@ interface PanelViewState { panel?: PanelName | PanelName[] | null; } interface PanelViewProps { - frame: IFrame; + frame: FrameEntryType; } export default class PanelView extends React.Component { diff --git a/src/viewer/types.ts b/src/viewer/types.ts index 9b36ddb..d5bc5e2 100644 --- a/src/viewer/types.ts +++ b/src/viewer/types.ts @@ -1,6 +1,6 @@ -export type IFrameType = 'incoming' | 'outgoing'; -export type IFrame = { - type: IFrameType; +export type frameSendingType = 'incoming' | 'outgoing'; +export interface FrameEntryType { + sendingType: frameSendingType; name: string; id: number; time: Date; @@ -15,7 +15,15 @@ export interface EFilter { filter: string; isFilterInverse: boolean; } -export interface Response { + +export interface WebSocketFrame { opcode: number; + mask: boolean; payloadData: string; } + +export interface NetworkWebSocketParams { + requestId: string; + timestamp: number; + response: WebSocketFrame; +} diff --git a/src/viewer/typings/react-flex-panel.d.ts b/src/viewer/typings/react-flex-panel.d.ts new file mode 100644 index 0000000..338d388 --- /dev/null +++ b/src/viewer/typings/react-flex-panel.d.ts @@ -0,0 +1,17 @@ +declare module 'react-flex-panel' { + export class Panel { + rows: boolean; + cols: boolean; + size: number; + minSize: number; + maxSize: number; + flex: number; + resizable: boolean; + panelRef: void; + constructor(props: any, context: any); + onResize: () => number; + renderChildren: () => void; + onRef: () => void; + render: () => any; + } +} diff --git a/tsconfig.json b/tsconfig.json index 6a5efee..1d78447 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,8 @@ "target": "es6", "experimentalDecorators": true, "noUnusedLocals": true, - - "resolveJsonModule": true + "resolveJsonModule": true, + "typeRoots": ["./src/typings"] }, "exclude": ["node_modules"] }