react-native/local-cli/server/util/messageSocket.js

236 lines
6.1 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const url = require('url');
const WebSocketServer = require('ws').Server;
const PROTOCOL_VERSION = 2;
const notifier = require('node-notifier');
function parseMessage(data, binary) {
if (binary) {
console.error('Expected text message, got binary!');
return undefined;
}
try {
const message = JSON.parse(data);
if (message.version === PROTOCOL_VERSION) {
return message;
}
console.error(
'Received message had wrong protocol version: ' + message.version,
);
} catch (e) {
console.error('Failed to parse the message as JSON:\n' + data);
}
return undefined;
}
function isBroadcast(message) {
return (
typeof message.method === 'string' &&
message.id === undefined &&
message.target === undefined
);
}
function isRequest(message) {
return (
typeof message.method === 'string' && typeof message.target === 'string'
);
}
function isResponse(message) {
return (
typeof message.id === 'object' &&
typeof message.id.requestId !== undefined &&
typeof message.id.clientId === 'string' &&
(message.result !== undefined || message.error !== undefined)
);
}
function attachToServer(server, path) {
const wss = new WebSocketServer({
server: server,
path: path,
});
const clients = new Map();
let nextClientId = 0;
function getClientWs(clientId) {
const clientWs = clients.get(clientId);
if (clientWs === undefined) {
throw `could not find id "${clientId}" while forwarding request`;
}
return clientWs;
}
function handleSendBroadcast(broadcasterId, message) {
const forwarded = {
version: PROTOCOL_VERSION,
method: message.method,
params: message.params,
};
if (clients.size === 0) {
notifier.notify({
title: 'React Native: No apps connected',
message:
`Sending '${message.method}' to all React Native apps ` +
'failed. Make sure your app is running in the simulator ' +
'or on a phone connected via USB.',
});
}
for (const [otherId, otherWs] of clients) {
if (otherId !== broadcasterId) {
try {
otherWs.send(JSON.stringify(forwarded));
} catch (e) {
console.error(
`Failed to send broadcast to client: '${otherId}' ` +
`due to:\n ${e.toString()}`,
);
}
}
}
}
wss.on('connection', function(clientWs) {
const clientId = `client#${nextClientId++}`;
function handleCaughtError(message, error) {
const errorMessage = {
id: message.id,
method: message.method,
target: message.target,
error: message.error === undefined ? 'undefined' : 'defined',
params: message.params === undefined ? 'undefined' : 'defined',
result: message.result === undefined ? 'undefined' : 'defined',
};
if (message.id === undefined) {
console.error(
`Handling message from ${clientId} failed with:\n${error}\n` +
`message:\n${JSON.stringify(errorMessage)}`,
);
} else {
try {
clientWs.send(
JSON.stringify({
version: PROTOCOL_VERSION,
error: error,
id: message.id,
}),
);
} catch (e) {
console.error(
`Failed to reply to ${clientId} with error:\n${error}` +
`\nmessage:\n${JSON.stringify(errorMessage)}` +
`\ndue to error: ${e.toString()}`,
);
}
}
}
function handleServerRequest(message) {
let result = null;
switch (message.method) {
case 'getid':
result = clientId;
break;
case 'getpeers':
result = {};
clients.forEach((otherWs, otherId) => {
if (clientId !== otherId) {
result[otherId] = url.parse(otherWs.upgradeReq.url, true).query;
}
});
break;
default:
throw `unknown method: ${message.method}`;
}
clientWs.send(
JSON.stringify({
version: PROTOCOL_VERSION,
result: result,
id: message.id,
}),
);
}
function forwardRequest(message) {
getClientWs(message.target).send(
JSON.stringify({
version: PROTOCOL_VERSION,
method: message.method,
params: message.params,
id:
message.id === undefined
? undefined
: {requestId: message.id, clientId: clientId},
}),
);
}
function forwardResponse(message) {
getClientWs(message.id.clientId).send(
JSON.stringify({
version: PROTOCOL_VERSION,
result: message.result,
error: message.error,
id: message.id.requestId,
}),
);
}
clients.set(clientId, clientWs);
clientWs.onclose = clientWs.onerror = () => {
clientWs.onmessage = null;
clients.delete(clientId);
};
clientWs.onmessage = event => {
const message = parseMessage(event.data, event.binary);
if (message === undefined) {
console.error('Received message not matching protocol');
return;
}
try {
if (isBroadcast(message)) {
handleSendBroadcast(clientId, message);
} else if (isRequest(message)) {
if (message.target === 'server') {
handleServerRequest(message);
} else {
forwardRequest(message);
}
} else if (isResponse(message)) {
forwardResponse(message);
} else {
throw 'Invalid message, did not match the protocol';
}
} catch (e) {
handleCaughtError(message, e.toString());
}
};
});
return {
broadcast: (method, params) => {
handleSendBroadcast(null, {method: method, params: params});
},
};
}
module.exports = {
attachToServer: attachToServer,
parseMessage: parseMessage,
};