'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.startAsync = exports.createAuthenticationContextAsync = undefined;

var _extends2 = require('babel-runtime/helpers/extends');

var _extends3 = _interopRequireDefault(_extends2);

var _promise = require('babel-runtime/core-js/promise');

var _promise2 = _interopRequireDefault(_promise);

var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');

var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);

let generateSecureRandomTokenAsync = (() => {
  var _ref = (0, _asyncToGenerator3.default)(function* () {
    return new _promise2.default(function (resolve, reject) {
      _crypto2.default.randomBytes(32, function (error, buffer) {
        if (error) reject(error);else resolve(_base64url2.default.fromBase64(buffer.toString('base64')));
      });
    });
  });

  return function generateSecureRandomTokenAsync() {
    return _ref.apply(this, arguments);
  };
})();

let createAuthenticationContextAsync = exports.createAuthenticationContextAsync = (() => {
  var _ref2 = (0, _asyncToGenerator3.default)(function* ({ port }) {
    const clientAuthenticationToken = yield generateSecureRandomTokenAsync();
    const endpointUrlToken = yield generateSecureRandomTokenAsync();
    const graphQLEndpointPath = `/${endpointUrlToken}/graphql`;
    const hostname = `localhost:${port}`;
    const webSocketGraphQLUrl = `ws://${hostname}${graphQLEndpointPath}`;
    const allowedOrigin = `http://${hostname}`;
    return {
      clientAuthenticationToken,
      graphQLEndpointPath,
      webSocketGraphQLUrl,
      allowedOrigin,
      requestHandler: function requestHandler(request, response) {
        response.json({ webSocketGraphQLUrl, clientAuthenticationToken });
      }
    };
  });

  return function createAuthenticationContextAsync(_x) {
    return _ref2.apply(this, arguments);
  };
})();

let startAsync = exports.startAsync = (() => {
  var _ref3 = (0, _asyncToGenerator3.default)(function* (projectDir) {
    const port = yield (0, _freeportAsync2.default)(19002);
    const server = (0, _express2.default)();

    const authenticationContext = yield createAuthenticationContextAsync({ port });
    const webSocketGraphQLUrl = authenticationContext.webSocketGraphQLUrl,
          clientAuthenticationToken = authenticationContext.clientAuthenticationToken;

    server.get('/dev-tools-info', authenticationContext.requestHandler);
    server.use('/_next', _express2.default.static(_path2.default.join(__dirname, '../client/_next'), {
      // All paths in the _next folder include hashes, so they can be cached more aggressively.
      immutable: true,
      maxAge: '1y',
      setHeaders
    }));
    server.use(_express2.default.static(_path2.default.join(__dirname, '../client'), { setHeaders }));

    const httpServer = _http2.default.createServer(server);
    yield new _promise2.default(function (resolve, reject) {
      httpServer.once('error', reject);
      httpServer.once('listening', resolve);
      httpServer.listen(port, 'localhost');
    });
    startGraphQLServer(projectDir, httpServer, authenticationContext);
    yield _xdl.ProjectSettings.setPackagerInfoAsync(projectDir, { devToolsPort: port });
    return `http://localhost:${port}`;
  });

  return function startAsync(_x2) {
    return _ref3.apply(this, arguments);
  };
})();

exports.startGraphQLServer = startGraphQLServer;

var _xdl = require('@expo/xdl');

var _subscriptionsTransportWs = require('subscriptions-transport-ws');

var _graphql = require('graphql');

var graphql = _interopRequireWildcard(_graphql);

var _express = require('express');

var _express2 = _interopRequireDefault(_express);

var _freeportAsync = require('freeport-async');

var _freeportAsync2 = _interopRequireDefault(_freeportAsync);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _http = require('http');

var _http2 = _interopRequireDefault(_http);

var _crypto = require('crypto');

var _crypto2 = _interopRequireDefault(_crypto);

var _base64url = require('base64url');

var _base64url2 = _interopRequireDefault(_base64url);

var _AsyncIterableRingBuffer = require('./graphql/AsyncIterableRingBuffer');

var _AsyncIterableRingBuffer2 = _interopRequireDefault(_AsyncIterableRingBuffer);

var _GraphQLSchema = require('./graphql/GraphQLSchema');

var _GraphQLSchema2 = _interopRequireDefault(_GraphQLSchema);

var _createContext = require('./graphql/createContext');

var _createContext2 = _interopRequireDefault(_createContext);

var _Issues = require('./graphql/Issues');

var _Issues2 = _interopRequireDefault(_Issues);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const serverStartTimeUTCString = new Date().toUTCString();

function setHeaders(res) {
  // Set the Last-Modified header to server start time because otherwise it
  // becomes Sat, 26 Oct 1985 08:15:00 GMT for files installed from npm.
  res.setHeader('Last-Modified', serverStartTimeUTCString);
}

function startGraphQLServer(projectDir, httpServer, authenticationContext) {
  const layout = createLayout();
  const issues = new _Issues2.default();
  const messageBuffer = createMessageBuffer(projectDir, issues);
  _subscriptionsTransportWs.SubscriptionServer.create({
    schema: _GraphQLSchema2.default,
    execute: graphql.execute,
    subscribe: graphql.subscribe,
    onOperation: (operation, params) => (0, _extends3.default)({}, params, {
      context: (0, _createContext2.default)({
        projectDir,
        messageBuffer,
        layout,
        issues
      })
    }),
    onConnect: connectionParams => {
      if (!connectionParams.clientAuthenticationToken || connectionParams.clientAuthenticationToken !== authenticationContext.clientAuthenticationToken) {
        throw new Error('Dev Tools API authentication failed.');
      }
      return true;
    }
  }, {
    server: httpServer,
    path: authenticationContext.graphQLEndpointPath,
    verifyClient: info => {
      return info.origin === authenticationContext.allowedOrigin;
    }
  });
}

function createLayout() {
  let layout = {
    selected: null,
    sources: null,
    sourceLastReads: {}
  };
  return {
    get() {
      return layout;
    },
    set(newLayout) {
      layout = (0, _extends3.default)({}, layout, newLayout);
    },
    setLastRead(sourceId, lastReadCursor) {
      layout.sourceLastReads[sourceId] = lastReadCursor;
    }
  };
}

function createMessageBuffer(projectRoot, issues) {
  const buffer = new _AsyncIterableRingBuffer2.default(10000);

  // eslint-disable-next-line no-new
  new _xdl.PackagerLogsStream({
    projectRoot,
    updateLogs: updater => {
      const chunks = updater([]);
      chunks.forEach(chunk => {
        if (chunk.issueId) {
          if (chunk.issueCleared) {
            issues.clearIssue(chunk.issueId);
          } else {
            issues.addIssue(chunk.issueId, chunk);
          }
          return;
        }
        buffer.push({
          type: 'ADDED',
          sourceId: _createContext.PROCESS_SOURCE.id,
          node: chunk
        });
      });
    },
    onStartBuildBundle: chunk => {
      buffer.push({
        type: 'ADDED',
        sourceId: _createContext.PROCESS_SOURCE.id,
        node: (0, _extends3.default)({}, chunk, {
          progress: 0,
          duration: 0
        })
      });
    },
    onProgressBuildBundle: (percentage, start, chunk) => {
      buffer.push({
        type: 'UPDATED',
        sourceId: _createContext.PROCESS_SOURCE.id,
        node: (0, _extends3.default)({}, chunk, {
          progress: percentage,
          duration: new Date() - (start || new Date())
        })
      });
    },
    onFinishBuildBundle: (error, start, end, chunk) => {
      buffer.push({
        type: 'UPDATED',
        sourceId: _createContext.PROCESS_SOURCE.id,
        node: (0, _extends3.default)({}, chunk, {
          error,
          duration: end - (start || new Date())
        })
      });
    }
  });

  // needed for validation logging to function
  _xdl.ProjectUtils.attachLoggerStream(projectRoot, {
    stream: {
      write: chunk => {
        if (chunk.tag === 'device') {
          buffer.push({
            type: 'ADDED',
            sourceId: chunk.deviceId,
            node: chunk
          });
        }
      }
    },
    type: 'raw'
  });

  _xdl.Logger.global.addStream({
    stream: {
      write: chunk => {
        buffer.push({
          type: 'ADDED',
          sourceId: _createContext.PROCESS_SOURCE.id,
          node: chunk
        });
      }
    },
    type: 'raw'
  });

  return buffer;
}