<commit_before>import { parse, nodes } from "lively.ast";
var {funcCall, member, literal} = nodes;
import { evalCodeTransform, evalCodeTransformOfSystemRegisterSetters } from "lively.vm";
import { arr, string, properties, classHelper } from "lively.lang";
import module from "./module.js";
import { install as installHook, remove as removeHook, isInstalled as isHookInstalled } from "./hooks.js";

var isNode = System.get("@system-env").node;

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// helpers
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

function canonicalURL(url) {
  // removes double slashes, doesn't resolve relative parts yet
  var m = url.match(/([^:]+:\/\/)(.*)/);
  if (m) {
    var protocol = m[1];
    url = m[2];
  }
  url = url.replace(/([^:])\/[\/]+/g, "$1/");
  return (protocol || "") + url;
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// code instrumentation
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

var node_modulesDir = System.decanonicalize("lively.modules/node_modules/");

var exceptions = [
      // id => id.indexOf(resolve("node_modules/")) > -1,
      // id => canonicalURL(id).indexOf(node_modulesDir) > -1,
      id => string.include(id, "acorn/src"),
      id => string.include(id, "babel-core/browser.js") || string.include(id, "system.src.js") || string.include(id, "systemjs-plugin-babel"),
      // id => lang.string.include(id, "lively.ast.es6.bundle.js"),
      id => id.slice(-3) !== ".js"
    ],
    pendingConfigs = [], configInitialized = false,
    esmFormatCommentRegExp = /['"]format (esm|es6)['"];/,
    cjsFormatCommentRegExp = /['"]format cjs['"];/,
    // Stolen from SystemJS
    esmRegEx = /(^\s*|[}\);\n]\s*)(import\s+(['"]|(\*\s+as\s+)?[^"'\(\)\n;]+\s+from\s+['"]|\{)|export\s+\*\s+from\s+["']|export\s+(\{|default|function|class|var|const|let|async\s+function))/;

function getExceptions() { return exceptions; }
function setExceptions(v) { return exceptions = v; }

function prepareCodeForCustomCompile(source, fullname, env, debug) {
  source = String(source);
  var tfmOptions = {
        topLevelVarRecorder: env.recorder,
        varRecorderName: env.recorderName,
        dontTransform: env.dontTransform,
        recordGlobals: true,
        keepPreviouslyDeclaredValues: true,
        currentModuleAccessor: funcCall(
                                member(
                                  funcCall(member("System", "get"), literal("@lively-env")),
                                  "moduleEnv"),
                                literal(fullname))

      },
      isGlobal = env.recorderName === "System.global",
      header = (debug ? `console.log("[lively.modules] executing module ${fullname}");\n` : ""),
      footer = "";

  if (isGlobal) {
    // FIXME how to update exports in that case?
  } else {
    header += `var ${env.recorderName} = System.get("@lively-env").moduleEnv("${fullname}").recorder;`;
    footer += `\nSystem.get("@lively-env").evaluationDone("${fullname}");`
  }

  try {
    var rewrittenSource = header + evalCodeTransform(source, tfmOptions) + footer;
    if (debug && typeof $morph !== "undefined" && $morph("log")) $morph("log").textString = rewrittenSource;
    return rewrittenSource;
  } catch (e) {
    console.error("Error in prepareCodeForCustomCompile", e.stack);
    return source;
  }
}

function prepareTranslatedCodeForSetterCapture(source, fullname, env, debug) {
  source = String(source);
  var tfmOptions = {
        topLevelVarRecorder: env.recorder,
        varRecorderName: env.recorderName,
        dontTransform: env.dontTransform,
        recordGlobals: true,
        currentModuleAccessor: funcCall(
                                member(
                                  funcCall(member("System", "get"), literal("@lively-env")),
                                  "moduleEnv"),
                                literal(fullname))
      },
      isGlobal = env.recorderName === "System.global";

  try {
    var rewrittenSource = evalCodeTransformOfSystemRegisterSetters(source, tfmOptions);
    if (debug && typeof $morph !== "undefined" && $morph("log")) $morph("log").textString += rewrittenSource;
    return rewrittenSource;
  } catch (e) {
    console.error("Error in prepareTranslatedCodeForSetterCapture", e.stack);
    return source;
  }
}

function getCachedNodejsModule(System, load) {
  // On nodejs we might run alongside normal node modules. To not load those
  // twice we have this little hack...
  try {
    var Module = System._nodeRequire("module").Module,
        id = Module._resolveFilename(load.name.replace(/^file:\/\//, "")),
        nodeModule = Module._cache[id];
    return nodeModule;
  } catch (e) {
    System.debug && console.log("[lively.modules getCachedNodejsModule] %s unknown to nodejs", load.name);
  }
  return null;
}

function addNodejsWrapperSource(System, load) {
  // On nodejs we might run alongside normal node modules. To not load those
  // twice we have this little hack...
  var m = getCachedNodejsModule(System, load);
  if (m) {
    load.metadata.format = 'esm';
    load.source = `var exports = System._nodeRequire('${m.id}'); export default exports;\n`
                + properties.allOwnPropertiesOrFunctions(m.exports).map(k =>
                    classHelper.isValidIdentifier(k) ?
                      `export var ${k} = exports['${k}'];` :
                      `/*ignoring export "${k}" b/c it is not a valid identifier*/`).join("\n");
    System.debug && console.log("[lively.modules customTranslate] loading %s from nodejs module cache", load.name);
    return true;
  }
  System.debug && console.log("[lively.modules customTranslate] %s not yet in nodejs module cache", load.name);
  return false;
}

function customTranslate(proceed, load) {
  // load like
  // {
  //   address: "file:///Users/robert/Lively/lively-dev/lively.vm/tests/test-resources/some-es6-module.js",
  //   name: "file:///Users/robert/Lively/lively-dev/lively.vm/tests/test-resources/some-es6-module.js",
  //   metadata: { deps: [/*...*/], entry: {/*...*/}, format: "esm", sourceMap: ... },
  //   source: "..."
  // }

  var System = this, debug = System.debug;

  if (exceptions.some(exc => exc(load.name))) {
    debug && console.log("[lively.modules customTranslate ignoring] %s", load.name);
    return proceed(load);
  }
  if (isNode && addNodejsWrapperSource(System, load)) {
    debug && console.log("[lively.modules] loaded %s from nodejs cache", load.name)
    return proceed(load);
  }

  var start = Date.now();

  var isEsm = load.metadata.format == 'esm' || load.metadata.format == 'es6'
           || (!load.metadata.format && esmFormatCommentRegExp.test(load.source.slice(0,5000)))
           || (!load.metadata.format && !cjsFormatCommentRegExp.test(load.source.slice(0,5000)) && esmRegEx.test(load.source)),
      isCjs = load.metadata.format == 'cjs',
      isGlobal = load.metadata.format == 'global' || !load.metadata.format,
      env = module(System, load.name).env(),
      instrumented = false;

  if (isEsm) {
    load.metadata.format = "esm";
    load.source = prepareCodeForCustomCompile(load.source, load.name, env, debug);
    load.metadata["lively.modules instrumented"] = true;
    instrumented = true;
    debug && console.log("[lively.modules] loaded %s as es6 module", load.name)
    // debug && console.log(load.source)

  } else if (isCjs && isNode) {
    load.metadata.format = "cjs";
    var id = cjs.resolve(load.address.replace(/^file:\/\//, ""));
    load.source = cjs._prepareCodeForCustomCompile(load.source, id, cjs.envFor(id), debug);
    load.metadata["lively.modules instrumented"] = true;
    instrumented = true;
    debug && console.log("[lively.modules] loaded %s as instrumented cjs module", load.name)
    // console.log("[lively.modules] no rewrite for cjs module", load.name)

  } else if (load.metadata.format === "global") {
    env.recorderName = "System.global";
    env.recorder = System.global;
    load.metadata.format = "global";
    load.source = prepareCodeForCustomCompile(load.source, load.name, env, debug);
    load.metadata["lively.modules instrumented"] = true;
    instrumented = true;
    debug && console.log("[lively.modules] loaded %s as instrumented global module", load.name)
  }

  if (!instrumented) {
    debug && console.log("[lively.modules] customTranslate ignoring %s b/c don't know how to handle format %s", load.name, load.metadata.format);
  }

  return proceed(load).then(translated => {
    if (translated.indexOf("System.register(") === 0) {
      debug && console.log("[lively.modules customTranslate] Installing System.register setter captures for %s", load.name);
      translated = prepareTranslatedCodeForSetterCapture(translated, load.name, env, debug);
    }

    debug && console.log("[lively.modules customTranslate] done %s after %sms", load.name, Date.now()-start);
    return translated;
  });
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// Functions below are for re-loading modules from change.js. We typically
// start with a load object that skips the normalize / fetch step. Since we need
// to jumo in the "middle" of the load process and SystemJS does not provide an
// interface to this, we need to invoke the translate / instantiate / execute
// manually
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

function instrumentSourceOfEsmModuleLoad(System, load) {
  // brittle!
  // The result of System.translate is source code for a call to
  // System.register that can't be run standalone. We parse the necessary
  // details from it that we will use to re-define the module
  // (dependencies, setters, execute)
  // Note: this only works for esm modules!

  return System.translate(load).then(translated => {
    // translated looks like
    // (function(__moduleName){System.register(["./some-es6-module.js", ...], function (_export) {
    //   "use strict";
    //   var x, z, y;
    //   return {
    //     setters: [function (_someEs6ModuleJs) { ... }],
    //     execute: function () {...}
    //   };
    // });

    var parsed            = parse(translated),
        registerCall      = parsed.body[0].expression,
        depNames          = arr.pluck(registerCall["arguments"][0].elements, "value"),
        declareFuncNode   = registerCall["arguments"][1],
        declareFuncSource = translated.slice(declareFuncNode.start, declareFuncNode.end),
        declare           = eval(`var __moduleName = "${load.name}";(${declareFuncSource});\n//@ sourceURL=${load.name}\n`);

    if (System.debug && typeof $morph !== "undefined" && $morph("log"))
      $morph("log").textString = declare;

    return {localDeps: depNames, declare: declare};
  });
}

function old_instrumentSourceOfEsmModuleLoad(System, load) {
  // brittle!
  // The result of System.translate is source code for a call to
  // System.register that can't be run standalone. We parse the necessary
  // details from it that we will use to re-define the module
  // (dependencies, setters, execute)
  // Note: this only works for esm modules!

  return System.translate(load).then(translated => {
    // translated looks like
    // (function(__moduleName){System.register(["./some-es6-module.js", ...], function (_export) {
    //   "use strict";
    //   var x, z, y;
    //   return {
    //     setters: [function (_someEs6ModuleJs) { ... }],
    //     execute: function () {...}
    //   };
    // });

    var parsed            = parse(translated),
        call              = parsed.body[0].expression,
        moduleName        = call.arguments[0].value,
        registerCall      = call.callee.body.body[0].expression,
        depNames          = arr.pluck(registerCall["arguments"][0].elements, "value"),
        declareFuncNode   = call.callee.body.body[0].expression["arguments"][1],
        declareFuncSource = translated.slice(declareFuncNode.start, declareFuncNode.end),
        declare           = eval(`var __moduleName = "${moduleName}";(${declareFuncSource});\n//@ sourceURL=${moduleName}\n`);

    if (System.debug && typeof $morph !== "undefined" && $morph("log"))
      $morph("log").textString = declare;

    return {localDeps: depNames, declare: declare};
  });
}

function instrumentSourceOfGlobalModuleLoad(System, load) {
  return System.translate(load).then(translated => {
    // return {localDeps: depNames, declare: declare};
    return {translated: translated};
  });
}

function wrapModuleLoad(System) {
  if (isHookInstalled(System, "translate", "lively_modules_translate_hook")) return;
  installHook(
    System, "translate",
    function lively_modules_translate_hook(proceed, load) { return customTranslate.call(System, proceed, load); });
}

function unwrapModuleLoad(System) {
  removeHook(System, "translate", "lively_modules_translate_hook");
}

export {
  wrapModuleLoad, unwrapModuleLoad,
  getExceptions, setExceptions,
  instrumentSourceOfEsmModuleLoad, instrumentSourceOfGlobalModuleLoad
}
<commit_msg>@ sourceURL => # sourceURL<commit_after>import { parse, nodes } from "lively.ast";
var {funcCall, member, literal} = nodes;
import { evalCodeTransform, evalCodeTransformOfSystemRegisterSetters } from "lively.vm";
import { arr, string, properties, classHelper } from "lively.lang";
import module from "./module.js";
import { install as installHook, remove as removeHook, isInstalled as isHookInstalled } from "./hooks.js";

var isNode = System.get("@system-env").node;

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// helpers
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

function canonicalURL(url) {
  // removes double slashes, doesn't resolve relative parts yet
  var m = url.match(/([^:]+:\/\/)(.*)/);
  if (m) {
    var protocol = m[1];
    url = m[2];
  }
  url = url.replace(/([^:])\/[\/]+/g, "$1/");
  return (protocol || "") + url;
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// code instrumentation
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

var node_modulesDir = System.decanonicalize("lively.modules/node_modules/");

var exceptions = [
      // id => id.indexOf(resolve("node_modules/")) > -1,
      // id => canonicalURL(id).indexOf(node_modulesDir) > -1,
      id => string.include(id, "acorn/src"),
      id => string.include(id, "babel-core/browser.js") || string.include(id, "system.src.js") || string.include(id, "systemjs-plugin-babel"),
      // id => lang.string.include(id, "lively.ast.es6.bundle.js"),
      id => id.slice(-3) !== ".js"
    ],
    pendingConfigs = [], configInitialized = false,
    esmFormatCommentRegExp = /['"]format (esm|es6)['"];/,
    cjsFormatCommentRegExp = /['"]format cjs['"];/,
    // Stolen from SystemJS
    esmRegEx = /(^\s*|[}\);\n]\s*)(import\s+(['"]|(\*\s+as\s+)?[^"'\(\)\n;]+\s+from\s+['"]|\{)|export\s+\*\s+from\s+["']|export\s+(\{|default|function|class|var|const|let|async\s+function))/;

function getExceptions() { return exceptions; }
function setExceptions(v) { return exceptions = v; }

function prepareCodeForCustomCompile(source, fullname, env, debug) {
  source = String(source);
  var tfmOptions = {
        topLevelVarRecorder: env.recorder,
        varRecorderName: env.recorderName,
        dontTransform: env.dontTransform,
        recordGlobals: true,
        keepPreviouslyDeclaredValues: true,
        currentModuleAccessor: funcCall(
                                member(
                                  funcCall(member("System", "get"), literal("@lively-env")),
                                  "moduleEnv"),
                                literal(fullname))

      },
      isGlobal = env.recorderName === "System.global",
      header = (debug ? `console.log("[lively.modules] executing module ${fullname}");\n` : ""),
      footer = "";

  if (isGlobal) {
    // FIXME how to update exports in that case?
  } else {
    header += `var ${env.recorderName} = System.get("@lively-env").moduleEnv("${fullname}").recorder;`;
    footer += `\nSystem.get("@lively-env").evaluationDone("${fullname}");`
  }

  try {
    var rewrittenSource = header + evalCodeTransform(source, tfmOptions) + footer;
    if (debug && typeof $morph !== "undefined" && $morph("log")) $morph("log").textString = rewrittenSource;
    return rewrittenSource;
  } catch (e) {
    console.error("Error in prepareCodeForCustomCompile", e.stack);
    return source;
  }
}

function prepareTranslatedCodeForSetterCapture(source, fullname, env, debug) {
  source = String(source);
  var tfmOptions = {
        topLevelVarRecorder: env.recorder,
        varRecorderName: env.recorderName,
        dontTransform: env.dontTransform,
        recordGlobals: true,
        currentModuleAccessor: funcCall(
                                member(
                                  funcCall(member("System", "get"), literal("@lively-env")),
                                  "moduleEnv"),
                                literal(fullname))
      },
      isGlobal = env.recorderName === "System.global";

  try {
    var rewrittenSource = evalCodeTransformOfSystemRegisterSetters(source, tfmOptions);
    if (debug && typeof $morph !== "undefined" && $morph("log")) $morph("log").textString += rewrittenSource;
    return rewrittenSource;
  } catch (e) {
    console.error("Error in prepareTranslatedCodeForSetterCapture", e.stack);
    return source;
  }
}

function getCachedNodejsModule(System, load) {
  // On nodejs we might run alongside normal node modules. To not load those
  // twice we have this little hack...
  try {
    var Module = System._nodeRequire("module").Module,
        id = Module._resolveFilename(load.name.replace(/^file:\/\//, "")),
        nodeModule = Module._cache[id];
    return nodeModule;
  } catch (e) {
    System.debug && console.log("[lively.modules getCachedNodejsModule] %s unknown to nodejs", load.name);
  }
  return null;
}

function addNodejsWrapperSource(System, load) {
  // On nodejs we might run alongside normal node modules. To not load those
  // twice we have this little hack...
  var m = getCachedNodejsModule(System, load);
  if (m) {
    load.metadata.format = 'esm';
    load.source = `var exports = System._nodeRequire('${m.id}'); export default exports;\n`
                + properties.allOwnPropertiesOrFunctions(m.exports).map(k =>
                    classHelper.isValidIdentifier(k) ?
                      `export var ${k} = exports['${k}'];` :
                      `/*ignoring export "${k}" b/c it is not a valid identifier*/`).join("\n");
    System.debug && console.log("[lively.modules customTranslate] loading %s from nodejs module cache", load.name);
    return true;
  }
  System.debug && console.log("[lively.modules customTranslate] %s not yet in nodejs module cache", load.name);
  return false;
}

function customTranslate(proceed, load) {
  // load like
  // {
  //   address: "file:///Users/robert/Lively/lively-dev/lively.vm/tests/test-resources/some-es6-module.js",
  //   name: "file:///Users/robert/Lively/lively-dev/lively.vm/tests/test-resources/some-es6-module.js",
  //   metadata: { deps: [/*...*/], entry: {/*...*/}, format: "esm", sourceMap: ... },
  //   source: "..."
  // }

  var System = this, debug = System.debug;

  if (exceptions.some(exc => exc(load.name))) {
    debug && console.log("[lively.modules customTranslate ignoring] %s", load.name);
    return proceed(load);
  }
  if (isNode && addNodejsWrapperSource(System, load)) {
    debug && console.log("[lively.modules] loaded %s from nodejs cache", load.name)
    return proceed(load);
  }

  var start = Date.now();

  var isEsm = load.metadata.format == 'esm' || load.metadata.format == 'es6'
           || (!load.metadata.format && esmFormatCommentRegExp.test(load.source.slice(0,5000)))
           || (!load.metadata.format && !cjsFormatCommentRegExp.test(load.source.slice(0,5000)) && esmRegEx.test(load.source)),
      isCjs = load.metadata.format == 'cjs',
      isGlobal = load.metadata.format == 'global' || !load.metadata.format,
      env = module(System, load.name).env(),
      instrumented = false;

  if (isEsm) {
    load.metadata.format = "esm";
    load.source = prepareCodeForCustomCompile(load.source, load.name, env, debug);
    load.metadata["lively.modules instrumented"] = true;
    instrumented = true;
    debug && console.log("[lively.modules] loaded %s as es6 module", load.name)
    // debug && console.log(load.source)

  } else if (isCjs && isNode) {
    load.metadata.format = "cjs";
    var id = cjs.resolve(load.address.replace(/^file:\/\//, ""));
    load.source = cjs._prepareCodeForCustomCompile(load.source, id, cjs.envFor(id), debug);
    load.metadata["lively.modules instrumented"] = true;
    instrumented = true;
    debug && console.log("[lively.modules] loaded %s as instrumented cjs module", load.name)
    // console.log("[lively.modules] no rewrite for cjs module", load.name)

  } else if (load.metadata.format === "global") {
    env.recorderName = "System.global";
    env.recorder = System.global;
    load.metadata.format = "global";
    load.source = prepareCodeForCustomCompile(load.source, load.name, env, debug);
    load.metadata["lively.modules instrumented"] = true;
    instrumented = true;
    debug && console.log("[lively.modules] loaded %s as instrumented global module", load.name)
  }

  if (!instrumented) {
    debug && console.log("[lively.modules] customTranslate ignoring %s b/c don't know how to handle format %s", load.name, load.metadata.format);
  }

  return proceed(load).then(translated => {
    if (translated.indexOf("System.register(") === 0) {
      debug && console.log("[lively.modules customTranslate] Installing System.register setter captures for %s", load.name);
      translated = prepareTranslatedCodeForSetterCapture(translated, load.name, env, debug);
    }

    debug && console.log("[lively.modules customTranslate] done %s after %sms", load.name, Date.now()-start);
    return translated;
  });
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// Functions below are for re-loading modules from change.js. We typically
// start with a load object that skips the normalize / fetch step. Since we need
// to jumo in the "middle" of the load process and SystemJS does not provide an
// interface to this, we need to invoke the translate / instantiate / execute
// manually
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

function instrumentSourceOfEsmModuleLoad(System, load) {
  // brittle!
  // The result of System.translate is source code for a call to
  // System.register that can't be run standalone. We parse the necessary
  // details from it that we will use to re-define the module
  // (dependencies, setters, execute)
  // Note: this only works for esm modules!

  return System.translate(load).then(translated => {
    // translated looks like
    // (function(__moduleName){System.register(["./some-es6-module.js", ...], function (_export) {
    //   "use strict";
    //   var x, z, y;
    //   return {
    //     setters: [function (_someEs6ModuleJs) { ... }],
    //     execute: function () {...}
    //   };
    // });

    var parsed            = parse(translated),
        registerCall      = parsed.body[0].expression,
        depNames          = arr.pluck(registerCall["arguments"][0].elements, "value"),
        declareFuncNode   = registerCall["arguments"][1],
        declareFuncSource = translated.slice(declareFuncNode.start, declareFuncNode.end),
        declare           = eval(`var __moduleName = "${load.name}";(${declareFuncSource});\n//# sourceURL=${load.name}\n`);

    if (System.debug && typeof $morph !== "undefined" && $morph("log"))
      $morph("log").textString = declare;

    return {localDeps: depNames, declare: declare};
  });
}

function old_instrumentSourceOfEsmModuleLoad(System, load) {
  // brittle!
  // The result of System.translate is source code for a call to
  // System.register that can't be run standalone. We parse the necessary
  // details from it that we will use to re-define the module
  // (dependencies, setters, execute)
  // Note: this only works for esm modules!

  return System.translate(load).then(translated => {
    // translated looks like
    // (function(__moduleName){System.register(["./some-es6-module.js", ...], function (_export) {
    //   "use strict";
    //   var x, z, y;
    //   return {
    //     setters: [function (_someEs6ModuleJs) { ... }],
    //     execute: function () {...}
    //   };
    // });

    var parsed            = parse(translated),
        call              = parsed.body[0].expression,
        moduleName        = call.arguments[0].value,
        registerCall      = call.callee.body.body[0].expression,
        depNames          = arr.pluck(registerCall["arguments"][0].elements, "value"),
        declareFuncNode   = call.callee.body.body[0].expression["arguments"][1],
        declareFuncSource = translated.slice(declareFuncNode.start, declareFuncNode.end),
        declare           = eval(`var __moduleName = "${moduleName}";(${declareFuncSource});\n//# sourceURL=${moduleName}\n`);

    if (System.debug && typeof $morph !== "undefined" && $morph("log"))
      $morph("log").textString = declare;

    return {localDeps: depNames, declare: declare};
  });
}

function instrumentSourceOfGlobalModuleLoad(System, load) {
  return System.translate(load).then(translated => {
    // return {localDeps: depNames, declare: declare};
    return {translated: translated};
  });
}

function wrapModuleLoad(System) {
  if (isHookInstalled(System, "translate", "lively_modules_translate_hook")) return;
  installHook(
    System, "translate",
    function lively_modules_translate_hook(proceed, load) { return customTranslate.call(System, proceed, load); });
}

function unwrapModuleLoad(System) {
  removeHook(System, "translate", "lively_modules_translate_hook");
}

export {
  wrapModuleLoad, unwrapModuleLoad,
  getExceptions, setExceptions,
  instrumentSourceOfEsmModuleLoad, instrumentSourceOfGlobalModuleLoad
}
<|endoftext|>


<commit_before>'use strict'

/**
 * adonis-framework
 * Copyright(c) 2015-2016 Harminder Virk
 * MIT Licensed
*/

const nodeReq = require('node-req')
const nodeCookie = require('node-cookie')
const File = require('../File')
const pathToRegexp = require('path-to-regexp')
const _ = require('lodash')
const CatLog = require('cat-log')
const log = new CatLog('adonis:framework')
const util = require('../../lib/util')

/**
 * @class  Request
 * @description instance is passed with every http request to
 * read request values in an unified way
 * @public
 */
class Request {

  constructor (request, response, Config) {
    this.request = request
    this.response = response
    this.config = Config
    this._body = {}
    this._files = []

    /**
     * secret to parse and decrypt cookies
     * @type {String}
     */
    this.secret = this.config.get('app.appKey')

    /**
     * holding references to cookies once they
     * have been parsed. It is required to
     * optimize performance as decrypting
     * is an expensive operation
     * @type {Object}
     */
    this.cookiesObject = {}

    /**
     * flag to find whether cookies have been
     * parsed once or not
     * @type {Boolean}
     */
    this.parsedCookies = false
  }

  /**
   * @description returns input value for a given
   * key from post and get values
   * @method input
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  input (key, defaultValue) {
    defaultValue = util.existy(defaultValue) ? defaultValue : null
    const input = this.all()
    return util.existy(input[key]) ? input[key] : defaultValue
  }

  /**
   * @description returns merged values from
   * get and post
   * @method all
   * @return {Object}
   * @public
   */
  all () {
    return _.merge(this.get(), this.post())
  }

  /**
   * @description returns all input values except defined
   * keys
   * @method except
   * @return {Object}
   * @public
   */
  except () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return _.omit(this.all(), args)
  }

  /**
   * @description returns values for defined keys only
   * @method only
   * @return {Object}
   * @public
   */
  only () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return _.pick(this.all(), args)
  }

  /**
   * @description returns query parameters
   * from request query
   * @method get
   * @return {Object}
   * @public
   */
  get () {
    return nodeReq.get(this.request)
  }

  /**
   * @description returns post body from request, BodyParser
   * middleware needs to be enabled for this to work
   * @method post
   * @return {Object}
   * @public
   */
  post () {
    return nodeReq.post(this.request) || {}
  }

  raw () {
    return this._raw
  }

  /**
   * @description returns header value for a given key
   * @method header
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  header (key) {
    return nodeReq.header(this.request, key)
  }

  /**
   * @description returns all request headers
   * @method headers
   * @return {Object}
   * @public
   */
  headers () {
    return nodeReq.headers(this.request)
  }

  /**
   * @description tells whether request is fresh or not by
   * checking Etag and expires header
   * @method fresh
   * @return {Boolean}
   * @public
   */
  fresh () {
    return nodeReq.fresh(this.request, this.response)
  }

  /**
   * @description opposite of fresh
   * @see  fresh
   * @method stale
   * @return {Boolean}
   * @public
   */
  stale () {
    return nodeReq.stale(this.request, this.response)
  }

  /**
   * @description returns most trusted ip address
   * @method ip
   * @return {String}
   * @public
   */
  ip () {
    return nodeReq.ip(this.request, this.config.get('app.http.trustProxy'))
  }

  /**
   * @description returns an array of ip addresses sorted from
   * most to least trusted
   * @method ips
   * @return {Array}
   * @public
   */
  ips () {
    return nodeReq.ips(this.request, this.config.get('app.http.trustProxy'))
  }

  /**
   * @description tells whether request is on https
   * or not
   * @method secure
   * @return {Boolean}
   * @public
   */
  secure () {
    return nodeReq.secure(this.request)
  }

  /**
   * @description returns a array of subdomains from url
   * @method subdomains
   * @return {Array}
   * @public
   */
  subdomains () {
    return nodeReq.subdomains(this.request, this.config.get('app.http.trustProxy'), this.config.get('app.http.subdomainOffset'))
  }

  /**
   * @description tells whether request is an ajax
   * request or not
   * @method ajax
   * @return {Boolean}
   * @public
   */
  ajax () {
    return nodeReq.ajax(this.request)
  }

  /**
   * @description tells whether request is pjax or
   * not based on X-PJAX header
   * @method pjax
   * @return {Boolean}
   */
  pjax () {
    return nodeReq.pjax(this.request)
  }

  /**
   * @description returns request hostname
   * @method hostname
   * @return {String}
   * @public
   */
  hostname () {
    return nodeReq.hostname(this.request, this.config.get('app.http.trustProxy'))
  }

  /**
   * @description returns request url without
   * query string
   * @method url
   * @return {String}
   * @public
   */
  url () {
    return nodeReq.url(this.request)
  }

  /**
   * @description returns request original Url
   * with query string
   * @method originalUrl
   * @return {String}
   * @public
   */
  originalUrl () {
    return nodeReq.originalUrl(this.request)
  }

  /**
   * @description tells whether request is of certain type
   * based upon Content-type header
   * @method is
   * @return {Boolean}
   * @public
   */
  is () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return nodeReq.is(this.request, args)
  }

  /**
   * @description returns the best response type to be accepted
   * using Accepts header
   * @method accepts
   * @return {String}
   * @public
   */
  accepts () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return nodeReq.accepts(this.request, args)
  }

  /**
   * @description returns request method or verb in HTTP terms
   * @method method
   * @return {String}
   * @public
   */
  method () {
    return nodeReq.method(this.request)
  }

  /**
   * @description returns cookie value for a given key
   * @method cookie
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  cookie (key) {
    const cookies = this.cookies()
    return cookies[key] || null
  }

  /**
   * @description returns all cookies associated to a
   * given request
   * @method cookies
   * @return {Object}
   * @public
   */
  cookies () {
    const secret = this.secret || null
    const decrypt = !!this.secret

    /**
     * avoiding re-parsing of cookies if done once
     */
    if (!this.parsedCookies) {
      this.cookiesObject = nodeCookie.parse(this.request, secret, decrypt)
      this.parsedCookies = true
    }

    return this.cookiesObject
  }

  /**
   * @description  return route param value for a given key
   * @method param
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  param (key, defaultValue) {
    defaultValue = util.existy(defaultValue) ? defaultValue : null
    return util.existy(this.params()[key]) ? this.params()[key] : defaultValue
  }

  /**
   * @description returns all route params
   * @method params
   * @return {Object}
   * @public
   */
  params () {
    return this._params || {}
  }

  /**
   * @description converts a file object to file instance
   * if already is not an instance
   * @method _toFileInstance
   * @param  {Object}        file
   * @return {Object}
   * @private
   */
  _toFileInstance (file) {
    if (!(file instanceof File)) {
      file = new File(file)
    }
    return file
  }

  /**
   * @description converts an uploaded file to file
   * instance
   * @method file
   * @param  {String} key
   * @return {Object}
   * @public
   */
  file (key) {
    /**
     * if requested file was not uploaded return an
     * empty instance of file object.
     */
    if (!this._files[key]) {
      return this._toFileInstance({})
    }

    /**
     * grabbing file from uploaded files and
     * converting them to file instance
     */
    const fileToReturn = this._files[key].toJSON()

    /**
     * if multiple file upload , convert of them to
     * file instance
     */
    if (_.isArray(fileToReturn)) {
      return _.map(fileToReturn, (file) => {
        return this._toFileInstance(file)
      })
    }
    return this._toFileInstance(fileToReturn)
  }

  /**
   * @description returns all uploded files by converting
   * them to file instances
   * @method files
   * @return {Array}
   * @public
   */
  files () {
    return _.map(this._files, (file, index) => {
      return this.file(index)
    })
  }

  /**
   * @description flash an object of messages to upcoming
   * request
   * @method flash
   * @param  {Object} values
   * @return {void}
   * @public
   */
  * flash (values) {
    if (typeof (values) !== 'object') {
      throw new Error('Flash values should be an object')
    }
    yield this.session.put('flash_messages', values)
  }

  /**
   * @description return values set via flash from
   * request session
   * @method old
   * @param  {String} key
   * @param  {Mixed} defaultValue
   * @return {Mixed}
   * @public
   */
  old (key, defaultValue) {
    if (!this._flash_messages) {
      log.warn('Make use of Flash middleware to enable flash messaging')
      this._flash_messages = {}
    }
    defaultValue = util.existy(defaultValue) ? defaultValue : null
    return util.existy(this._flash_messages[key]) ? this._flash_messages[key] : defaultValue
  }

  /**
   * @description flash all request input fields to
   * session flash
   * @method flashAll
   * @return {void}
   * @public
   */
  * flashAll () {
    yield this.flash(this.all())
  }

  /**
   * @description flash values of request keys from request
   * input field to session flash
   * @method flashOnly
   * @return {void}
   * @public
   */
  * flashOnly () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    yield this.flash(this.only(args))
  }

  /**
   * @description flash values of request to session flash
   * except defined keys
   * @method flashExcept
   * @return {void}
   * @public
   */
  * flashExcept () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    yield this.flash(this.except(args))
  }

  /**
   * @description tells whether a given pattern matches the
   * current url or not
   * @method match
   * @param  {String} pattern
   * @return {Boolean}
   * @public
   */
  match () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    const url = this.url()
    const pattern = pathToRegexp(args, [])
    return pattern.test(url)
  }

}

module.exports = Request
<commit_msg>refactor(request): removed raw method with new body parser<commit_after>'use strict'

/**
 * adonis-framework
 * Copyright(c) 2015-2016 Harminder Virk
 * MIT Licensed
*/

const nodeReq = require('node-req')
const nodeCookie = require('node-cookie')
const File = require('../File')
const pathToRegexp = require('path-to-regexp')
const _ = require('lodash')
const CatLog = require('cat-log')
const log = new CatLog('adonis:framework')
const util = require('../../lib/util')

/**
 * @class  Request
 * @description instance is passed with every http request to
 * read request values in an unified way
 * @public
 */
class Request {

  constructor (request, response, Config) {
    this.request = request
    this.response = response
    this.config = Config
    this._body = {}
    this._files = []

    /**
     * secret to parse and decrypt cookies
     * @type {String}
     */
    this.secret = this.config.get('app.appKey')

    /**
     * holding references to cookies once they
     * have been parsed. It is required to
     * optimize performance as decrypting
     * is an expensive operation
     * @type {Object}
     */
    this.cookiesObject = {}

    /**
     * flag to find whether cookies have been
     * parsed once or not
     * @type {Boolean}
     */
    this.parsedCookies = false
  }

  /**
   * @description returns input value for a given
   * key from post and get values
   * @method input
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  input (key, defaultValue) {
    defaultValue = util.existy(defaultValue) ? defaultValue : null
    const input = this.all()
    return util.existy(input[key]) ? input[key] : defaultValue
  }

  /**
   * @description returns merged values from
   * get and post
   * @method all
   * @return {Object}
   * @public
   */
  all () {
    return _.merge(this.get(), this.post())
  }

  /**
   * @description returns all input values except defined
   * keys
   * @method except
   * @return {Object}
   * @public
   */
  except () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return _.omit(this.all(), args)
  }

  /**
   * @description returns values for defined keys only
   * @method only
   * @return {Object}
   * @public
   */
  only () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return _.pick(this.all(), args)
  }

  /**
   * @description returns query parameters
   * from request query
   * @method get
   * @return {Object}
   * @public
   */
  get () {
    return nodeReq.get(this.request)
  }

  /**
   * @description returns post body from request, BodyParser
   * middleware needs to be enabled for this to work
   * @method post
   * @return {Object}
   * @public
   */
  post () {
    return nodeReq.post(this.request) || {}
  }

  /**
   * @description returns header value for a given key
   * @method header
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  header (key) {
    return nodeReq.header(this.request, key)
  }

  /**
   * @description returns all request headers
   * @method headers
   * @return {Object}
   * @public
   */
  headers () {
    return nodeReq.headers(this.request)
  }

  /**
   * @description tells whether request is fresh or not by
   * checking Etag and expires header
   * @method fresh
   * @return {Boolean}
   * @public
   */
  fresh () {
    return nodeReq.fresh(this.request, this.response)
  }

  /**
   * @description opposite of fresh
   * @see  fresh
   * @method stale
   * @return {Boolean}
   * @public
   */
  stale () {
    return nodeReq.stale(this.request, this.response)
  }

  /**
   * @description returns most trusted ip address
   * @method ip
   * @return {String}
   * @public
   */
  ip () {
    return nodeReq.ip(this.request, this.config.get('app.http.trustProxy'))
  }

  /**
   * @description returns an array of ip addresses sorted from
   * most to least trusted
   * @method ips
   * @return {Array}
   * @public
   */
  ips () {
    return nodeReq.ips(this.request, this.config.get('app.http.trustProxy'))
  }

  /**
   * @description tells whether request is on https
   * or not
   * @method secure
   * @return {Boolean}
   * @public
   */
  secure () {
    return nodeReq.secure(this.request)
  }

  /**
   * @description returns a array of subdomains from url
   * @method subdomains
   * @return {Array}
   * @public
   */
  subdomains () {
    return nodeReq.subdomains(this.request, this.config.get('app.http.trustProxy'), this.config.get('app.http.subdomainOffset'))
  }

  /**
   * @description tells whether request is an ajax
   * request or not
   * @method ajax
   * @return {Boolean}
   * @public
   */
  ajax () {
    return nodeReq.ajax(this.request)
  }

  /**
   * @description tells whether request is pjax or
   * not based on X-PJAX header
   * @method pjax
   * @return {Boolean}
   */
  pjax () {
    return nodeReq.pjax(this.request)
  }

  /**
   * @description returns request hostname
   * @method hostname
   * @return {String}
   * @public
   */
  hostname () {
    return nodeReq.hostname(this.request, this.config.get('app.http.trustProxy'))
  }

  /**
   * @description returns request url without
   * query string
   * @method url
   * @return {String}
   * @public
   */
  url () {
    return nodeReq.url(this.request)
  }

  /**
   * @description returns request original Url
   * with query string
   * @method originalUrl
   * @return {String}
   * @public
   */
  originalUrl () {
    return nodeReq.originalUrl(this.request)
  }

  /**
   * @description tells whether request is of certain type
   * based upon Content-type header
   * @method is
   * @return {Boolean}
   * @public
   */
  is () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return nodeReq.is(this.request, args)
  }

  /**
   * @description returns the best response type to be accepted
   * using Accepts header
   * @method accepts
   * @return {String}
   * @public
   */
  accepts () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    return nodeReq.accepts(this.request, args)
  }

  /**
   * @description returns request method or verb in HTTP terms
   * @method method
   * @return {String}
   * @public
   */
  method () {
    return nodeReq.method(this.request)
  }

  /**
   * @description returns cookie value for a given key
   * @method cookie
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  cookie (key) {
    const cookies = this.cookies()
    return cookies[key] || null
  }

  /**
   * @description returns all cookies associated to a
   * given request
   * @method cookies
   * @return {Object}
   * @public
   */
  cookies () {
    const secret = this.secret || null
    const decrypt = !!this.secret

    /**
     * avoiding re-parsing of cookies if done once
     */
    if (!this.parsedCookies) {
      this.cookiesObject = nodeCookie.parse(this.request, secret, decrypt)
      this.parsedCookies = true
    }

    return this.cookiesObject
  }

  /**
   * @description  return route param value for a given key
   * @method param
   * @param  {String} key
   * @return {Mixed}
   * @public
   */
  param (key, defaultValue) {
    defaultValue = util.existy(defaultValue) ? defaultValue : null
    return util.existy(this.params()[key]) ? this.params()[key] : defaultValue
  }

  /**
   * @description returns all route params
   * @method params
   * @return {Object}
   * @public
   */
  params () {
    return this._params || {}
  }

  /**
   * @description converts a file object to file instance
   * if already is not an instance
   * @method _toFileInstance
   * @param  {Object}        file
   * @return {Object}
   * @private
   */
  _toFileInstance (file) {
    if (!(file instanceof File)) {
      file = new File(file)
    }
    return file
  }

  /**
   * @description converts an uploaded file to file
   * instance
   * @method file
   * @param  {String} key
   * @return {Object}
   * @public
   */
  file (key) {
    /**
     * if requested file was not uploaded return an
     * empty instance of file object.
     */
    if (!this._files[key]) {
      return this._toFileInstance({})
    }

    /**
     * grabbing file from uploaded files and
     * converting them to file instance
     */
    const fileToReturn = this._files[key].toJSON()

    /**
     * if multiple file upload , convert of them to
     * file instance
     */
    if (_.isArray(fileToReturn)) {
      return _.map(fileToReturn, (file) => {
        return this._toFileInstance(file)
      })
    }
    return this._toFileInstance(fileToReturn)
  }

  /**
   * @description returns all uploded files by converting
   * them to file instances
   * @method files
   * @return {Array}
   * @public
   */
  files () {
    return _.map(this._files, (file, index) => {
      return this.file(index)
    })
  }

  /**
   * @description flash an object of messages to upcoming
   * request
   * @method flash
   * @param  {Object} values
   * @return {void}
   * @public
   */
  * flash (values) {
    if (typeof (values) !== 'object') {
      throw new Error('Flash values should be an object')
    }
    yield this.session.put('flash_messages', values)
  }

  /**
   * @description return values set via flash from
   * request session
   * @method old
   * @param  {String} key
   * @param  {Mixed} defaultValue
   * @return {Mixed}
   * @public
   */
  old (key, defaultValue) {
    if (!this._flash_messages) {
      log.warn('Make use of Flash middleware to enable flash messaging')
      this._flash_messages = {}
    }
    defaultValue = util.existy(defaultValue) ? defaultValue : null
    return util.existy(this._flash_messages[key]) ? this._flash_messages[key] : defaultValue
  }

  /**
   * @description flash all request input fields to
   * session flash
   * @method flashAll
   * @return {void}
   * @public
   */
  * flashAll () {
    yield this.flash(this.all())
  }

  /**
   * @description flash values of request keys from request
   * input field to session flash
   * @method flashOnly
   * @return {void}
   * @public
   */
  * flashOnly () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    yield this.flash(this.only(args))
  }

  /**
   * @description flash values of request to session flash
   * except defined keys
   * @method flashExcept
   * @return {void}
   * @public
   */
  * flashExcept () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    yield this.flash(this.except(args))
  }

  /**
   * @description tells whether a given pattern matches the
   * current url or not
   * @method match
   * @param  {String} pattern
   * @return {Boolean}
   * @public
   */
  match () {
    const args = _.isArray(arguments[0]) ? arguments[0] : _.toArray(arguments)
    const url = this.url()
    const pattern = pathToRegexp(args, [])
    return pattern.test(url)
  }

}

module.exports = Request
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var globalTimers = {
    setTimeout: require('timers').setTimeout,
    clearTimeout: require('timers').clearTimeout,
    now: Date.now
};
var globalRandom = Math.random;
var net = require('net');
var format = require('util').format;
var extend = require('xtend');

var inherits = require('util').inherits;
var StatEmitter = require('./lib/stat_emitter');

var nullLogger = require('./null-logger.js');
var EndpointHandler = require('./endpoint-handler.js');
var TChannelRequest = require('./request');
var TChannelServiceNameHandler = require('./service-name-handler');
var errors = require('./errors');

var TChannelConnection = require('./connection');
var TChannelPeers = require('./peers');
var TChannelServices = require('./services');

var TracingAgent = require('./trace/agent');

// TODO restore spying
// var Spy = require('./v2/spy');
// var dumpEnabled = /\btchannel_dump\b/.test(process.env.NODE_DEBUG || '');

function TChannel(options) {
    if (!(this instanceof TChannel)) {
        return new TChannel(options);
    }

    var self = this;
    StatEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.listeningEvent = self.defineEvent('listening');
    self.connectionEvent = self.defineEvent('connection');
    self.requestEvent = self.defineEvent('request');

    self.options = extend({
        timeoutCheckInterval: 1000,
        timeoutFuzz: 100,
        // TODO: maybe we should always add pid to user-supplied?
        processName: format('%s[%s]', process.title, process.pid)
    }, options);

    self.requestDefaults = extend({
        timeout: TChannelRequest.defaultTimeout
    }, self.options.requestDefaults);

    self.logger = self.options.logger || nullLogger;
    self.random = self.options.random || globalRandom;
    self.timers = self.options.timers || globalTimers;

    // Filled in by the listen call:
    self.host = null;
    self.requestedPort = null;

    // Filled in by listening event:
    self.hostPort = null;

    // name of the service running over this channel
    self.serviceName = '';
    if (self.options.serviceName) {
        self.serviceName = self.options.serviceName;
        delete self.options.serviceName;
    }

    // populated by makeSubChannel
    self.topChannel = null;
    self.subChannels = self.serviceName ? null : {};

    // how to handle incoming requests
    if (!self.options.handler) {
        if (!self.serviceName) {
            self.handler = TChannelServiceNameHandler(self);
        } else {
            self.handler = EndpointHandler(self.serviceName);
        }
    } else {
        self.handler = self.options.handler;
        delete self.options.handler;
    }

    // populated by:
    // - manually api (.peers.add etc)
    // - incoming connections on any listening socket
    self.peers = TChannelPeers(self, self.options);

    // For tracking the number of pending requests to any service
    self.services = new TChannelServices();
    if (self.options.maxPending !== undefined) {
        self.services.maxPending = self.options.maxPending;
    }
    if (self.options.maxPendingForService !== undefined) {
        self.services.maxPendingForService = self.options.maxPendingForService;
    }

    // TChannel advances through the following states.
    self.listened = false;
    self.listening = false;
    self.destroyed = false;

    if (self.options.trace) {
        self.tracer = new TracingAgent({
            logger: self.logger,
            forceTrace: self.options.forceTrace,
            serviceName: self.options.tracingServiceName,
            reporter: self.options.traceReporter
        });

        if (self.requestDefaults.trace !== false) {
            self.requestDefaults.trace = true;
        }
    }

    // lazily created by .getServer (usually from .listen)
    self.serverSocket = null;
    self.serverConnections = null;
}
inherits(TChannel, StatEmitter);

TChannel.prototype.getServer = function getServer() {
    var self = this;
    if (self.serverSocket) {
        return self.serverSocket;
    }

    self.serverConnections = Object.create(null);
    self.serverSocket = net.createServer(onServerSocketConnection);
    self.serverSocket.on('listening', onServerSocketListening);
    self.serverSocket.on('error', onServerSocketError);

    return self.serverSocket;

    function onServerSocketConnection(sock) {
        self.onServerSocketConnection(sock);
    }

    function onServerSocketListening() {
        self.onServerSocketListening();
    }

    function onServerSocketError(err) {
        self.onServerSocketError(err);
    }
};

TChannel.prototype.onServerSocketConnection = function onServerSocketConnection(sock) {
    var self = this;

    if (self.destroyed) {
        self.logger.error('got incoming socket whilst destroyed', {
            remoteAddr: sock.remoteAddr,
            remotePort: sock.remotePort,
            hostPort: self.hostPort
        });
        return;
    }

    var remoteAddr = sock.remoteAddress + ':' + sock.remotePort;
    var conn = new TChannelConnection(self, sock, 'in', remoteAddr);

    conn.spanEvent.on(function handleSpanFromConn(span) {
        self.tracer.report(span);
    });

    if (self.serverConnections[remoteAddr]) {
        var oldConn = self.serverConnections[remoteAddr];
        oldConn.resetAll(errors.SocketClosedError({
            reason: 'duplicate remoteAddr incoming conn'
        }));
        delete self.serverConnections[remoteAddr];
    }

    sock.on('close', onSocketClose);

    self.serverConnections[remoteAddr] = conn;
    self.connectionEvent.emit(self, conn);

    function onSocketClose() {
        delete self.serverConnections[remoteAddr];
    }
};

TChannel.prototype.onServerSocketListening = function onServerSocketListening() {
    var self = this;

    if (self.destroyed) {
        self.logger.error('got serverSocket listen whilst destroyed', {
            requestHostPort: self.host + ':' + self.requestedPort,
            hostPort: self.host + ':' + self.serverSocket.address().port
        });
        return;
    }

    var address = self.serverSocket.address();
    self.hostPort = self.host + ':' + address.port;
    self.listening = true;

    if (self.subChannels) {
        Object.keys(self.subChannels).forEach(function each(serviceName) {
            var chan = self.subChannels[serviceName];
            if (!chan.hostPort) {
                chan.hostPort = self.hostPort;
            }
        });
    }

    self.listeningEvent.emit(self);
};

TChannel.prototype.onServerSocketError = function onServerSocketError(err) {
    var self = this;

    if (err.code === 'EADDRINUSE') {
        err = errors.TChannelListenError(err, {
            requestedPort: self.requestedPort,
            host: self.host
        });
    }
    self.logger.error('server socket error', {
        err: err,
        requestedPort: self.requestedPort,
        host: self.host,
        hostPort: self.hostPort || null
    });
    self.errorEvent.emit(self, err);
};

TChannel.prototype.makeSubChannel = function makeSubChannel(options) {
    var self = this;
    if (!options) options = {};
    assert(!self.serviceName, 'arbitrary-depth sub channels are unsupported');
    assert(options.serviceName, 'must specify serviceName');
    assert(!self.subChannels[options.serviceName], 'duplicate sub channel creation');
    var opts = extend(self.options);
    var keys = Object.keys(options);
    for (var i = 0; i < keys.length; i++) {
        switch (keys[i]) {
            case 'peers':
                break;
            default:
                opts[keys[i]] = options[keys[i]];
        }
    }
    var chan = TChannel(opts);
    chan.topChannel = self;
    if (options.peers) {
        for (i = 0; i < options.peers.length; i++) {
            if (typeof options.peers[i] === 'string') {
                chan.peers.addPeer(self.peers.add(options.peers[i]));
            } else {
                chan.peers.addPeer(options.peers[i]);
            }
        }
    }
    self.subChannels[chan.serviceName] = chan;

    if (self.hostPort) {
        chan.hostPort = self.hostPort;
    }

    return chan;
};

TChannel.prototype.listen = function listen(port, host, callback) {
    // Note:
    // - 0 is a valid port number, indicating that the system must assign an
    //   available ephemeral port
    // - 127.0.0.1 is a valid host, primarily for testing
    var self = this;
    assert(!self.listened, 'TChannel can only listen once');
    assert(typeof host === 'string', 'TChannel requires host argument');
    assert(typeof port === 'number', 'TChannel must listen with numeric port');
    assert(host !== '0.0.0.0', 'TChannel must listen with externally visible host');
    self.listened = true;
    self.requestedPort = port;
    self.host = host;
    self.getServer().listen(port, host, callback);
};

TChannel.prototype.register = function register(name, options, handler) {
    var self = this;

    var handlerType = self.handler && self.handler.type;

    switch (handlerType) {
        case 'tchannel.endpoint-handler':
            self.handler.register(name, options, handler);
            break;

        case 'tchannel.service-name-handler':
            throw errors.TopLevelRegisterError();

        default:
            throw errors.InvalidHandlerForRegister({
                handlerType: handlerType,
                handler: self.handler
            });
    }
};

TChannel.prototype.address = function address() {
    var self = this;
    if (self.serverSocket) {
        return self.serverSocket.address() || null;
    } else if (self.topChannel) {
        return self.topChannel.address();
    } else {
        return null;
    }
};

TChannel.prototype.requestOptions = function requestOptions(options) {
    var self = this;
    var prop;
    var opts = {};
    // jshint forin:false
    for (prop in self.requestDefaults) {
        opts[prop] = self.requestDefaults[prop];
    }
    if (options) {
        for (prop in options) {
            opts[prop] = options[prop];
        }
    }
    // jshint forin:true
    return opts;
};

TChannel.prototype.request = function channelRequest(options) {
    var self = this;
    assert(!self.destroyed, 'cannot request() to destroyed tchannel');
    var opts = self.requestOptions(options);

    if (!self.serviceName && !opts.host) {
        if (opts.serviceName &&
            self.subChannels &&
            self.subChannels[opts.serviceName]) {
            return self.subChannels[opts.serviceName].request(opts);
        } else {
            throw errors.TopLevelRequestError();
        }
    }

    var req = null;
    if (opts.host || // retries are only between hosts
        opts.streamed // streaming retries not yet implemented
    ) {
        req = self.peers.request(null, opts);
    } else {
        req = new TChannelRequest(self, opts);
    }
    self.requestEvent.emit(self, req);
    return req;
};

TChannel.prototype.quit = // to provide backward compatibility.
TChannel.prototype.close = function close(callback) {
    var self = this;
    assert(!self.destroyed, 'TChannel double close');
    self.destroyed = true;

    var counter = 1;

    if (self.serverSocket) {
        ++counter;
        if (self.serverSocket.address()) {
            closeServerSocket();
        } else {
            self.serverSocket.once('listening', closeServerSocket);
        }
    }

    if (self.serverConnections) {
        var incomingConns = Object.keys(self.serverConnections);
        for (var i = 0; i < incomingConns.length; i++) {
            ++counter;
            var incomingConn = self.serverConnections[incomingConns[i]];
            incomingConn.close(onClose);
        }
    }

    if (self.subChannels) {
        var serviceNames = Object.keys(self.subChannels);
        serviceNames.forEach(function each(serviceName) {
            var svcchan = self.subChannels[serviceName];
            if (!svcchan.destroyed) {
                counter++;
                svcchan.close(onClose);
            }
        });
    }

    self.peers.close(onClose);

    function closeServerSocket() {
        self.serverSocket.once('close', onClose);
        self.serverSocket.close();
    }

    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more channel sockets than expected', {
                    counter: counter
                });
            }
            if (typeof callback === 'function') {
                callback();
            }
        }
    }
};

module.exports = TChannel;
<commit_msg>channel: add `emitStat()` method<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var globalTimers = {
    setTimeout: require('timers').setTimeout,
    clearTimeout: require('timers').clearTimeout,
    now: Date.now
};
var globalRandom = Math.random;
var net = require('net');
var format = require('util').format;
var extend = require('xtend');

var inherits = require('util').inherits;
var StatEmitter = require('./lib/stat_emitter');

var nullLogger = require('./null-logger.js');
var EndpointHandler = require('./endpoint-handler.js');
var TChannelRequest = require('./request');
var TChannelServiceNameHandler = require('./service-name-handler');
var errors = require('./errors');

var TChannelConnection = require('./connection');
var TChannelPeers = require('./peers');
var TChannelServices = require('./services');

var TracingAgent = require('./trace/agent');

// TODO restore spying
// var Spy = require('./v2/spy');
// var dumpEnabled = /\btchannel_dump\b/.test(process.env.NODE_DEBUG || '');

function TChannel(options) {
    if (!(this instanceof TChannel)) {
        return new TChannel(options);
    }

    var self = this;
    StatEmitter.call(self);
    self.errorEvent = self.defineEvent('error');
    self.listeningEvent = self.defineEvent('listening');
    self.connectionEvent = self.defineEvent('connection');
    self.requestEvent = self.defineEvent('request');

    self.options = extend({
        timeoutCheckInterval: 1000,
        timeoutFuzz: 100,
        // TODO: maybe we should always add pid to user-supplied?
        processName: format('%s[%s]', process.title, process.pid)
    }, options);

    // must have 'app', 'host', 'cluster', 'version'
    self.statTags = self.options.statTags || {};

    self.requestDefaults = extend({
        timeout: TChannelRequest.defaultTimeout
    }, self.options.requestDefaults);

    self.logger = self.options.logger || nullLogger;
    self.random = self.options.random || globalRandom;
    self.timers = self.options.timers || globalTimers;

    // Filled in by the listen call:
    self.host = null;
    self.requestedPort = null;

    // Filled in by listening event:
    self.hostPort = null;

    // name of the service running over this channel
    self.serviceName = '';
    if (self.options.serviceName) {
        self.serviceName = self.options.serviceName;
        delete self.options.serviceName;
    }

    // populated by makeSubChannel
    self.topChannel = null;
    self.subChannels = self.serviceName ? null : {};

    // how to handle incoming requests
    if (!self.options.handler) {
        if (!self.serviceName) {
            self.handler = TChannelServiceNameHandler(self);
        } else {
            self.handler = EndpointHandler(self.serviceName);
        }
    } else {
        self.handler = self.options.handler;
        delete self.options.handler;
    }

    // populated by:
    // - manually api (.peers.add etc)
    // - incoming connections on any listening socket
    self.peers = TChannelPeers(self, self.options);

    // For tracking the number of pending requests to any service
    self.services = new TChannelServices();
    if (self.options.maxPending !== undefined) {
        self.services.maxPending = self.options.maxPending;
    }
    if (self.options.maxPendingForService !== undefined) {
        self.services.maxPendingForService = self.options.maxPendingForService;
    }

    // TChannel advances through the following states.
    self.listened = false;
    self.listening = false;
    self.destroyed = false;

    if (self.options.trace) {
        self.tracer = new TracingAgent({
            logger: self.logger,
            forceTrace: self.options.forceTrace,
            serviceName: self.options.tracingServiceName,
            reporter: self.options.traceReporter
        });

        if (self.requestDefaults.trace !== false) {
            self.requestDefaults.trace = true;
        }
    }

    // lazily created by .getServer (usually from .listen)
    self.serverSocket = null;
    self.serverConnections = null;
}
inherits(TChannel, StatEmitter);

TChannel.prototype.getServer = function getServer() {
    var self = this;
    if (self.serverSocket) {
        return self.serverSocket;
    }

    self.serverConnections = Object.create(null);
    self.serverSocket = net.createServer(onServerSocketConnection);
    self.serverSocket.on('listening', onServerSocketListening);
    self.serverSocket.on('error', onServerSocketError);

    return self.serverSocket;

    function onServerSocketConnection(sock) {
        self.onServerSocketConnection(sock);
    }

    function onServerSocketListening() {
        self.onServerSocketListening();
    }

    function onServerSocketError(err) {
        self.onServerSocketError(err);
    }
};

TChannel.prototype.onServerSocketConnection = function onServerSocketConnection(sock) {
    var self = this;

    if (self.destroyed) {
        self.logger.error('got incoming socket whilst destroyed', {
            remoteAddr: sock.remoteAddr,
            remotePort: sock.remotePort,
            hostPort: self.hostPort
        });
        return;
    }

    var remoteAddr = sock.remoteAddress + ':' + sock.remotePort;
    var conn = new TChannelConnection(self, sock, 'in', remoteAddr);

    conn.spanEvent.on(function handleSpanFromConn(span) {
        self.tracer.report(span);
    });

    if (self.serverConnections[remoteAddr]) {
        var oldConn = self.serverConnections[remoteAddr];
        oldConn.resetAll(errors.SocketClosedError({
            reason: 'duplicate remoteAddr incoming conn'
        }));
        delete self.serverConnections[remoteAddr];
    }

    sock.on('close', onSocketClose);

    self.serverConnections[remoteAddr] = conn;
    self.connectionEvent.emit(self, conn);

    function onSocketClose() {
        delete self.serverConnections[remoteAddr];
    }
};

TChannel.prototype.onServerSocketListening = function onServerSocketListening() {
    var self = this;

    if (self.destroyed) {
        self.logger.error('got serverSocket listen whilst destroyed', {
            requestHostPort: self.host + ':' + self.requestedPort,
            hostPort: self.host + ':' + self.serverSocket.address().port
        });
        return;
    }

    var address = self.serverSocket.address();
    self.hostPort = self.host + ':' + address.port;
    self.listening = true;

    if (self.subChannels) {
        Object.keys(self.subChannels).forEach(function each(serviceName) {
            var chan = self.subChannels[serviceName];
            if (!chan.hostPort) {
                chan.hostPort = self.hostPort;
            }
        });
    }

    self.listeningEvent.emit(self);
};

TChannel.prototype.onServerSocketError = function onServerSocketError(err) {
    var self = this;

    if (err.code === 'EADDRINUSE') {
        err = errors.TChannelListenError(err, {
            requestedPort: self.requestedPort,
            host: self.host
        });
    }
    self.logger.error('server socket error', {
        err: err,
        requestedPort: self.requestedPort,
        host: self.host,
        hostPort: self.hostPort || null
    });
    self.errorEvent.emit(self, err);
};

TChannel.prototype.makeSubChannel = function makeSubChannel(options) {
    var self = this;
    if (!options) options = {};
    assert(!self.serviceName, 'arbitrary-depth sub channels are unsupported');
    assert(options.serviceName, 'must specify serviceName');
    assert(!self.subChannels[options.serviceName], 'duplicate sub channel creation');
    var opts = extend(self.options);
    var keys = Object.keys(options);
    for (var i = 0; i < keys.length; i++) {
        switch (keys[i]) {
            case 'peers':
                break;
            default:
                opts[keys[i]] = options[keys[i]];
        }
    }
    var chan = TChannel(opts);
    chan.topChannel = self;
    if (options.peers) {
        for (i = 0; i < options.peers.length; i++) {
            if (typeof options.peers[i] === 'string') {
                chan.peers.addPeer(self.peers.add(options.peers[i]));
            } else {
                chan.peers.addPeer(options.peers[i]);
            }
        }
    }
    self.subChannels[chan.serviceName] = chan;

    if (self.hostPort) {
        chan.hostPort = self.hostPort;
    }

    return chan;
};

TChannel.prototype.listen = function listen(port, host, callback) {
    // Note:
    // - 0 is a valid port number, indicating that the system must assign an
    //   available ephemeral port
    // - 127.0.0.1 is a valid host, primarily for testing
    var self = this;
    assert(!self.listened, 'TChannel can only listen once');
    assert(typeof host === 'string', 'TChannel requires host argument');
    assert(typeof port === 'number', 'TChannel must listen with numeric port');
    assert(host !== '0.0.0.0', 'TChannel must listen with externally visible host');
    self.listened = true;
    self.requestedPort = port;
    self.host = host;
    self.getServer().listen(port, host, callback);
};

TChannel.prototype.register = function register(name, options, handler) {
    var self = this;

    var handlerType = self.handler && self.handler.type;

    switch (handlerType) {
        case 'tchannel.endpoint-handler':
            self.handler.register(name, options, handler);
            break;

        case 'tchannel.service-name-handler':
            throw errors.TopLevelRegisterError();

        default:
            throw errors.InvalidHandlerForRegister({
                handlerType: handlerType,
                handler: self.handler
            });
    }
};

TChannel.prototype.address = function address() {
    var self = this;
    if (self.serverSocket) {
        return self.serverSocket.address() || null;
    } else if (self.topChannel) {
        return self.topChannel.address();
    } else {
        return null;
    }
};

TChannel.prototype.requestOptions = function requestOptions(options) {
    var self = this;
    var prop;
    var opts = {};
    // jshint forin:false
    for (prop in self.requestDefaults) {
        opts[prop] = self.requestDefaults[prop];
    }
    if (options) {
        for (prop in options) {
            opts[prop] = options[prop];
        }
    }
    // jshint forin:true
    return opts;
};

TChannel.prototype.request = function channelRequest(options) {
    var self = this;
    assert(!self.destroyed, 'cannot request() to destroyed tchannel');
    var opts = self.requestOptions(options);

    if (!self.serviceName && !opts.host) {
        if (opts.serviceName &&
            self.subChannels &&
            self.subChannels[opts.serviceName]) {
            return self.subChannels[opts.serviceName].request(opts);
        } else {
            throw errors.TopLevelRequestError();
        }
    }

    var req = null;
    if (opts.host || // retries are only between hosts
        opts.streamed // streaming retries not yet implemented
    ) {
        req = self.peers.request(null, opts);
    } else {
        req = new TChannelRequest(self, opts);
    }
    self.requestEvent.emit(self, req);
    return req;
};

TChannel.prototype.quit = // to provide backward compatibility.
TChannel.prototype.close = function close(callback) {
    var self = this;
    assert(!self.destroyed, 'TChannel double close');
    self.destroyed = true;

    var counter = 1;

    if (self.serverSocket) {
        ++counter;
        if (self.serverSocket.address()) {
            closeServerSocket();
        } else {
            self.serverSocket.once('listening', closeServerSocket);
        }
    }

    if (self.serverConnections) {
        var incomingConns = Object.keys(self.serverConnections);
        for (var i = 0; i < incomingConns.length; i++) {
            ++counter;
            var incomingConn = self.serverConnections[incomingConns[i]];
            incomingConn.close(onClose);
        }
    }

    if (self.subChannels) {
        var serviceNames = Object.keys(self.subChannels);
        serviceNames.forEach(function each(serviceName) {
            var svcchan = self.subChannels[serviceName];
            if (!svcchan.destroyed) {
                counter++;
                svcchan.close(onClose);
            }
        });
    }

    self.peers.close(onClose);

    function closeServerSocket() {
        self.serverSocket.once('close', onClose);
        self.serverSocket.close();
    }

    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more channel sockets than expected', {
                    counter: counter
                });
            }
            if (typeof callback === 'function') {
                callback();
            }
        }
    }
};

TChannel.prototype.emitStat = function emitStat(stat) {
    var self = this;

    var commonTags = self.statTags;
    var commonKeys = Object.keys(self.statTags);

    var localTags = stat.tags;
    for (var i = 0; i < commonKeys.length; i++) {
        localTags[commonKeys[i]] = commonTags[commonKeys[i]];
    }

    self.statEvent.emit(self, stat);

    if (self.topChannel) {
        self.topChannel.statEvent.emit(self, stat);
    }
};

module.exports = TChannel;
<|endoftext|>