-
-
Save darkl/3acc48adb6d7094826a7566aeca32b59 to your computer and use it in GitHub Desktop.
WAMP parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 { | |
Component, | |
createFactory, | |
} = require("devtools/client/shared/vendor/react"); | |
const dom = require("devtools/client/shared/vendor/react-dom-factories"); | |
const { div } = dom; | |
const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); | |
const Services = require("Services"); | |
const { L10N } = require("devtools/client/netmonitor/src/utils/l10n.js"); | |
const { | |
getFramePayload, | |
isJSON, | |
} = require("devtools/client/netmonitor/src/utils/request-utils.js"); | |
const { | |
getFormattedSize, | |
} = require("devtools/client/netmonitor/src/utils/format-utils.js"); | |
const MESSAGE_DATA_LIMIT = Services.prefs.getIntPref( | |
"devtools.netmonitor.ws.messageDataLimit" | |
); | |
const MESSAGE_DATA_TRUNCATED = L10N.getStr("messageDataTruncated"); | |
const SocketIODecoder = require("devtools/client/netmonitor/src/components/websockets/parsers/socket-io/index.js"); | |
const { | |
JsonHubProtocol, | |
HandshakeProtocol, | |
} = require("devtools/client/netmonitor/src/components/websockets/parsers/signalr/index.js"); | |
const { | |
parseSockJS, | |
} = require("devtools/client/netmonitor/src/components/websockets/parsers/sockjs/index.js"); | |
const { | |
parseWampMessage, | |
} = require("devtools/client/netmonitor/src/components/websockets/parsers/wamp/index.js"); | |
// Components | |
const Accordion = createFactory( | |
require("devtools/client/shared/components/Accordion") | |
); | |
const RawData = createFactory(require("./RawData")); | |
loader.lazyGetter(this, "JSONPreview", function() { | |
return createFactory( | |
require("devtools/client/netmonitor/src/components/JSONPreview") | |
); | |
}); | |
/** | |
* Shows the full payload of a WebSocket frame. | |
* The payload is unwrapped from the LongStringActor object. | |
*/ | |
class FramePayload extends Component { | |
static get propTypes() { | |
return { | |
connector: PropTypes.object.isRequired, | |
selectedFrame: PropTypes.object, | |
}; | |
} | |
constructor(props) { | |
super(props); | |
this.state = { | |
payload: "", | |
isFormattedData: false, | |
formattedData: {}, | |
formattedDataTitle: "", | |
}; | |
} | |
componentDidMount() { | |
this.updateFramePayload(); | |
} | |
componentDidUpdate(prevProps) { | |
if (this.props.selectedFrame !== prevProps.selectedFrame) { | |
this.updateFramePayload(); | |
} | |
} | |
updateFramePayload() { | |
const { selectedFrame, connector } = this.props; | |
getFramePayload(selectedFrame.payload, connector.getLongString).then( | |
payload => { | |
const { formattedData, formattedDataTitle } = this.parsePayload( | |
payload | |
); | |
this.setState({ | |
payload, | |
isFormattedData: !!formattedData, | |
formattedData, | |
formattedDataTitle, | |
}); | |
} | |
); | |
} | |
parsePayload(payload) { | |
// socket.io payload | |
const socketIOPayload = this.parseSocketIOPayload(payload); | |
if (socketIOPayload) { | |
return { | |
formattedData: socketIOPayload, | |
formattedDataTitle: "Socket.IO", | |
}; | |
} | |
// sockjs payload | |
const sockJSPayload = parseSockJS(payload); | |
if (sockJSPayload) { | |
return { | |
formattedData: sockJSPayload, | |
formattedDataTitle: "SockJS", | |
}; | |
} | |
// signalr payload | |
const signalRPayload = this.parseSignalR(payload); | |
if (signalRPayload) { | |
return { | |
formattedData: signalRPayload, | |
formattedDataTitle: "SignalR", | |
}; | |
} | |
// wamp payload | |
const wampPayload = this.parseWamp(payload); | |
if (wampPayload) { | |
return { | |
formattedData: wampPayload, | |
formattedDataTitle: "WAMP", | |
}; | |
} | |
// json payload | |
const { json } = isJSON(payload); | |
if (json) { | |
return { | |
formattedData: json, | |
formattedDataTitle: "JSON", | |
}; | |
} | |
return { | |
formattedData: null, | |
formattedDataTitle: "", | |
}; | |
} | |
parseSocketIOPayload(payload) { | |
let result; | |
// Try decoding socket.io frames | |
try { | |
const decoder = new SocketIODecoder(); | |
decoder.on("decoded", decodedPacket => { | |
if ( | |
decodedPacket && | |
!decodedPacket.data.includes("parser error") && | |
decodedPacket.type | |
) { | |
result = decodedPacket; | |
} | |
}); | |
decoder.add(payload); | |
return result; | |
} catch (err) { | |
// Ignore errors | |
} | |
return null; | |
} | |
parseSignalR(payload) { | |
// attempt to parse as HandshakeResponseMessage | |
let decoder; | |
try { | |
decoder = new HandshakeProtocol(); | |
const [remainingData, responseMessage] = decoder.parseHandshakeResponse( | |
payload | |
); | |
if (responseMessage) { | |
return { | |
handshakeResponse: responseMessage, | |
remainingData: this.parseSignalR(remainingData), | |
}; | |
} | |
} catch (err) { | |
// ignore errors; | |
} | |
// attempt to parse as JsonHubProtocolMessage | |
try { | |
decoder = new JsonHubProtocol(); | |
const msgs = decoder.parseMessages(payload, null); | |
if (msgs && msgs.length) { | |
return msgs; | |
} | |
} catch (err) { | |
// ignore errors; | |
} | |
// MVP Signalr | |
if (payload.endsWith("\u001e")) { | |
const { json } = isJSON(payload.slice(0, -1)); | |
if (json) { | |
return json; | |
} | |
} | |
return null; | |
} | |
parseWampMessage(payload) { | |
try { | |
var array = JSON.parse(payload); | |
return parseWampMessage(array); | |
} | |
catch (err) { | |
// ignore errors; | |
} | |
return null; | |
} | |
render() { | |
let payload = this.state.payload; | |
let isTruncated = false; | |
if (this.state.payload.length >= MESSAGE_DATA_LIMIT) { | |
payload = payload.substring(0, MESSAGE_DATA_LIMIT); | |
isTruncated = true; | |
} | |
const items = [ | |
{ | |
className: "rawData", | |
component: RawData, | |
componentProps: { payload }, | |
header: L10N.getFormatStrWithNumbers( | |
"netmonitor.ws.rawData.header", | |
getFormattedSize(this.state.payload.length) | |
), | |
id: "ws-frame-rawData", | |
opened: true, | |
}, | |
]; | |
if (!isTruncated && this.state.isFormattedData) { | |
/** | |
* Push the JSON section (formatted data) at the begging of the array | |
* before the raw data section. Note that the JSON section will be | |
* auto-expanded while the raw data auto-collapsed. | |
*/ | |
items.unshift({ | |
className: "formattedData", | |
component: JSONPreview, | |
componentProps: { | |
object: this.state.formattedData, | |
columns: [ | |
{ | |
id: "value", | |
width: "100%", | |
}, | |
], | |
}, | |
header: `${this.state.formattedDataTitle} (${getFormattedSize( | |
this.state.payload.length | |
)})`, | |
id: "ws-frame-formattedData", | |
opened: true, | |
}); | |
} | |
return div( | |
{ | |
className: "ws-frame-payload", | |
}, | |
isTruncated && | |
div( | |
{ | |
className: "truncated-data-message", | |
}, | |
MESSAGE_DATA_TRUNCATED | |
), | |
Accordion({ | |
items, | |
}) | |
); | |
} | |
} | |
module.exports = FramePayload; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// parsers/wamp/index.js | |
var WampMessageType; | |
(function (WampMessageType) { | |
WampMessageType[WampMessageType["Hello"] = 1] = "Hello"; | |
WampMessageType[WampMessageType["Welcome"] = 2] = "Welcome"; | |
WampMessageType[WampMessageType["Abort"] = 3] = "Abort"; | |
WampMessageType[WampMessageType["Challenge"] = 4] = "Challenge"; | |
WampMessageType[WampMessageType["Authenticate"] = 5] = "Authenticate"; | |
WampMessageType[WampMessageType["Goodbye"] = 6] = "Goodbye"; | |
WampMessageType[WampMessageType["Error"] = 8] = "Error"; | |
WampMessageType[WampMessageType["Publish"] = 16] = "Publish"; | |
WampMessageType[WampMessageType["Published"] = 17] = "Published"; | |
WampMessageType[WampMessageType["Subscribe"] = 32] = "Subscribe"; | |
WampMessageType[WampMessageType["Subscribed"] = 33] = "Subscribed"; | |
WampMessageType[WampMessageType["Unsubscribe"] = 34] = "Unsubscribe"; | |
WampMessageType[WampMessageType["Unsubscribed"] = 35] = "Unsubscribed"; | |
WampMessageType[WampMessageType["Event"] = 36] = "Event"; | |
WampMessageType[WampMessageType["Call"] = 48] = "Call"; | |
WampMessageType[WampMessageType["Cancel"] = 49] = "Cancel"; | |
WampMessageType[WampMessageType["Result"] = 50] = "Result"; | |
WampMessageType[WampMessageType["Register"] = 64] = "Register"; | |
WampMessageType[WampMessageType["Registered"] = 65] = "Registered"; | |
WampMessageType[WampMessageType["Unregister"] = 66] = "Unregister"; | |
WampMessageType[WampMessageType["Unregistered"] = 67] = "Unregistered"; | |
WampMessageType[WampMessageType["Invocation"] = 68] = "Invocation"; | |
WampMessageType[WampMessageType["Interrupt"] = 69] = "Interrupt"; | |
WampMessageType[WampMessageType["Yield"] = 70] = "Yield"; | |
})(WampMessageType || (WampMessageType = {})); | |
function parseWampMessage(messageArray) { | |
var messageType; | |
var arguments; | |
[messageType, ...arguments] = messageArray; | |
switch (messageType) { | |
case WampMessageType.Hello: | |
return new HelloMessage(arguments); | |
case WampMessageType.Welcome: | |
return new WelcomeMessage(arguments); | |
case WampMessageType.Abort: | |
return new AbortMessage(arguments); | |
case WampMessageType.Challenge: | |
return new ChallengeMessage(arguments); | |
case WampMessageType.Authenticate: | |
return new AuthenticateMessage(arguments); | |
case WampMessageType.Goodbye: | |
return new GoodbyeMessage(arguments); | |
case WampMessageType.Error: | |
return new ErrorMessage(arguments); | |
case WampMessageType.Publish: | |
return new PublishMessage(arguments); | |
case WampMessageType.Published: | |
return new PublishedMessage(arguments); | |
case WampMessageType.Subscribe: | |
return new SubscribeMessage(arguments); | |
case WampMessageType.Subscribed: | |
return new SubscribedMessage(arguments); | |
case WampMessageType.Unsubscribe: | |
return new UnsubscribeMessage(arguments); | |
case WampMessageType.Unsubscribed: | |
return new UnsubscribedMessage(arguments); | |
case WampMessageType.Event: | |
return new EventMessage(arguments); | |
case WampMessageType.Call: | |
return new CallMessage(arguments); | |
case WampMessageType.Cancel: | |
return new CancelMessage(arguments); | |
case WampMessageType.Result: | |
return new ResultMessage(arguments); | |
case WampMessageType.Register: | |
return new RegisterMessage(arguments); | |
case WampMessageType.Registered: | |
return new RegisteredMessage(arguments); | |
case WampMessageType.Unregister: | |
return new UnregisterMessage(arguments); | |
case WampMessageType.Unregistered: | |
return new UnregisteredMessage(arguments); | |
case WampMessageType.Invocation: | |
return new InvocationMessage(arguments); | |
case WampMessageType.Interrupt: | |
return new InterruptMessage(arguments); | |
case WampMessageType.Yield: | |
return new YieldMessage(arguments); | |
default: | |
return null; | |
} | |
} | |
class HelloMessage { | |
constructor(messageArguments) { | |
[this._realm, this._details] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Hello; } | |
get messageName() { return "HELLO"; } | |
get realm() { return this._realm; } | |
get details() { return this._details; } | |
} | |
class WelcomeMessage { | |
constructor(messageArguments) { | |
[this._session, this._details] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Welcome; } | |
get messageName() { return "WELCOME"; } | |
get session() { return this._session; } | |
get details() { return this._details; } | |
} | |
class AbortMessage { | |
constructor(messageArguments) { | |
[this._details, this._reason] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Abort; } | |
get messageName() { return "ABORT"; } | |
get details() { return this._details; } | |
get reason() { return this._reason; } | |
} | |
class ChallengeMessage { | |
constructor(messageArguments) { | |
[this._authMethod, this._extra] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Challenge; } | |
get messageName() { return "CHALLENGE"; } | |
get authMethod() { return this._authMethod; } | |
get extra() { return this._extra; } | |
} | |
class AuthenticateMessage { | |
constructor(messageArguments) { | |
[this._signature, this._extra] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Authenticate; } | |
get messageName() { return "AUTHENTICATE"; } | |
get signature() { return this._signature; } | |
get extra() { return this._extra; } | |
} | |
class GoodbyeMessage { | |
constructor(messageArguments) { | |
[this._details, this._reason] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Goodbye; } | |
get messageName() { return "GOODBYE"; } | |
get details() { return this._details; } | |
get reason() { return this._reason; } | |
} | |
class ErrorMessage { | |
constructor(messageArguments) { | |
[this._type, this._request, this._details, this._error, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Error; } | |
get messageName() { return "ERROR"; } | |
get type() { return this._type; } | |
get request() { return this._request; } | |
get details() { return this._details; } | |
get error() { return this._error; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} | |
class PublishMessage { | |
constructor(messageArguments) { | |
[this._request, this._options, this._topic, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Publish; } | |
get messageName() { return "PUBLISH"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
get topic() { return this._topic; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} | |
class PublishedMessage { | |
constructor(messageArguments) { | |
[this._request, this._publication] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Published; } | |
get messageName() { return "PUBLISHED"; } | |
get request() { return this._request; } | |
get publication() { return this._publication; } | |
} | |
class SubscribeMessage { | |
constructor(messageArguments) { | |
[this._request, this._options, this._topic] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Subscribe; } | |
get messageName() { return "SUBSCRIBE"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
get topic() { return this._topic; } | |
} | |
class SubscribedMessage { | |
constructor(messageArguments) { | |
[this._request, this._subscription] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Subscribed; } | |
get messageName() { return "SUBSCRIBED"; } | |
get request() { return this._request; } | |
get subscription() { return this._subscription; } | |
} | |
class UnsubscribeMessage { | |
constructor(messageArguments) { | |
[this._request, this._subscription] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Unsubscribe; } | |
get messageName() { return "UNSUBSCRIBE"; } | |
get request() { return this._request; } | |
get subscription() { return this._subscription; } | |
} | |
class UnsubscribedMessage { | |
constructor(messageArguments) { | |
[this._request] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Unsubscribed; } | |
get messageName() { return "UNSUBSCRIBED"; } | |
get request() { return this._request; } | |
} | |
class EventMessage { | |
constructor(messageArguments) { | |
[this._subscription, this._publication, this._details, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Event; } | |
get messageName() { return "EVENT"; } | |
get subscription() { return this._subscription; } | |
get publication() { return this._publication; } | |
get details() { return this._details; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} | |
class CallMessage { | |
constructor(messageArguments) { | |
[this._request, this._options, this._procedure, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Call; } | |
get messageName() { return "CALL"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
get procedure() { return this._procedure; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} | |
class CancelMessage { | |
constructor(messageArguments) { | |
[this._request, this._options] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Cancel; } | |
get messageName() { return "CANCEL"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
} | |
class ResultMessage { | |
constructor(messageArguments) { | |
[this._request, this._details, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Result; } | |
get messageName() { return "RESULT"; } | |
get request() { return this._request; } | |
get details() { return this._details; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} | |
class RegisterMessage { | |
constructor(messageArguments) { | |
[this._request, this._options, this._procedure] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Register; } | |
get messageName() { return "REGISTER"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
get procedure() { return this._procedure; } | |
} | |
class RegisteredMessage { | |
constructor(messageArguments) { | |
[this._request, this._registration] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Registered; } | |
get messageName() { return "REGISTERED"; } | |
get request() { return this._request; } | |
get registration() { return this._registration; } | |
} | |
class UnregisterMessage { | |
constructor(messageArguments) { | |
[this._request, this._registration] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Unregister; } | |
get messageName() { return "UNREGISTER"; } | |
get request() { return this._request; } | |
get registration() { return this._registration; } | |
} | |
class UnregisteredMessage { | |
constructor(messageArguments) { | |
[this._request] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Unregistered; } | |
get messageName() { return "UNREGISTERED"; } | |
get request() { return this._request; } | |
} | |
class InvocationMessage { | |
constructor(messageArguments) { | |
[this._request, this._registration, this._details, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Invocation; } | |
get messageName() { return "INVOCATION"; } | |
get request() { return this._request; } | |
get registration() { return this._registration; } | |
get details() { return this._details; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} | |
class InterruptMessage { | |
constructor(messageArguments) { | |
[this._request, this._options] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Interrupt; } | |
get messageName() { return "INTERRUPT"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
} | |
class YieldMessage { | |
constructor(messageArguments) { | |
[this._request, this._options, this._arguments, this._argumentsKw] = messageArguments; | |
} | |
get messageCode() { return WampMessageType.Yield; } | |
get messageName() { return "YIELD"; } | |
get request() { return this._request; } | |
get options() { return this._options; } | |
get arguments() { return this._arguments; } | |
get argumentsKw() { return this._argumentsKw; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment