react-native-flip
Version:
350 lines (297 loc) • 10 kB
JavaScript
/**
* 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
*/
;
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
"Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr)))
return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (
var _i = arr[Symbol.iterator](), _s;
!(_n = (_s = _i.next()).done);
_n = true
) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const Device = require("./Device");
const WS = require("ws");
const debug = require("debug")("Metro:InspectorProxy");
const url = require("url");
const WS_DEVICE_URL = "/inspector/device";
const WS_DEBUGGER_URL = "/inspector/debug";
const PAGES_LIST_JSON_URL = "/json";
const PAGES_LIST_JSON_URL_2 = "/json/list";
const PAGES_LIST_JSON_VERSION_URL = "/json/version";
const INTERNAL_ERROR_CODE = 1011;
/**
* Main Inspector Proxy class that connects JavaScript VM inside Android/iOS apps and JS debugger.
*/
class InspectorProxy {
// Root of the project used for relative to absolute source path conversion.
// Maps device ID to Device instance.
// Internal counter for device IDs -- just gets incremented for each new device.
// We store server's address with port (like '127.0.0.1:8081') to be able to build URLs
// (devtoolsFrontendUrl and webSocketDebuggerUrl) for page descriptions. These URLs are used
// by debugger to know where to connect.
constructor(projectRoot) {
_defineProperty(this, "_deviceCounter", 0);
_defineProperty(this, "_serverAddressWithPort", "");
this._projectRoot = projectRoot;
this._devices = new Map();
} // Process HTTP request sent to server. We only respond to 2 HTTP requests:
// 1. /json/version returns Chrome debugger protocol version that we use
// 2. /json and /json/list returns list of page descriptions (list of inspectable apps).
// This list is combined from all the connected devices.
processRequest(request, response, next) {
if (
request.url === PAGES_LIST_JSON_URL ||
request.url === PAGES_LIST_JSON_URL_2
) {
// Build list of pages from all devices.
let result = [];
Array.from(this._devices.entries()).forEach(_ref => {
let _ref2 = _slicedToArray(_ref, 2),
deviceId = _ref2[0],
device = _ref2[1];
result = result.concat(
device
.getPagesList()
.map(page => this._buildPageDescription(deviceId, device, page))
);
});
this._sendJsonResponse(response, result);
} else if (request.url === PAGES_LIST_JSON_VERSION_URL) {
this._sendJsonResponse(response, {
Browser: "Mobile JavaScript",
"Protocol-Version": "1.1"
});
} else {
next();
}
} // Adds websocket listeners to the provided HTTP/HTTPS server.
addWebSocketListener(server) {
const _server$address = server.address(),
port = _server$address.port;
if (server.address().family === "IPv6") {
this._serverAddressWithPort = `[::]:${port}`;
} else {
this._serverAddressWithPort = `localhost:${port}`;
}
this._addDeviceConnectionHandler(server);
this._addDebuggerConnectionHandler(server);
} // Converts page information received from device into PageDescription object
// that is sent to debugger.
_buildPageDescription(deviceId, device, page) {
const debuggerUrl = `${this._serverAddressWithPort}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`;
const webSocketDebuggerUrl = "ws://" + debuggerUrl;
const devtoolsFrontendUrl =
"chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=" +
encodeURIComponent(debuggerUrl);
return {
id: `${deviceId}-${page.id}`,
description: page.app,
title: page.title,
faviconUrl: "https://reactjs.org/favicon.ico",
devtoolsFrontendUrl,
type: "node",
webSocketDebuggerUrl,
vm: page.vm
};
} // Sends object as response to HTTP request.
// Just serializes object using JSON and sets required headers.
_sendJsonResponse(response, object) {
const data = JSON.stringify(object, null, 2);
response.writeHead(200, {
"Content-Type": "application/json; charset=UTF-8",
"Cache-Control": "no-cache",
"Content-Length": data.length.toString(),
Connection: "close"
});
response.end(data);
} // Adds websocket handler for device connections.
// Device connects to /inspector/device and passes device and app names as
// HTTP GET params.
// For each new websocket connection we parse device and app names and create
// new instance of Device class.
_addDeviceConnectionHandler(server) {
var _this = this;
const wss = new WS.Server({
server,
path: WS_DEVICE_URL,
perMessageDeflate: true
}); // $FlowFixMe[value-as-type]
wss.on(
"connection",
/*#__PURE__*/ (function() {
var _ref3 = _asyncToGenerator(function*(socket) {
try {
const query =
url.parse(socket.upgradeReq.url || "", true).query || {};
const deviceName = query.name || "Unknown";
const appName = query.app || "Unknown";
const deviceId = _this._deviceCounter++;
_this._devices.set(
deviceId,
new Device(
deviceId,
deviceName,
appName,
socket,
_this._projectRoot
)
);
debug(`Got new connection: device=${deviceName}, app=${appName}`);
socket.on("close", () => {
_this._devices.delete(deviceId);
debug(`Device ${deviceName} disconnected.`);
});
} catch (e) {
console.error("error", e);
socket.close(INTERNAL_ERROR_CODE, e);
}
});
return function(_x) {
return _ref3.apply(this, arguments);
};
})()
);
} // Adds websocket handler for debugger connections.
// Debugger connects to webSocketDebuggerUrl that we return as part of page description
// in /json response.
// When debugger connects we try to parse device and page IDs from the query and pass
// websocket object to corresponding Device instance.
_addDebuggerConnectionHandler(server) {
var _this2 = this;
const wss = new WS.Server({
server,
path: WS_DEBUGGER_URL,
perMessageDeflate: false
}); // $FlowFixMe[value-as-type]
wss.on(
"connection",
/*#__PURE__*/ (function() {
var _ref4 = _asyncToGenerator(function*(socket) {
try {
const query =
url.parse(socket.upgradeReq.url || "", true).query || {};
const deviceId = query.device;
const pageId = query.page;
if (deviceId == null || pageId == null) {
throw new Error(
"Incorrect URL - must provide device and page IDs"
);
}
const device = _this2._devices.get(parseInt(deviceId, 10));
if (device == null) {
throw new Error("Unknown device with ID " + deviceId);
}
device.handleDebuggerConnection(socket, pageId);
} catch (e) {
console.error(e);
socket.close(INTERNAL_ERROR_CODE, e);
}
});
return function(_x2) {
return _ref4.apply(this, arguments);
};
})()
);
}
}
module.exports = InspectorProxy;