mirror of https://github.com/status-im/metro.git
Delete SocketInterface code
Reviewed By: davidaurelio Differential Revision: D3152105 fb-gh-sync-id: a6c13bb54c2164ebc063a1b14f00114738546c8c fbshipit-source-id: a6c13bb54c2164ebc063a1b14f00114738546c8c
This commit is contained in:
parent
1d9df442a4
commit
43c5ed2be0
|
@ -73,17 +73,6 @@ exports.getOrderedDependencyPaths = function(options, bundleOptions) {
|
|||
});
|
||||
};
|
||||
|
||||
exports.createClientFor = function(options) {
|
||||
if (options.verbose) {
|
||||
enableDebug();
|
||||
}
|
||||
startSocketInterface();
|
||||
return (
|
||||
require('./src/SocketInterface')
|
||||
.getOrCreateSocketFor(omit(options, ['verbose']))
|
||||
);
|
||||
};
|
||||
|
||||
function useGracefulFs() {
|
||||
var fs = require('fs');
|
||||
var gracefulFs = require('graceful-fs');
|
||||
|
@ -111,7 +100,6 @@ function createServer(options) {
|
|||
enableDebug();
|
||||
}
|
||||
|
||||
startSocketInterface();
|
||||
var Server = require('./src/Server');
|
||||
return new Server(omit(options, ['verbose']));
|
||||
}
|
||||
|
@ -135,19 +123,3 @@ function omit(obj, blacklistedKeys) {
|
|||
return clone;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// we need to listen on a socket as soon as a server is created, but only once.
|
||||
// This file also serves as entry point when spawning a socket server; in that
|
||||
// case we need to start the server immediately.
|
||||
var didStartSocketInterface = false;
|
||||
function startSocketInterface() {
|
||||
if (didStartSocketInterface) {
|
||||
return;
|
||||
}
|
||||
didStartSocketInterface = true;
|
||||
require('./src/SocketInterface').listenOnServerMessages();
|
||||
}
|
||||
|
||||
if (require.main === module) { // used as entry point
|
||||
startSocketInterface();
|
||||
}
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Bundle = require('../Bundler/Bundle');
|
||||
const PrepackBundle = require('../Bundler/PrepackBundle');
|
||||
const Promise = require('promise');
|
||||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactNativePackager:SocketClient');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
|
||||
const LOG_PATH = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
class SocketClient {
|
||||
static create(sockPath) {
|
||||
return new SocketClient(sockPath).onReady();
|
||||
}
|
||||
|
||||
constructor(sockPath) {
|
||||
debug('connecting to', sockPath);
|
||||
|
||||
this._sock = net.connect(sockPath);
|
||||
this._ready = new Promise((resolve, reject) => {
|
||||
this._sock.on('connect', () => {
|
||||
this._sock.removeAllListeners('error');
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('uncaught error', error.stack);
|
||||
setImmediate(() => process.exit(1));
|
||||
});
|
||||
resolve(this);
|
||||
});
|
||||
this._sock.on('error', (e) => {
|
||||
e.message = `Error connecting to server on ${sockPath} ` +
|
||||
`with error: ${e.message}`;
|
||||
e.message += getServerLogs();
|
||||
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
|
||||
this._resolvers = Object.create(null);
|
||||
const bunser = new bser.BunserBuf();
|
||||
this._sock.on('data', (buf) => bunser.append(buf));
|
||||
bunser.on('value', (message) => this._handleMessage(message));
|
||||
|
||||
this._sock.on('close', () => {
|
||||
if (!this._closing) {
|
||||
const terminate = (result) => {
|
||||
const sockPathExists = fs.existsSync(sockPath);
|
||||
throw new Error(
|
||||
'Server closed unexpectedly.\n' +
|
||||
'Server ping connection attempt result: ' + result + '\n' +
|
||||
'Socket path: `' + sockPath + '` ' +
|
||||
(sockPathExists ? ' exists.' : 'doesn\'t exist') + '\n' +
|
||||
getServerLogs()
|
||||
);
|
||||
};
|
||||
|
||||
// before throwing ping the server to see if it's still alive
|
||||
const socket = net.connect(sockPath);
|
||||
socket.on('connect', () => {
|
||||
socket.end();
|
||||
terminate('OK');
|
||||
});
|
||||
socket.on('error', error => terminate(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onReady() {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
getDependencies(main) {
|
||||
return this._send({
|
||||
type: 'getDependencies',
|
||||
data: main,
|
||||
});
|
||||
}
|
||||
|
||||
getOrderedDependencyPaths(main) {
|
||||
return this._send({
|
||||
type: 'getOrderedDependencyPaths',
|
||||
data: main,
|
||||
});
|
||||
}
|
||||
|
||||
buildBundle(options) {
|
||||
return this._send({
|
||||
type: 'buildBundle',
|
||||
data: options,
|
||||
}).then(json => Bundle.fromJSON(json));
|
||||
}
|
||||
|
||||
buildPrepackBundle(options) {
|
||||
return this._send({
|
||||
type: 'buildPrepackBundle',
|
||||
data: options,
|
||||
}).then(json => PrepackBundle.fromJSON(json));
|
||||
}
|
||||
|
||||
_send(message) {
|
||||
message.id = uid();
|
||||
this._sock.write(bser.dumpToBuffer(message));
|
||||
return new Promise((resolve, reject) => {
|
||||
this._resolvers[message.id] = {resolve, reject};
|
||||
});
|
||||
}
|
||||
|
||||
_handleMessage(message) {
|
||||
if (!(message && message.id && message.type)) {
|
||||
throw new Error(
|
||||
'Malformed message from server ' + JSON.stringify(message)
|
||||
);
|
||||
}
|
||||
|
||||
debug('got message with type', message.type);
|
||||
|
||||
const resolver = this._resolvers[message.id];
|
||||
if (!resolver) {
|
||||
throw new Error(
|
||||
'Unrecognized message id (' + message.id + ') ' +
|
||||
'message already resolved or never existed.'
|
||||
);
|
||||
}
|
||||
|
||||
delete this._resolvers[message.id];
|
||||
|
||||
if (message.type === 'error') {
|
||||
const errorLog =
|
||||
message.data && message.data.indexOf('TimeoutError') === -1
|
||||
? 'See logs ' + LOG_PATH
|
||||
: getServerLogs();
|
||||
|
||||
resolver.reject(new Error(message.data + '\n' + errorLog));
|
||||
} else {
|
||||
resolver.resolve(message.data);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
debug('closing connection');
|
||||
this._closing = true;
|
||||
this._sock.end();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SocketClient;
|
||||
|
||||
function uid(len) {
|
||||
len = len || 7;
|
||||
return Math.random().toString(35).substr(2, len);
|
||||
}
|
||||
|
||||
function getServerLogs() {
|
||||
if (fs.existsSync(LOG_PATH)) {
|
||||
return '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
|
@ -1,224 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Promise = require('promise');
|
||||
const Server = require('../Server');
|
||||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactNativePackager:SocketServer');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
|
||||
const MAX_IDLE_TIME = 30 * 1000;
|
||||
const MAX_STARTUP_TIME = 5 * 60 * 1000;
|
||||
|
||||
class SocketServer {
|
||||
constructor(sockPath, options) {
|
||||
this._server = net.createServer();
|
||||
this._server.listen(sockPath);
|
||||
this._ready = new Promise((resolve, reject) => {
|
||||
this._server.once('error', (e) => reject(e));
|
||||
this._server.once('listening', () => {
|
||||
// Remove error listener so we make sure errors propagate.
|
||||
this._server.removeAllListeners('error');
|
||||
this._server.on(
|
||||
'close',
|
||||
() => debug('server closed')
|
||||
);
|
||||
|
||||
debug(
|
||||
'Process %d listening on socket path %s ' +
|
||||
'for server with options %j',
|
||||
process.pid,
|
||||
sockPath,
|
||||
options
|
||||
);
|
||||
resolve(this);
|
||||
process.on('exit', code => {
|
||||
debug('exit code:', code);
|
||||
fs.unlinkSync(sockPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
debug('uncaught error', error.stack);
|
||||
setImmediate(() => process.exit(1));
|
||||
});
|
||||
|
||||
this._server.on('connection', (sock) => this._handleConnection(sock));
|
||||
|
||||
// Disable the file watcher.
|
||||
options.nonPersistent = true;
|
||||
this._packagerServer = new Server(options);
|
||||
this._dieEventually(MAX_STARTUP_TIME);
|
||||
}
|
||||
|
||||
onReady() {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
_handleConnection(sock) {
|
||||
debug('connection to server', process.pid);
|
||||
|
||||
const bunser = new bser.BunserBuf();
|
||||
sock.on('data', (buf) => bunser.append(buf));
|
||||
bunser.on('value', (m) => this._handleMessage(sock, m));
|
||||
bunser.on('error', (e) => {
|
||||
e.message = 'Unhandled error from the bser buffer. ' +
|
||||
'Either error on encoding or message handling: \n' +
|
||||
e.message;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
_handleMessage(sock, m) {
|
||||
if (!m || !m.id || !m.data) {
|
||||
console.error('SocketServer recieved a malformed message: %j', m);
|
||||
return;
|
||||
}
|
||||
|
||||
debug('got request', m);
|
||||
|
||||
// Debounce the kill timer.
|
||||
this._dieEventually();
|
||||
|
||||
const handleError = (error) => {
|
||||
debug('request error', error);
|
||||
this._reply(sock, m.id, 'error', error.stack);
|
||||
|
||||
// Fatal error from JSTransformer transform workers.
|
||||
if (error.type === 'ProcessTerminatedError') {
|
||||
setImmediate(() => process.exit(1));
|
||||
}
|
||||
};
|
||||
|
||||
switch (m.type) {
|
||||
case 'getDependencies':
|
||||
this._packagerServer.getDependencies(m.data).then(
|
||||
({ dependencies }) => this._reply(sock, m.id, 'result', dependencies),
|
||||
handleError,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'buildBundle':
|
||||
this._packagerServer.buildBundle(m.data).then(
|
||||
(result) => this._reply(sock, m.id, 'result', result),
|
||||
handleError,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'buildPrepackBundle':
|
||||
this._packagerServer.buildPrepackBundle(m.data).then(
|
||||
(result) => this._reply(sock, m.id, 'result', result),
|
||||
handleError,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'getOrderedDependencyPaths':
|
||||
this._packagerServer.getOrderedDependencyPaths(m.data).then(
|
||||
(dependencies) => this._reply(sock, m.id, 'result', dependencies),
|
||||
handleError,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
this._reply(sock, m.id, 'error', 'Unknown message type: ' + m.type);
|
||||
}
|
||||
}
|
||||
|
||||
_reply(sock, id, type, data) {
|
||||
debug('request finished', type);
|
||||
|
||||
try {
|
||||
data = toJSON(data);
|
||||
} catch (e) {
|
||||
console.error('SocketServer exception:', e);
|
||||
}
|
||||
|
||||
sock.write(bser.dumpToBuffer({
|
||||
id,
|
||||
type,
|
||||
data,
|
||||
}));
|
||||
|
||||
// Debounce the kill timer to make sure all the bytes are sent through
|
||||
// the socket and the client has time to fully finish and disconnect.
|
||||
this._dieEventually();
|
||||
}
|
||||
|
||||
_dieEventually(delay = MAX_IDLE_TIME) {
|
||||
clearTimeout(this._deathTimer);
|
||||
this._deathTimer = setTimeout(() => {
|
||||
this._server.getConnections((error, numConnections) => {
|
||||
// error is passed when connection count is below 0
|
||||
if (error || numConnections <= 0) {
|
||||
debug('server dying', process.pid);
|
||||
process.exit();
|
||||
}
|
||||
this._dieEventually();
|
||||
});
|
||||
}, delay);
|
||||
}
|
||||
|
||||
static listenOnServerIPCMessages() {
|
||||
process.on('message', (message) => {
|
||||
if (!(message && message.type && message.type === 'createSocketServer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug('server got ipc message', message);
|
||||
|
||||
const {options, sockPath} = message.data;
|
||||
// regexp doesn't naturally serialize to json.
|
||||
options.blacklistRE = new RegExp(options.blacklistRE.source);
|
||||
|
||||
new SocketServer(sockPath, options).onReady().then(
|
||||
() => {
|
||||
debug('succesfully created server');
|
||||
process.send({ type: 'createdServer' });
|
||||
},
|
||||
error => {
|
||||
if (error.code === 'EADDRINUSE' || error.code === 'EEXIST') {
|
||||
// Server already listening, this may happen if multiple
|
||||
// clients where started in quick succussion (buck).
|
||||
process.send({ type: 'createdServer' });
|
||||
|
||||
// Kill this server because some other server with the same
|
||||
// config and socket already started.
|
||||
debug('server already started');
|
||||
setImmediate(() => process.exit());
|
||||
} else {
|
||||
debug('error creating server', error.code);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
).done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move this to bser code.
|
||||
function toJSON(object) {
|
||||
if (!(object && typeof object === 'object')) {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (object.toJSON) {
|
||||
return object.toJSON();
|
||||
}
|
||||
|
||||
for (var p in object) {
|
||||
object[p] = toJSON(object[p]);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
module.exports = SocketServer;
|
|
@ -1,113 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.setMock('worker-farm', function() { return () => {}; })
|
||||
.setMock('uglify-js')
|
||||
.mock('net')
|
||||
.dontMock('../SocketClient');
|
||||
|
||||
var Bundle = require('../../Bundler/Bundle');
|
||||
var SocketClient = require('../SocketClient');
|
||||
var bser = require('bser');
|
||||
var net = require('net');
|
||||
|
||||
describe('SocketClient', () => {
|
||||
let sock;
|
||||
let bunser;
|
||||
|
||||
beforeEach(() => {
|
||||
const {EventEmitter} = require.requireActual('events');
|
||||
sock = new EventEmitter();
|
||||
sock.write = jest.genMockFn();
|
||||
|
||||
net.connect.mockImpl(() => sock);
|
||||
|
||||
bunser = new EventEmitter();
|
||||
bser.BunserBuf.mockImpl(() => bunser);
|
||||
bser.dumpToBuffer.mockImpl((a) => a);
|
||||
|
||||
Bundle.fromJSON.mockImpl((a) => a);
|
||||
});
|
||||
|
||||
pit('create a connection', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
return client.onReady().then(c => {
|
||||
expect(c).toBe(client);
|
||||
expect(net.connect).toBeCalledWith('/sock');
|
||||
});
|
||||
});
|
||||
|
||||
pit('buildBundle', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
const options = { entryFile: '/main' };
|
||||
|
||||
const promise = client.buildBundle(options);
|
||||
|
||||
expect(sock.write).toBeCalled();
|
||||
const message = sock.write.mock.calls[0][0];
|
||||
expect(message.type).toBe('buildBundle');
|
||||
expect(message.data).toEqual(options);
|
||||
expect(typeof message.id).toBe('string');
|
||||
|
||||
bunser.emit('value', {
|
||||
id: message.id,
|
||||
type: 'result',
|
||||
data: { bundle: 'foo' },
|
||||
});
|
||||
|
||||
return promise.then(bundle => expect(bundle).toEqual({ bundle: 'foo' }));
|
||||
});
|
||||
|
||||
pit('getDependencies', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
const main = '/main';
|
||||
|
||||
const promise = client.getDependencies(main);
|
||||
|
||||
expect(sock.write).toBeCalled();
|
||||
const message = sock.write.mock.calls[0][0];
|
||||
expect(message.type).toBe('getDependencies');
|
||||
expect(message.data).toEqual(main);
|
||||
expect(typeof message.id).toBe('string');
|
||||
|
||||
bunser.emit('value', {
|
||||
id: message.id,
|
||||
type: 'result',
|
||||
data: ['a', 'b', 'c'],
|
||||
});
|
||||
|
||||
return promise.then(result => expect(result).toEqual(['a', 'b', 'c']));
|
||||
});
|
||||
|
||||
pit('handle errors', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
const main = '/main';
|
||||
|
||||
const promise = client.getDependencies(main);
|
||||
|
||||
expect(sock.write).toBeCalled();
|
||||
const message = sock.write.mock.calls[0][0];
|
||||
expect(message.type).toBe('getDependencies');
|
||||
expect(message.data).toEqual(main);
|
||||
expect(typeof message.id).toBe('string');
|
||||
|
||||
bunser.emit('value', {
|
||||
id: message.id,
|
||||
type: 'error',
|
||||
data: 'some error'
|
||||
});
|
||||
|
||||
return promise.catch(m => expect(m.message).toContain('some error'));
|
||||
});
|
||||
});
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.setMock('uglify-js')
|
||||
.mock('child_process')
|
||||
.dontMock('../');
|
||||
|
||||
var SocketInterface = require('../');
|
||||
var SocketClient = require('../SocketClient');
|
||||
var childProcess = require('child_process');
|
||||
var fs = require('fs');
|
||||
|
||||
describe('SocketInterface', () => {
|
||||
describe('getOrCreateSocketFor', () => {
|
||||
pit('creates socket path by hashing options', () => {
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => true);
|
||||
fs.unlinkSync = jest.genMockFn();
|
||||
let callback;
|
||||
|
||||
childProcess.spawn.mockImpl(() => ({
|
||||
on: (event, cb) => callback = cb,
|
||||
send: (message) => {
|
||||
setImmediate(() => callback({ type: 'createdServer' }));
|
||||
},
|
||||
unref: () => undefined,
|
||||
disconnect: () => undefined,
|
||||
}));
|
||||
|
||||
// Check that given two equivelant server options, we end up with the same
|
||||
// socket path.
|
||||
const options1 = { projectRoots: ['/root'], transformModulePath: '/root/foo' };
|
||||
const options2 = { transformModulePath: '/root/foo', projectRoots: ['/root'] };
|
||||
const options3 = { projectRoots: ['/root', '/root2'] };
|
||||
|
||||
return SocketInterface.getOrCreateSocketFor(options1).then(() => {
|
||||
expect(SocketClient.create).toBeCalled();
|
||||
return SocketInterface.getOrCreateSocketFor(options2).then(() => {
|
||||
expect(SocketClient.create.mock.calls.length).toBe(2);
|
||||
expect(SocketClient.create.mock.calls[0]).toEqual(SocketClient.create.mock.calls[1]);
|
||||
return SocketInterface.getOrCreateSocketFor(options3).then(() => {
|
||||
expect(SocketClient.create.mock.calls.length).toBe(3);
|
||||
expect(SocketClient.create.mock.calls[1]).not.toEqual(SocketClient.create.mock.calls[2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('should fork a server', () => {
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => false);
|
||||
fs.unlinkSync = jest.genMockFn();
|
||||
let sockPath;
|
||||
let callback;
|
||||
|
||||
childProcess.spawn.mockImpl(() => ({
|
||||
on: (event, cb) => callback = cb,
|
||||
send: (message) => {
|
||||
expect(message.type).toBe('createSocketServer');
|
||||
expect(message.data.options).toEqual({ projectRoots: ['/root'] });
|
||||
expect(message.data.sockPath).toContain('react-packager');
|
||||
sockPath = message.data.sockPath;
|
||||
|
||||
setImmediate(() => callback({ type: 'createdServer' }));
|
||||
},
|
||||
unref: () => undefined,
|
||||
disconnect: () => undefined,
|
||||
}));
|
||||
|
||||
return SocketInterface.getOrCreateSocketFor({ projectRoots: ['/root'] })
|
||||
.then(() => {
|
||||
expect(SocketClient.create).toBeCalledWith(sockPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.autoMockOff();
|
||||
jest.setMock('uglify-js')
|
||||
.mock('net')
|
||||
.mock('fs')
|
||||
.mock('bser')
|
||||
.mock('../../Server');
|
||||
|
||||
var PackagerServer = require('../../Server');
|
||||
var SocketServer = require('../SocketServer');
|
||||
var bser = require('bser');
|
||||
var net = require('net');
|
||||
|
||||
describe('SocketServer', () => {
|
||||
let netServer;
|
||||
let bunser;
|
||||
let processOn;
|
||||
|
||||
beforeEach(() => {
|
||||
const {EventEmitter} = require.requireActual('events');
|
||||
netServer = new EventEmitter();
|
||||
netServer.listen = jest.genMockFn();
|
||||
net.createServer.mockImpl(() => netServer);
|
||||
|
||||
bunser = new EventEmitter();
|
||||
bser.BunserBuf.mockImpl(() => bunser);
|
||||
bser.dumpToBuffer.mockImpl((a) => a);
|
||||
|
||||
// Don't attach `process.on('exit')` handlers directly from SocketServer
|
||||
processOn = process.on;
|
||||
process.on = jest.genMockFn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.on = processOn;
|
||||
});
|
||||
|
||||
pit('create a server', () => {
|
||||
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
|
||||
netServer.emit('listening');
|
||||
return server.onReady().then(s => {
|
||||
expect(s).toBe(server);
|
||||
expect(netServer.listen).toBeCalledWith('/sock');
|
||||
});
|
||||
});
|
||||
|
||||
pit('handles getDependencies message', () => {
|
||||
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
|
||||
netServer.emit('listening');
|
||||
return server.onReady().then(() => {
|
||||
const sock = { on: jest.genMockFn(), write: jest.genMockFn() };
|
||||
netServer.emit('connection', sock);
|
||||
PackagerServer.prototype.getDependencies.mockImpl(
|
||||
() => Promise.resolve({ dependencies: ['a', 'b', 'c'] })
|
||||
);
|
||||
bunser.emit('value', { type: 'getDependencies', id: 1, data: '/main' });
|
||||
expect(PackagerServer.prototype.getDependencies).toBeCalledWith('/main');
|
||||
|
||||
// Run pending promises.
|
||||
return Promise.resolve().then(() => {
|
||||
expect(sock.write).toBeCalledWith(
|
||||
{ id: 1, type: 'result', data: ['a', 'b', 'c']}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('handles buildBundle message', () => {
|
||||
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
|
||||
netServer.emit('listening');
|
||||
return server.onReady().then(() => {
|
||||
const sock = { on: jest.genMockFn(), write: jest.genMockFn() };
|
||||
netServer.emit('connection', sock);
|
||||
PackagerServer.prototype.buildBundle.mockImpl(
|
||||
() => Promise.resolve({ bundle: 'foo' })
|
||||
);
|
||||
bunser.emit(
|
||||
'value',
|
||||
{ type: 'buildBundle', id: 1, data: { options: 'bar' } }
|
||||
);
|
||||
expect(PackagerServer.prototype.buildBundle).toBeCalledWith(
|
||||
{ options: 'bar' }
|
||||
);
|
||||
|
||||
// Run pending promises.
|
||||
return Promise.resolve().then(() => {
|
||||
expect(sock.write).toBeCalledWith(
|
||||
{ id: 1, type: 'result', data: { bundle: 'foo' }}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,146 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Promise = require('promise');
|
||||
const SocketClient = require('./SocketClient');
|
||||
const SocketServer = require('./SocketServer');
|
||||
const crypto = require('crypto');
|
||||
const debug = require('debug')('ReactNativePackager:SocketInterface');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000;
|
||||
|
||||
const SocketInterface = {
|
||||
getOrCreateSocketFor(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('md5');
|
||||
Object.keys(options).sort().forEach(key => {
|
||||
const value = options[key];
|
||||
if (value) {
|
||||
hash.update(
|
||||
options[key] != null && typeof value === 'string'
|
||||
? value
|
||||
: JSON.stringify(value)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let sockPath = path.join(
|
||||
tmpdir,
|
||||
'react-packager-' + hash.digest('hex')
|
||||
);
|
||||
if (process.platform === 'win32'){
|
||||
// on Windows, use a named pipe, convert sockPath into a valid pipe name
|
||||
// based on https://gist.github.com/domenic/2790533
|
||||
sockPath = sockPath.replace(/^\//, '')
|
||||
sockPath = sockPath.replace(/\//g, '-')
|
||||
sockPath = '\\\\.\\pipe\\' + sockPath
|
||||
}
|
||||
|
||||
if (existsSync(sockPath)) {
|
||||
var sock = net.connect(sockPath);
|
||||
sock.on('connect', () => {
|
||||
SocketClient.create(sockPath).then(
|
||||
client => {
|
||||
sock.end();
|
||||
resolve(client);
|
||||
},
|
||||
error => {
|
||||
sock.end();
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
sock.on('error', (e) => {
|
||||
try {
|
||||
debug('deleting socket for not responding', sockPath);
|
||||
fs.unlinkSync(sockPath);
|
||||
} catch (err) {
|
||||
// Another client might have deleted it first.
|
||||
}
|
||||
createServer(resolve, reject, options, sockPath);
|
||||
});
|
||||
} else {
|
||||
createServer(resolve, reject, options, sockPath);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
listenOnServerMessages() {
|
||||
return SocketServer.listenOnServerIPCMessages();
|
||||
}
|
||||
};
|
||||
|
||||
function createServer(resolve, reject, options, sockPath) {
|
||||
const logPath = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
const timeout = setTimeout(
|
||||
() => reject(
|
||||
new Error(
|
||||
'Took too long to start server. Server logs: \n' +
|
||||
fs.readFileSync(logPath, 'utf8')
|
||||
)
|
||||
),
|
||||
CREATE_SERVER_TIMEOUT,
|
||||
);
|
||||
|
||||
const log = fs.openSync(logPath, 'a');
|
||||
|
||||
// Enable server debugging by default since it's going to a log file.
|
||||
const env = Object.assign({}, process.env);
|
||||
env.DEBUG = 'ReactNativePackager:SocketServer';
|
||||
|
||||
// We have to go through the main entry point to make sure
|
||||
// we go through the babel require hook.
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[path.join(__dirname, '..', '..', 'index.js')],
|
||||
{
|
||||
detached: true,
|
||||
env: env,
|
||||
stdio: ['ipc', log, log]
|
||||
}
|
||||
);
|
||||
|
||||
child.unref();
|
||||
|
||||
child.on('message', m => {
|
||||
if (m && m.type && m.type === 'createdServer') {
|
||||
clearTimeout(timeout);
|
||||
child.disconnect();
|
||||
|
||||
resolve(SocketClient.create(sockPath));
|
||||
}
|
||||
});
|
||||
|
||||
if (options.blacklistRE) {
|
||||
options.blacklistRE = { source: options.blacklistRE.source };
|
||||
}
|
||||
|
||||
child.send({
|
||||
type: 'createSocketServer',
|
||||
data: { sockPath, options }
|
||||
});
|
||||
}
|
||||
|
||||
function existsSync(filename) {
|
||||
try {
|
||||
fs.accessSync(filename);
|
||||
return true;
|
||||
} catch(ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SocketInterface;
|
Loading…
Reference in New Issue