<commit_before>/* @flow */

import * as t from "../index";

import {
  BINARY_OPERATORS,
  LOGICAL_OPERATORS,
  UNARY_OPERATORS,
  UPDATE_OPERATORS,
} from "../constants";

import defineType, {
  assertValueType,
  assertNodeType,
  assertNodeOrValueType,
  assertEach,
  chain,
  assertOneOf,
} from "./index";

defineType("ArrayExpression", {
  fields: {
    elements: {
      validate: chain(assertValueType("array"), assertEach(assertNodeOrValueType("null", "Expression", "SpreadElement"))),
      default: [],
    }
  },
  visitor: ["elements"],
  aliases: ["Expression"]
});

defineType("AssignmentExpression", {
  fields: {
    operator: {
      validate: assertValueType("string")
    },
    left: {
      validate: assertNodeType("LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Expression"]
});

defineType("BinaryExpression", {
  builder: ["operator", "left", "right"],
  fields: {
    operator: {
      validate: assertOneOf(...BINARY_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"]
});

defineType("Directive", {
  visitor: ["value"],
  fields: {
    value: {
      validate: assertNodeType("DirectiveLiteral")
    }
  }
});

defineType("DirectiveLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  }
});

defineType("BlockStatement", {
  builder: ["body", "directives"],
  visitor: ["directives", "body"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "Statement"]
});

defineType("BreakStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("CallExpression", {
  visitor: ["callee", "arguments"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression", "SpreadElement")))
    }
  },
  aliases: ["Expression"]
});

defineType("CatchClause", {
  visitor: ["param", "body"],
  fields: {
    param: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  },
  aliases: ["Scopable"]
});

defineType("ConditionalExpression", {
  visitor: ["test", "consequent", "alternate"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Expression")
    },
    alternate: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Expression", "Conditional"]
});

defineType("ContinueStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("DebuggerStatement", {
  aliases: ["Statement"]
});

defineType("DoWhileStatement", {
  visitor: ["test", "body"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  },
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"]
});

defineType("EmptyStatement", {
  aliases: ["Statement"]
});

defineType("ExpressionStatement", {
  visitor: ["expression"],
  fields: {
    expression: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Statement", "ExpressionWrapper"]
});

defineType("File", {
  builder: ["program", "comments", "tokens"],
  visitor: ["program"],
  fields: {
    program: {
      validate: assertNodeType("Program")
    }
  }
});

defineType("ForInStatement", {
  visitor: ["left", "right", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"],
  fields: {
    left: {
      validate: assertNodeType("VariableDeclaration", "LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("ForStatement", {
  visitor: ["init", "test", "update", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop"],
  fields: {
    init: {
      validate: assertNodeType("VariableDeclaration", "Expression"),
      optional: true
    },
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    update: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("FunctionDeclaration", {
  builder: ["id", "params", "body", "generator", "async"],
  visitor: ["id", "params", "body", "returnType", "typeParameters"],
  fields: {
    id: {
      validate: assertNodeType("Identifier")
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  aliases: [
    "Scopable",
    "Function",
    "BlockParent",
    "FunctionParent",
    "Statement",
    "Pureish",
    "Declaration"
  ]
});

defineType("FunctionExpression", {
  inherits: "FunctionDeclaration",
  aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Expression", "Pureish"],
  fields: {
    id: {
      validate: assertNodeType("Identifier"),
      optional: true
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  }
});

defineType("Identifier", {
  builder: ["name"],
  visitor: ["typeAnnotation"],
  aliases: ["Expression", "LVal"],
  fields: {
    name: {
      validate(node, key, val) {
        if (!t.isValidIdentifier(val)) {
          // todo
        }
      }
    }
  }
});

defineType("IfStatement", {
  visitor: ["test", "consequent", "alternate"],
  aliases: ["Statement", "Conditional"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Statement")
    },
    alternate: {
      optional: true,
      validate: assertNodeType("Statement")
    }
  }
});

defineType("LabeledStatement", {
  visitor: ["label", "body"],
  aliases: ["Statement"],
  fields: {
    label: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("StringLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NumericLiteral", {
  builder: ["value"],
  deprecatedAlias: "NumberLiteral",
  fields: {
    value: {
      validate: assertValueType("number")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NullLiteral", {
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("BooleanLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("boolean")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("RegExpLiteral", {
  builder: ["pattern", "flags"],
  deprecatedAlias: "RegexLiteral",
  aliases: ["Expression", "Literal"],
  fields: {
    pattern: {
      validate: assertValueType("string")
    },
    flags: {
      validate: assertValueType("string"),
      default: ""
    }
  }
});

defineType("LogicalExpression", {
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"],
  fields: {
    operator: {
      validate: assertOneOf(...LOGICAL_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  }
});

defineType("MemberExpression", {
  builder: ["object", "property", "computed"],
  visitor: ["object", "property"],
  aliases: ["Expression", "LVal"],
  fields: {
    object: {
      validate: assertNodeType("Expression")
    },
    property: {
      validate(node, key, val) {
        let expectedType = node.computed ? "Expression" : "Identifier";
        assertNodeType(expectedType)(node, key, val);
      }
    },
    computed: {
      default: false
    }
  }
});

defineType("NewExpression", {
  visitor: ["callee", "arguments"],
  aliases: ["Expression"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression", "SpreadElement")))
    }
  }
});

defineType("Program", {
  visitor: ["directives", "body"],
  builder: ["body", "directives"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "FunctionParent"]
});

defineType("ObjectExpression", {
  visitor: ["properties"],
  aliases: ["Expression"],
  fields: {
    properties: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("ObjectMethod", "ObjectProperty", "SpreadProperty")))
    }
  }
});

defineType("ObjectMethod", {
  builder: ["kind", "key", "params", "body", "computed"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("method", "get", "set")),
      default: "method"
    },
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  visitor: ["key", "params", "body", "decorators", "returnType", "typeParameters"],
  aliases: ["UserWhitespacable", "Function", "Scopable", "BlockParent", "FunctionParent", "Method", "ObjectMember"]
});

defineType("ObjectProperty", {
  builder: ["key", "value", "computed", "shorthand", "decorators"],
  fields: {
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    value: {
      validate: assertNodeType("Expression")
    },
    shorthand: {
      validate: assertValueType("boolean"),
      default: false
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator"))),
      optional: true
    }
  },
  visitor: ["key", "value", "decorators"],
  aliases: ["UserWhitespacable", "Property", "ObjectMember"]
});

defineType("RestElement", {
  visitor: ["argument", "typeAnnotation"],
  aliases: ["LVal"],
  fields: {
    argument: {
      validate: assertNodeType("LVal")
    }
  }
});

defineType("ReturnStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression"),
      optional: true
    }
  }
});

defineType("SequenceExpression", {
  visitor: ["expressions"],
  fields: {
    expressions: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression")))
    }
  },
  aliases: ["Expression"]
});

defineType("SwitchCase", {
  visitor: ["test", "consequent"],
  fields: {
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    consequent: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  }
});

defineType("SwitchStatement", {
  visitor: ["discriminant", "cases"],
  aliases: ["Statement", "BlockParent", "Scopable"],
  fields: {
    discriminant: {
      validate: assertNodeType("Expression")
    },
    cases: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("SwitchCase")))
    }
  }
});

defineType("ThisExpression", {
  aliases: ["Expression"]
});

defineType("ThrowStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression")
    }
  }
});

// todo: at least handler or finalizer should be set to be valid
defineType("TryStatement", {
  visitor: ["block", "handler", "finalizer"],
  aliases: ["Statement"],
  fields: {
    body: {
      validate: assertNodeType("BlockStatement")
    },
    handler: {
      optional: true,
      handler: assertNodeType("BlockStatement")
    },
    finalizer: {
      optional: true,
      validate: assertNodeType("BlockStatement")
    }
  }
});

defineType("UnaryExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UNARY_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["UnaryLike", "Expression"]
});

defineType("UpdateExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UPDATE_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["Expression"]
});

defineType("VariableDeclaration", {
  builder: ["kind", "declarations"],
  visitor: ["declarations"],
  aliases: ["Statement", "Declaration"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("var", "let", "const"))
    },
    declarations: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("VariableDeclarator")))
    }
  }
});

defineType("VariableDeclarator", {
  visitor: ["id", "init"],
  fields: {
    id: {
      validate: assertNodeType("LVal")
    },
    init: {
      optional: true,
      validate: assertNodeType("Expression")
    }
  }
});

defineType("WhileStatement", {
  visitor: ["test", "body"],
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement", "Statement")
    }
  }
});

defineType("WithStatement", {
  visitor: ["object", "body"],
  aliases: ["Statement"],
  fields: {
    object: {
      object: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  }
});
<commit_msg>UnaryExpressions are never not prefix<commit_after>/* @flow */

import * as t from "../index";

import {
  BINARY_OPERATORS,
  LOGICAL_OPERATORS,
  UNARY_OPERATORS,
  UPDATE_OPERATORS,
} from "../constants";

import defineType, {
  assertValueType,
  assertNodeType,
  assertNodeOrValueType,
  assertEach,
  chain,
  assertOneOf,
} from "./index";

defineType("ArrayExpression", {
  fields: {
    elements: {
      validate: chain(assertValueType("array"), assertEach(assertNodeOrValueType("null", "Expression", "SpreadElement"))),
      default: [],
    }
  },
  visitor: ["elements"],
  aliases: ["Expression"]
});

defineType("AssignmentExpression", {
  fields: {
    operator: {
      validate: assertValueType("string")
    },
    left: {
      validate: assertNodeType("LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Expression"]
});

defineType("BinaryExpression", {
  builder: ["operator", "left", "right"],
  fields: {
    operator: {
      validate: assertOneOf(...BINARY_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"]
});

defineType("Directive", {
  visitor: ["value"],
  fields: {
    value: {
      validate: assertNodeType("DirectiveLiteral")
    }
  }
});

defineType("DirectiveLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  }
});

defineType("BlockStatement", {
  builder: ["body", "directives"],
  visitor: ["directives", "body"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "Statement"]
});

defineType("BreakStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("CallExpression", {
  visitor: ["callee", "arguments"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression", "SpreadElement")))
    }
  },
  aliases: ["Expression"]
});

defineType("CatchClause", {
  visitor: ["param", "body"],
  fields: {
    param: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  },
  aliases: ["Scopable"]
});

defineType("ConditionalExpression", {
  visitor: ["test", "consequent", "alternate"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Expression")
    },
    alternate: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Expression", "Conditional"]
});

defineType("ContinueStatement", {
  visitor: ["label"],
  fields: {
    label: {
      validate: assertNodeType("Identifier"),
      optional: true
    }
  },
  aliases: ["Statement", "Terminatorless", "CompletionStatement"]
});

defineType("DebuggerStatement", {
  aliases: ["Statement"]
});

defineType("DoWhileStatement", {
  visitor: ["test", "body"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  },
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"]
});

defineType("EmptyStatement", {
  aliases: ["Statement"]
});

defineType("ExpressionStatement", {
  visitor: ["expression"],
  fields: {
    expression: {
      validate: assertNodeType("Expression")
    }
  },
  aliases: ["Statement", "ExpressionWrapper"]
});

defineType("File", {
  builder: ["program", "comments", "tokens"],
  visitor: ["program"],
  fields: {
    program: {
      validate: assertNodeType("Program")
    }
  }
});

defineType("ForInStatement", {
  visitor: ["left", "right", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"],
  fields: {
    left: {
      validate: assertNodeType("VariableDeclaration", "LVal")
    },
    right: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("ForStatement", {
  visitor: ["init", "test", "update", "body"],
  aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop"],
  fields: {
    init: {
      validate: assertNodeType("VariableDeclaration", "Expression"),
      optional: true
    },
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    update: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("FunctionDeclaration", {
  builder: ["id", "params", "body", "generator", "async"],
  visitor: ["id", "params", "body", "returnType", "typeParameters"],
  fields: {
    id: {
      validate: assertNodeType("Identifier")
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  aliases: [
    "Scopable",
    "Function",
    "BlockParent",
    "FunctionParent",
    "Statement",
    "Pureish",
    "Declaration"
  ]
});

defineType("FunctionExpression", {
  inherits: "FunctionDeclaration",
  aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Expression", "Pureish"],
  fields: {
    id: {
      validate: assertNodeType("Identifier"),
      optional: true
    },
    params: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("LVal")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  }
});

defineType("Identifier", {
  builder: ["name"],
  visitor: ["typeAnnotation"],
  aliases: ["Expression", "LVal"],
  fields: {
    name: {
      validate(node, key, val) {
        if (!t.isValidIdentifier(val)) {
          // todo
        }
      }
    }
  }
});

defineType("IfStatement", {
  visitor: ["test", "consequent", "alternate"],
  aliases: ["Statement", "Conditional"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    consequent: {
      validate: assertNodeType("Statement")
    },
    alternate: {
      optional: true,
      validate: assertNodeType("Statement")
    }
  }
});

defineType("LabeledStatement", {
  visitor: ["label", "body"],
  aliases: ["Statement"],
  fields: {
    label: {
      validate: assertNodeType("Identifier")
    },
    body: {
      validate: assertNodeType("Statement")
    }
  }
});

defineType("StringLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("string")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NumericLiteral", {
  builder: ["value"],
  deprecatedAlias: "NumberLiteral",
  fields: {
    value: {
      validate: assertValueType("number")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("NullLiteral", {
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("BooleanLiteral", {
  builder: ["value"],
  fields: {
    value: {
      validate: assertValueType("boolean")
    }
  },
  aliases: ["Expression", "Pureish", "Literal", "Immutable"]
});

defineType("RegExpLiteral", {
  builder: ["pattern", "flags"],
  deprecatedAlias: "RegexLiteral",
  aliases: ["Expression", "Literal"],
  fields: {
    pattern: {
      validate: assertValueType("string")
    },
    flags: {
      validate: assertValueType("string"),
      default: ""
    }
  }
});

defineType("LogicalExpression", {
  builder: ["operator", "left", "right"],
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"],
  fields: {
    operator: {
      validate: assertOneOf(...LOGICAL_OPERATORS)
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  }
});

defineType("MemberExpression", {
  builder: ["object", "property", "computed"],
  visitor: ["object", "property"],
  aliases: ["Expression", "LVal"],
  fields: {
    object: {
      validate: assertNodeType("Expression")
    },
    property: {
      validate(node, key, val) {
        let expectedType = node.computed ? "Expression" : "Identifier";
        assertNodeType(expectedType)(node, key, val);
      }
    },
    computed: {
      default: false
    }
  }
});

defineType("NewExpression", {
  visitor: ["callee", "arguments"],
  aliases: ["Expression"],
  fields: {
    callee: {
      validate: assertNodeType("Expression")
    },
    arguments: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression", "SpreadElement")))
    }
  }
});

defineType("Program", {
  visitor: ["directives", "body"],
  builder: ["body", "directives"],
  fields: {
    directives: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Directive"))),
      default: []
    },
    body: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  },
  aliases: ["Scopable", "BlockParent", "Block", "FunctionParent"]
});

defineType("ObjectExpression", {
  visitor: ["properties"],
  aliases: ["Expression"],
  fields: {
    properties: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("ObjectMethod", "ObjectProperty", "SpreadProperty")))
    }
  }
});

defineType("ObjectMethod", {
  builder: ["kind", "key", "params", "body", "computed"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("method", "get", "set")),
      default: "method"
    },
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator")))
    },
    body: {
      validate: assertNodeType("BlockStatement")
    },
    generator: {
      default: false,
      validate: assertValueType("boolean")
    },
    async: {
      default: false,
      validate: assertValueType("boolean")
    }
  },
  visitor: ["key", "params", "body", "decorators", "returnType", "typeParameters"],
  aliases: ["UserWhitespacable", "Function", "Scopable", "BlockParent", "FunctionParent", "Method", "ObjectMember"]
});

defineType("ObjectProperty", {
  builder: ["key", "value", "computed", "shorthand", "decorators"],
  fields: {
    computed: {
      validate: assertValueType("boolean"),
      default: false
    },
    key: {
      validate(node, key, val) {
        let expectedTypes = node.computed ? ["Expression"] : ["Identifier", "StringLiteral", "NumericLiteral"];
        assertNodeType(...expectedTypes)(node, key, val);
      }
    },
    value: {
      validate: assertNodeType("Expression")
    },
    shorthand: {
      validate: assertValueType("boolean"),
      default: false
    },
    decorators: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Decorator"))),
      optional: true
    }
  },
  visitor: ["key", "value", "decorators"],
  aliases: ["UserWhitespacable", "Property", "ObjectMember"]
});

defineType("RestElement", {
  visitor: ["argument", "typeAnnotation"],
  aliases: ["LVal"],
  fields: {
    argument: {
      validate: assertNodeType("LVal")
    }
  }
});

defineType("ReturnStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression"),
      optional: true
    }
  }
});

defineType("SequenceExpression", {
  visitor: ["expressions"],
  fields: {
    expressions: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Expression")))
    }
  },
  aliases: ["Expression"]
});

defineType("SwitchCase", {
  visitor: ["test", "consequent"],
  fields: {
    test: {
      validate: assertNodeType("Expression"),
      optional: true
    },
    consequent: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("Statement")))
    }
  }
});

defineType("SwitchStatement", {
  visitor: ["discriminant", "cases"],
  aliases: ["Statement", "BlockParent", "Scopable"],
  fields: {
    discriminant: {
      validate: assertNodeType("Expression")
    },
    cases: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("SwitchCase")))
    }
  }
});

defineType("ThisExpression", {
  aliases: ["Expression"]
});

defineType("ThrowStatement", {
  visitor: ["argument"],
  aliases: ["Statement", "Terminatorless", "CompletionStatement"],
  fields: {
    argument: {
      validate: assertNodeType("Expression")
    }
  }
});

// todo: at least handler or finalizer should be set to be valid
defineType("TryStatement", {
  visitor: ["block", "handler", "finalizer"],
  aliases: ["Statement"],
  fields: {
    body: {
      validate: assertNodeType("BlockStatement")
    },
    handler: {
      optional: true,
      handler: assertNodeType("BlockStatement")
    },
    finalizer: {
      optional: true,
      validate: assertNodeType("BlockStatement")
    }
  }
});

defineType("UnaryExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: true
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UNARY_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["UnaryLike", "Expression"]
});

defineType("UpdateExpression", {
  builder: ["operator", "argument", "prefix"],
  fields: {
    prefix: {
      default: false
    },
    argument: {
      validate: assertNodeType("Expression")
    },
    operator: {
      validate: assertOneOf(...UPDATE_OPERATORS)
    }
  },
  visitor: ["argument"],
  aliases: ["Expression"]
});

defineType("VariableDeclaration", {
  builder: ["kind", "declarations"],
  visitor: ["declarations"],
  aliases: ["Statement", "Declaration"],
  fields: {
    kind: {
      validate: chain(assertValueType("string"), assertOneOf("var", "let", "const"))
    },
    declarations: {
      validate: chain(assertValueType("array"), assertEach(assertNodeType("VariableDeclarator")))
    }
  }
});

defineType("VariableDeclarator", {
  visitor: ["id", "init"],
  fields: {
    id: {
      validate: assertNodeType("LVal")
    },
    init: {
      optional: true,
      validate: assertNodeType("Expression")
    }
  }
});

defineType("WhileStatement", {
  visitor: ["test", "body"],
  aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"],
  fields: {
    test: {
      validate: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement", "Statement")
    }
  }
});

defineType("WithStatement", {
  visitor: ["object", "body"],
  aliases: ["Statement"],
  fields: {
    object: {
      object: assertNodeType("Expression")
    },
    body: {
      validate: assertNodeType("BlockStatement")
    }
  }
});
<|endoftext|>


<commit_before>let _ = require('underscore');

module.exports = function (ldapSettings, userData) {
    let searchDn = ldapSettings.searchDn || 'cn';

    // userData.mail may be a string with one mail address or an array.
    // Nevertheless we are only interested in the first mail address here - if there should be more...
    let tmpEMail = userData.mail;
    if (Array.isArray(userData.mail)) {
        tmpEMail = userData.mail[0];
    }
    let tmpEMailArray = [{
        address: tmpEMail,
        verified: true,
        fromLDAP: true
    }];

    let username = userData[searchDn] || '';

    let user = {
        createdAt: new Date(),
        emails: tmpEMailArray,
        username: username.toLowerCase(),
        profile: _.pick(userData, _.without(ldapSettings.whiteListedFields, 'mail'))
    };
    // copy over the LDAP user's long name from "cn" field to the meteor accounts long name field
    if (user.profile.cn) {
        user.profile.name = user.profile.cn;
        delete user.profile.cn;
    }
    if (userData.isInactive) {
        user.isInactive = true;
    }
    return user;
};
<commit_msg>Fix: LDAP import shall make users inActive:false per default [skip ci]<commit_after>let _ = require('underscore');

module.exports = function (ldapSettings, userData) {
    let searchDn = ldapSettings.searchDn || 'cn';

    // userData.mail may be a string with one mail address or an array.
    // Nevertheless we are only interested in the first mail address here - if there should be more...
    let tmpEMail = userData.mail;
    if (Array.isArray(userData.mail)) {
        tmpEMail = userData.mail[0];
    }
    let tmpEMailArray = [{
        address: tmpEMail,
        verified: true,
        fromLDAP: true
    }];

    let username = userData[searchDn] || '';

    let user = {
        createdAt: new Date(),
        isInactive: false,
        emails: tmpEMailArray,
        username: username.toLowerCase(),
        profile: _.pick(userData, _.without(ldapSettings.whiteListedFields, 'mail'))
    };
    // copy over the LDAP user's long name from "cn" field to the meteor accounts long name field
    if (user.profile.cn) {
        user.profile.name = user.profile.cn;
        delete user.profile.cn;
    }
    if (userData.isInactive) {
        user.isInactive = true;
    }
    return user;
};
<|endoftext|>


<commit_before>(function() {

'use strict';

var expect = chai.expect;

describe('data-canvas', function() {
  var testDiv = document.getElementById('testdiv');
  var canvas;

  before(function() {
    canvas = document.createElement('canvas');
    canvas.width = 600;
    canvas.height = 200;
    testDiv.appendChild(canvas);
  });

  after(function() {
    // testDiv.innerHTML = '';  // avoid pollution between tests.
  });

  function rgbAtPos(im, x, y) {
    var index = y * (im.width * 4) + x * 4;
    return [
      im.data[index],
      im.data[index + 1],
      im.data[index + 2]
    ];
  }

  describe('DataContext', function() {
    it('should put pixels on the canvas', function() {
      if (!canvas) throw 'bad';  // for flow
      var ctx = canvas.getContext('2d');
      var dtx = dataCanvas.getDataContext(ctx);

      dtx.fillStyle = 'red';
      dtx.fillRect(100, 50, 200, 25);
      dtx.pushObject({something: 'or other'});
      dtx.popObject();

      var im = ctx.getImageData(0, 0, 600, 400);
      expect(rgbAtPos(im, 50, 50)).to.deep.equal([0, 0, 0]);
      expect(rgbAtPos(im, 200, 60)).to.deep.equal([255, 0, 0]);
    });

    it('should cache calls', function() {
      if (!canvas) throw 'bad';  // for flow
      var ctx = canvas.getContext('2d');
      var dtx = dataCanvas.getDataContext(canvas);
      var dtx2 = dataCanvas.getDataContext(ctx);

      expect(dtx2).to.equal(dtx2);
    });

    it('should support read/write to properties', function() {
      var dtx = dataCanvas.getDataContext(canvas);
      dtx.lineWidth = 10;
      expect(dtx.lineWidth).to.equal(10);
    });
  });

  describe('ClickTrackingContext', function() {
    var ctx;
    before(function() {
      if (!canvas) throw 'bad';  // for flow
      ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    });

    function getObjectsAt(draw, x, y) {
      var dtx = new dataCanvas.ClickTrackingContext(ctx, x, y);
      draw(dtx);
      return dtx.hits;
    }

    // To draw any of these:
    // draw(dataCanvas.getDataContext(ctx));

    it('should track clicks on rects', function() {
      function draw(dtx) {
        dtx.pushObject('r');
        dtx.fillStyle = 'red';
        dtx.fillRect(100, 50, 100, 25);
        dtx.popObject();
        dtx.pushObject('b');
        dtx.fillStyle = 'blue';
        dtx.fillRect(300, 100, 200, 25);
        dtx.popObject();
      }

      expect(getObjectsAt(draw, 150, 60)).to.deep.equal([['r']]);
      expect(getObjectsAt(draw, 350, 110)).to.deep.equal([['b']]);
      expect(getObjectsAt(draw, 250, 110)).to.deep.equal([]);
    });

    it('should track clicks on complex shapes', function() {
      function draw(dtx) {
        // This is the upper right half of a rectangle, i.e. a triangle.
        dtx.pushObject('triangle');
        dtx.beginPath();
        dtx.moveTo(100, 100);
        dtx.lineTo(400, 100);
        dtx.lineTo(400, 200);
        dtx.closePath();
        dtx.fill();
        dtx.popObject();
      }

      // This point is in the top right (and hence in the triangle)
      expect(getObjectsAt(draw, 300, 110)).to.deep.equal([['triangle']]);
      // This poitn is in the bottom left (and hence not in the triangle)
      expect(getObjectsAt(draw, 200, 180)).to.deep.equal([]);
    });

    it('should track clicks on stacked shapes', function() {
      function draw(dtx) {
        dtx.pushObject('bottom');
        dtx.fillStyle = 'red';
        dtx.fillRect(100, 50, 400, 100);
        dtx.pushObject('top');
        dtx.fillStyle = 'blue';
        dtx.fillRect(200, 75, 100, 50);
        dtx.popObject();
        dtx.popObject();
        dtx.pushObject('side');
        dtx.fillStyle = 'green';
        dtx.fillRect(450, 75, 100, 50);
        dtx.popObject();
      }

      draw(dataCanvas.getDataContext(ctx));
      expect(getObjectsAt(draw, 110, 60)).to.deep.equal([['bottom']]);
      expect(getObjectsAt(draw, 250, 100)).to.deep.equal([['top', 'bottom'], ['bottom']]);
      expect(getObjectsAt(draw, 475, 100)).to.deep.equal([['side'], ['bottom']]);
    });

    it('should reset hit tracker', function() {
      function draw(dtx) {
        dtx.reset();
        dtx.clearRect(0, 0, dtx.canvas.width, dtx.canvas.height);
        dtx.pushObject('rect');
        dtx.fillRect(100, 10, 200, 30);
        dtx.popObject();
      }
      function doubledraw(dtx) {
        draw(dtx);
        draw(dtx);
      }

      // Despite the double-drawing, only one object matches, not two.
      // This is because of the reset() call.
      doubledraw(dataCanvas.getDataContext(ctx));
      expect(getObjectsAt(doubledraw, 110, 30)).to.deep.equal([['rect']]);
    });

    // PhantomJS 1.9.x does not support isStrokeInPath
    // When Travis-CI updates to Phantom2, this can be re-enabled.
    // See https://github.com/ariya/phantomjs/issues/12948
    if (!navigator.userAgent.match(/PhantomJS\/1.9/)) {
      it('should detect clicks in strokes', function() {
        function draw(dtx) {
          dtx.save();
          dtx.pushObject('shape');
          dtx.lineWidth = 5;
          dtx.beginPath();
          dtx.moveTo(100, 10);
          dtx.lineTo(200, 10);
          dtx.lineTo(200, 30);
          dtx.lineTo(100, 30);
          dtx.closePath();
          dtx.stroke();
          dtx.popObject();
          dtx.restore();
        }

        console.log('isPointInStroke:');
        console.log(ctx.isPointInPath.toString());
        console.log(ctx.isPointInStroke.toString());
        console.log('---:');

        draw(dataCanvas.getDataContext(ctx));
        // a click on the stroke is a hit...
        expect(getObjectsAt(draw, 100, 10)).to.deep.equal([['shape']]);
        // ... while a click in the interior is not.
        expect(getObjectsAt(draw, 150, 20)).to.deep.equal([]);
      });
    }

  });

  describe('RecordingContext', function() {
    var RecordingContext = dataCanvas.RecordingContext;

    var ctx;
    before(function() {
      if (!canvas) throw 'bad';  // for flow
      ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    });

    describe('single canvas', function() {
      it('should record calls', function() {
        var dtx = new RecordingContext(ctx);
        dtx.fillStyle = 'red';
        dtx.pushObject('a');
        dtx.fillRect(100, 50, 200, 25);
        dtx.popObject();

        expect(dtx.calls).to.have.length(3); // push, fill, pop
        expect(dtx.drawnObjectsWith(function(x) { return x == 'a' })).to.have.length(1);
        expect(dtx.drawnObjectsWith(function(x) { return x == 'b' })).to.have.length(0);

        // TODO: check drawing styles
      });

      it('should return values from proxied functions', function() {
        var dtx = new RecordingContext(ctx);
        var metrics = dtx.measureText('Hello');

        expect(dtx.calls).to.deep.equal([['measureText', 'Hello']]);
        expect(metrics.width).to.be.greaterThan(0);
      });

      it('should provid static testing methods', function() {
        RecordingContext.recordAll();
        var dtx = dataCanvas.getDataContext(ctx);
        dtx.pushObject('hello');
        dtx.fillText('hello', 100, 10);
        dtx.popObject();

        expect(RecordingContext.drawnObjects()).to.deep.equal(['hello']);
        expect(RecordingContext.drawnObjectsWith(function(x) { return x == 'hello' })).to.deep.equal(['hello']);
        expect(RecordingContext.callsOf('fillText')).to.deep.equal(
            [['fillText', 'hello', 100, 10]]);

        RecordingContext.reset();
      });

      it('should reset the list of calls', function() {
        function render(dtx) {
          dtx.reset();  // this clears the list of calls
          dtx.pushObject('hello');
          dtx.fillText('hello', 100, 10);
          dtx.popObject();
        }

        RecordingContext.recordAll();
        var dtx = dataCanvas.getDataContext(ctx);
        render(dtx);
        render(dtx);

        // Only one object, not two (even though there are two render calls).
        expect(RecordingContext.drawnObjects()).to.have.length(1);

        RecordingContext.reset();
      });
    });

    describe('multiple canvases', function() {
      var canvas2;
      before(function() {
        canvas2 = document.createElement('canvas');
        canvas2.width = 400;
        canvas2.height = 100;
        canvas2.setAttribute('class', 'canvas2');
        canvas.setAttribute('class', 'canvas1');
        testDiv.appendChild(canvas2);
      });

      it('should record calls to both canvases', function() {
        function render(dtx, text) {
          dtx.pushObject(text);
          dtx.fillText(text, 100, 10);
          dtx.popObject();
        }

        RecordingContext.recordAll();

        var dtx1 = dataCanvas.getDataContext(canvas),
            dtx2 = dataCanvas.getDataContext(canvas2);
        render(dtx1, 'Hello #1');
        render(dtx2, 'Hello #2');

        expect(function() {
          RecordingContext.drawnObjects();
        }).to.throw(/multiple canvases/);

        expect(RecordingContext.drawnObjects(testdiv, '.canvas1'))
            .to.deep.equal(['Hello #1']);
        expect(RecordingContext.drawnObjects(testdiv, '.canvas2'))
            .to.deep.equal(['Hello #2']);

        expect(RecordingContext.callsOf(testdiv, '.canvas1', 'fillText'))
            .to.deep.equal([['fillText', 'Hello #1', 100, 10]]);
        expect(RecordingContext.callsOf(testdiv, '.canvas2', 'fillText'))
            .to.deep.equal([['fillText', 'Hello #2', 100, 10]]);

        expect(function() {
          RecordingContext.drawnObjects(testdiv, '.canvas3');
        }).to.throw(/Unable to find.*\.canvas3/);

        RecordingContext.reset();
      });

      it('should throw on matching non-canvas', function() {
        testDiv.innerHTML += '
Foo
';
        RecordingContext.recordAll();
        expect(function() {
          RecordingContext.drawnObjects(testdiv, '.foo');
        }).to.throw(/.foo neither matches nor contains/);
        RecordingContext.reset();
      });

      it('should throw before recording', function() {
        // TODO: this error message doesn't make much sense for a user.
        expect(function() {
          RecordingContext.drawnObjects(testdiv, '.canvas1');
        }).to.throw(/must call .*recordAll.*other.*static methods/);
      });
    });

    describe('error cases', function() {
      it('should throw on reset before record', function() {
        expect(function() {
          RecordingContext.reset();
        }).to.throw(/reset.*before.*recordAll/);
      });

      it('should throw on double record', function() {
        expect(function() {
          RecordingContext.recordAll();
          RecordingContext.recordAll();
        }).to.throw(/forgot.*reset/);
      });

      it('should throw on access without recording', function() {
        expect(function() {
          RecordingContext.drawnObjects();
        }).to.throw(/no canvases.*recorded/);
      });
    });
  });
});

})();
<commit_msg>drop console.log<commit_after>(function() {

'use strict';

var expect = chai.expect;

describe('data-canvas', function() {
  var testDiv = document.getElementById('testdiv');
  var canvas;

  before(function() {
    canvas = document.createElement('canvas');
    canvas.width = 600;
    canvas.height = 200;
    testDiv.appendChild(canvas);
  });

  after(function() {
    // testDiv.innerHTML = '';  // avoid pollution between tests.
  });

  function rgbAtPos(im, x, y) {
    var index = y * (im.width * 4) + x * 4;
    return [
      im.data[index],
      im.data[index + 1],
      im.data[index + 2]
    ];
  }

  describe('DataContext', function() {
    it('should put pixels on the canvas', function() {
      if (!canvas) throw 'bad';  // for flow
      var ctx = canvas.getContext('2d');
      var dtx = dataCanvas.getDataContext(ctx);

      dtx.fillStyle = 'red';
      dtx.fillRect(100, 50, 200, 25);
      dtx.pushObject({something: 'or other'});
      dtx.popObject();

      var im = ctx.getImageData(0, 0, 600, 400);
      expect(rgbAtPos(im, 50, 50)).to.deep.equal([0, 0, 0]);
      expect(rgbAtPos(im, 200, 60)).to.deep.equal([255, 0, 0]);
    });

    it('should cache calls', function() {
      if (!canvas) throw 'bad';  // for flow
      var ctx = canvas.getContext('2d');
      var dtx = dataCanvas.getDataContext(canvas);
      var dtx2 = dataCanvas.getDataContext(ctx);

      expect(dtx2).to.equal(dtx2);
    });

    it('should support read/write to properties', function() {
      var dtx = dataCanvas.getDataContext(canvas);
      dtx.lineWidth = 10;
      expect(dtx.lineWidth).to.equal(10);
    });
  });

  describe('ClickTrackingContext', function() {
    var ctx;
    before(function() {
      if (!canvas) throw 'bad';  // for flow
      ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    });

    function getObjectsAt(draw, x, y) {
      var dtx = new dataCanvas.ClickTrackingContext(ctx, x, y);
      draw(dtx);
      return dtx.hits;
    }

    // To draw any of these:
    // draw(dataCanvas.getDataContext(ctx));

    it('should track clicks on rects', function() {
      function draw(dtx) {
        dtx.pushObject('r');
        dtx.fillStyle = 'red';
        dtx.fillRect(100, 50, 100, 25);
        dtx.popObject();
        dtx.pushObject('b');
        dtx.fillStyle = 'blue';
        dtx.fillRect(300, 100, 200, 25);
        dtx.popObject();
      }

      expect(getObjectsAt(draw, 150, 60)).to.deep.equal([['r']]);
      expect(getObjectsAt(draw, 350, 110)).to.deep.equal([['b']]);
      expect(getObjectsAt(draw, 250, 110)).to.deep.equal([]);
    });

    it('should track clicks on complex shapes', function() {
      function draw(dtx) {
        // This is the upper right half of a rectangle, i.e. a triangle.
        dtx.pushObject('triangle');
        dtx.beginPath();
        dtx.moveTo(100, 100);
        dtx.lineTo(400, 100);
        dtx.lineTo(400, 200);
        dtx.closePath();
        dtx.fill();
        dtx.popObject();
      }

      // This point is in the top right (and hence in the triangle)
      expect(getObjectsAt(draw, 300, 110)).to.deep.equal([['triangle']]);
      // This poitn is in the bottom left (and hence not in the triangle)
      expect(getObjectsAt(draw, 200, 180)).to.deep.equal([]);
    });

    it('should track clicks on stacked shapes', function() {
      function draw(dtx) {
        dtx.pushObject('bottom');
        dtx.fillStyle = 'red';
        dtx.fillRect(100, 50, 400, 100);
        dtx.pushObject('top');
        dtx.fillStyle = 'blue';
        dtx.fillRect(200, 75, 100, 50);
        dtx.popObject();
        dtx.popObject();
        dtx.pushObject('side');
        dtx.fillStyle = 'green';
        dtx.fillRect(450, 75, 100, 50);
        dtx.popObject();
      }

      draw(dataCanvas.getDataContext(ctx));
      expect(getObjectsAt(draw, 110, 60)).to.deep.equal([['bottom']]);
      expect(getObjectsAt(draw, 250, 100)).to.deep.equal([['top', 'bottom'], ['bottom']]);
      expect(getObjectsAt(draw, 475, 100)).to.deep.equal([['side'], ['bottom']]);
    });

    it('should reset hit tracker', function() {
      function draw(dtx) {
        dtx.reset();
        dtx.clearRect(0, 0, dtx.canvas.width, dtx.canvas.height);
        dtx.pushObject('rect');
        dtx.fillRect(100, 10, 200, 30);
        dtx.popObject();
      }
      function doubledraw(dtx) {
        draw(dtx);
        draw(dtx);
      }

      // Despite the double-drawing, only one object matches, not two.
      // This is because of the reset() call.
      doubledraw(dataCanvas.getDataContext(ctx));
      expect(getObjectsAt(doubledraw, 110, 30)).to.deep.equal([['rect']]);
    });

    // PhantomJS 1.9.x does not support isStrokeInPath
    // When Travis-CI updates to Phantom2, this can be re-enabled.
    // See https://github.com/ariya/phantomjs/issues/12948
    if (!navigator.userAgent.match(/PhantomJS\/1.9/)) {
      it('should detect clicks in strokes', function() {
        function draw(dtx) {
          dtx.save();
          dtx.pushObject('shape');
          dtx.lineWidth = 5;
          dtx.beginPath();
          dtx.moveTo(100, 10);
          dtx.lineTo(200, 10);
          dtx.lineTo(200, 30);
          dtx.lineTo(100, 30);
          dtx.closePath();
          dtx.stroke();
          dtx.popObject();
          dtx.restore();
        }

        draw(dataCanvas.getDataContext(ctx));
        // a click on the stroke is a hit...
        expect(getObjectsAt(draw, 100, 10)).to.deep.equal([['shape']]);
        // ... while a click in the interior is not.
        expect(getObjectsAt(draw, 150, 20)).to.deep.equal([]);
      });
    }

  });

  describe('RecordingContext', function() {
    var RecordingContext = dataCanvas.RecordingContext;

    var ctx;
    before(function() {
      if (!canvas) throw 'bad';  // for flow
      ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    });

    describe('single canvas', function() {
      it('should record calls', function() {
        var dtx = new RecordingContext(ctx);
        dtx.fillStyle = 'red';
        dtx.pushObject('a');
        dtx.fillRect(100, 50, 200, 25);
        dtx.popObject();

        expect(dtx.calls).to.have.length(3); // push, fill, pop
        expect(dtx.drawnObjectsWith(function(x) { return x == 'a' })).to.have.length(1);
        expect(dtx.drawnObjectsWith(function(x) { return x == 'b' })).to.have.length(0);

        // TODO: check drawing styles
      });

      it('should return values from proxied functions', function() {
        var dtx = new RecordingContext(ctx);
        var metrics = dtx.measureText('Hello');

        expect(dtx.calls).to.deep.equal([['measureText', 'Hello']]);
        expect(metrics.width).to.be.greaterThan(0);
      });

      it('should provid static testing methods', function() {
        RecordingContext.recordAll();
        var dtx = dataCanvas.getDataContext(ctx);
        dtx.pushObject('hello');
        dtx.fillText('hello', 100, 10);
        dtx.popObject();

        expect(RecordingContext.drawnObjects()).to.deep.equal(['hello']);
        expect(RecordingContext.drawnObjectsWith(function(x) { return x == 'hello' })).to.deep.equal(['hello']);
        expect(RecordingContext.callsOf('fillText')).to.deep.equal(
            [['fillText', 'hello', 100, 10]]);

        RecordingContext.reset();
      });

      it('should reset the list of calls', function() {
        function render(dtx) {
          dtx.reset();  // this clears the list of calls
          dtx.pushObject('hello');
          dtx.fillText('hello', 100, 10);
          dtx.popObject();
        }

        RecordingContext.recordAll();
        var dtx = dataCanvas.getDataContext(ctx);
        render(dtx);
        render(dtx);

        // Only one object, not two (even though there are two render calls).
        expect(RecordingContext.drawnObjects()).to.have.length(1);

        RecordingContext.reset();
      });
    });

    describe('multiple canvases', function() {
      var canvas2;
      before(function() {
        canvas2 = document.createElement('canvas');
        canvas2.width = 400;
        canvas2.height = 100;
        canvas2.setAttribute('class', 'canvas2');
        canvas.setAttribute('class', 'canvas1');
        testDiv.appendChild(canvas2);
      });

      it('should record calls to both canvases', function() {
        function render(dtx, text) {
          dtx.pushObject(text);
          dtx.fillText(text, 100, 10);
          dtx.popObject();
        }

        RecordingContext.recordAll();

        var dtx1 = dataCanvas.getDataContext(canvas),
            dtx2 = dataCanvas.getDataContext(canvas2);
        render(dtx1, 'Hello #1');
        render(dtx2, 'Hello #2');

        expect(function() {
          RecordingContext.drawnObjects();
        }).to.throw(/multiple canvases/);

        expect(RecordingContext.drawnObjects(testdiv, '.canvas1'))
            .to.deep.equal(['Hello #1']);
        expect(RecordingContext.drawnObjects(testdiv, '.canvas2'))
            .to.deep.equal(['Hello #2']);

        expect(RecordingContext.callsOf(testdiv, '.canvas1', 'fillText'))
            .to.deep.equal([['fillText', 'Hello #1', 100, 10]]);
        expect(RecordingContext.callsOf(testdiv, '.canvas2', 'fillText'))
            .to.deep.equal([['fillText', 'Hello #2', 100, 10]]);

        expect(function() {
          RecordingContext.drawnObjects(testdiv, '.canvas3');
        }).to.throw(/Unable to find.*\.canvas3/);

        RecordingContext.reset();
      });

      it('should throw on matching non-canvas', function() {
        testDiv.innerHTML += '
Foo
';
        RecordingContext.recordAll();
        expect(function() {
          RecordingContext.drawnObjects(testdiv, '.foo');
        }).to.throw(/.foo neither matches nor contains/);
        RecordingContext.reset();
      });

      it('should throw before recording', function() {
        // TODO: this error message doesn't make much sense for a user.
        expect(function() {
          RecordingContext.drawnObjects(testdiv, '.canvas1');
        }).to.throw(/must call .*recordAll.*other.*static methods/);
      });
    });

    describe('error cases', function() {
      it('should throw on reset before record', function() {
        expect(function() {
          RecordingContext.reset();
        }).to.throw(/reset.*before.*recordAll/);
      });

      it('should throw on double record', function() {
        expect(function() {
          RecordingContext.recordAll();
          RecordingContext.recordAll();
        }).to.throw(/forgot.*reset/);
      });

      it('should throw on access without recording', function() {
        expect(function() {
          RecordingContext.drawnObjects();
        }).to.throw(/no canvases.*recorded/);
      });
    });
  });
});

})();
<|endoftext|>


<commit_before>import Ember from 'ember';
import $ from 'jquery';

export default Ember.Controller.extend({
  transportApiSrv: Ember.inject.service('trasport-api'),
  searchLocation: null,
  destinations: null,
  selectedDestination: null,
  timetable: null,

  setInitialState () {
    const initialStation = this.get('model').stations[0];
    this.set('searchLocation', initialStation.name);
    $('.departures-select').children().eq(1).attr('selected', 'selected');

    this.setDestinationSelectList(initialStation.station_code);
  },

  actions: {
    destinationSelected (destination) {
      let selectedService = this.get('destinations').filterBy('destination_name', destination);
      this.set('timetable', selectedService);
    },
    departureSelected (departureStation) {
      const $selectEl = $('.station-select');

      $selectEl.attr('disabled', 'disabled');
      $('select').material_select();
      this.set('timetable', null);
      this.setDestinationSelectList(departureStation).then(() => {
        $selectEl.removeAttr('disabled');
      });
    }
  },

  setDestinationSelectList (stationCode) {
    return this.get('transportApiSrv').getTrainSchedule({stationCode})
      .then(response => {
        this.set('destinations', response.departures.all);
        Ember.run.later(() => {
          $('select').material_select();
        }, 500);
      });
  }
});
<commit_msg>feat: Beatify time and date values on trains page<commit_after>import Ember from 'ember';
import $ from 'jquery';

export default Ember.Controller.extend({
  transportApiSrv: Ember.inject.service('trasport-api'),
  searchLocation: null,
  destinations: null,
  selectedDestination: null,
  timetable: null,

  setInitialState () {
    const initialStation = this.get('model').stations[0];
    this.set('searchLocation', initialStation.name);
    $('.departures-select').children().eq(1).attr('selected', 'selected');

    this.setDestinationSelectList(initialStation.station_code);
  },

  beatifyDatepickerValue: function () {
    if (this.get('datepickerValue')) {
      return moment(this.get('datepickerValue')).format('DD MMM YYYY');
    }
  }.property('datepickerValue'),

  beatifyTimepickerValue: function () {
    let time = this.get('trainsTimeValue');
    if (time) {
      if (time % 60 === 0) {
        return `${time / 60}:00`;
      } else {
        return `${(time - 30) / 60}:30`;
      }
    }
  }.property('trainsTimeValue'),

  actions: {
    destinationSelected (destination) {
      let selectedService = this.get('destinations').filterBy('destination_name', destination);
      this.set('timetable', selectedService);
    },
    departureSelected (departureStation) {
      const $selectEl = $('.station-select');

      $selectEl.attr('disabled', 'disabled');
      $('select').material_select();
      this.set('timetable', null);
      this.setDestinationSelectList(departureStation).then(() => {
        $selectEl.removeAttr('disabled');
      });
    }
  },

  setDestinationSelectList (stationCode) {
    return this.get('transportApiSrv').getTrainSchedule({stationCode})
      .then(response => {
        this.set('destinations', response.departures.all);
        Ember.run.later(() => {
          $('select').material_select();
        }, 500);
      });
  }
});
<|endoftext|>


<commit_before>// @flow
import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga';
import * as Sentry from '@sentry/browser';
import { history } from './store';
import { SDK_API_PATH } from './index';
// @if TARGET='app'
import Native from 'native';
import ElectronCookies from '@exponent/electron-cookies';
import { generateInitialUrl } from 'util/url';
// @endif

const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
const LBRY_TV_MINUS_PIRATE_BAY_UA_ID = 'UA-60403362-16';
const LBRY_TV_UA_ID = 'UA-60403362-12';
const DESKTOP_UA_ID = 'UA-60403362-13';
const SECOND_TRACKER_NAME = 'tracker2';

export const SHARE_INTERNAL = 'shareInternal';
const SHARE_THIRD_PARTY = 'shareThirdParty';

// @if TARGET='app'
ElectronCookies.enable({
  origin: 'https://lbry.tv',
});
// @endif

type Analytics = {
  error: string => Promise<any>,
  sentryError: ({}, {}) => Promise<any>,
  pageView: string => void,
  setUser: Object => void,
  toggleInternal: (boolean, ?boolean) => void,
  toggleThirdParty: (boolean, ?boolean) => void,
  apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
  apiLogPublish: (ChannelClaim | StreamClaim) => void,
  apiSyncTags: ({}) => void,
  tagFollowEvent: (string, boolean, string) => void,
  videoStartEvent: (string, number) => void,
  videoBufferEvent: (string, number) => void,
  emailProvidedEvent: () => void,
  emailVerifiedEvent: () => void,
  rewardEligibleEvent: () => void,
  startupEvent: () => void,
  readyEvent: number => void,
  openUrlEvent: string => void,
};

type LogPublishParams = {
  uri: string,
  claim_id: string,
  outpoint: string,
  channel_claim_id?: string,
};

let internalAnalyticsEnabled: boolean = IS_WEB || false;
let thirdPartyAnalyticsEnabled: boolean = IS_WEB || false;
// @if TARGET='app'
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true;
if (window.localStorage.getItem(SHARE_THIRD_PARTY) === 'true') thirdPartyAnalyticsEnabled = true;
// @endif

const analytics: Analytics = {
  error: message => {
    return new Promise(resolve => {
      if (internalAnalyticsEnabled && isProduction) {
        return Lbryio.call('event', 'desktop_error', { error_message: message }).then(() => {
          resolve(true);
        });
      } else {
        resolve(false);
      }
    });
  },
  sentryError: (error, errorInfo) => {
    return new Promise(resolve => {
      if (internalAnalyticsEnabled && isProduction) {
        Sentry.withScope(scope => {
          scope.setExtras(errorInfo);
          const eventId = Sentry.captureException(error);
          resolve(eventId);
        });
      } else {
        resolve(null);
      }
    });
  },
  pageView: path => {
    if (thirdPartyAnalyticsEnabled) {
      ReactGA.pageview(path, [SECOND_TRACKER_NAME]);
    }
  },
  setUser: userId => {
    if (thirdPartyAnalyticsEnabled && userId) {
      ReactGA.set({
        userId,
      });

      // @if TARGET='app'
      Native.getAppVersionInfo().then(({ localVersion }) => {
        sendGaEvent('Desktop-Version', localVersion);
      });
      // @endif
    }
  },
  toggleInternal: (enabled: boolean): void => {
    // Always collect analytics on lbry.tv
    // @if TARGET='app'
    internalAnalyticsEnabled = enabled;
    window.localStorage.setItem(SHARE_INTERNAL, enabled);
    // @endif
  },

  toggleThirdParty: (enabled: boolean): void => {
    // Always collect analytics on lbry.tv
    // @if TARGET='app'
    thirdPartyAnalyticsEnabled = enabled;
    window.localStorage.setItem(SHARE_THIRD_PARTY, enabled);
    // @endif
  },

  apiLogView: (uri, outpoint, claimId, timeToStart) => {
    return new Promise((resolve, reject) => {
      if (internalAnalyticsEnabled && (isProduction || devInternalApis)) {
        const params: {
          uri: string,
          outpoint: string,
          claim_id: string,
          time_to_start?: number,
        } = {
          uri,
          outpoint,
          claim_id: claimId,
        };

        // lbry.tv streams from AWS so we don't care about the time to start
        if (timeToStart && !IS_WEB) {
          params.time_to_start = timeToStart;
        }

        resolve(Lbryio.call('file', 'view', params));
      } else {
        resolve();
      }
    });
  },
  apiLogSearch: () => {
    if (internalAnalyticsEnabled && isProduction) {
      Lbryio.call('event', 'search');
    }
  },
  apiLogPublish: (claimResult: ChannelClaim | StreamClaim) => {
    if (internalAnalyticsEnabled && isProduction) {
      const { permanent_url: uri, claim_id: claimId, txid, nout, signing_channel: signingChannel } = claimResult;
      let channelClaimId;
      if (signingChannel) {
        channelClaimId = signingChannel.claim_id;
      }
      const outpoint = `${txid}:${nout}`;
      const params: LogPublishParams = { uri, claim_id: claimId, outpoint };
      if (channelClaimId) {
        params['channel_claim_id'] = channelClaimId;
      }

      Lbryio.call('event', 'publish', params);
    }
  },

  apiSyncTags: params => {
    if (internalAnalyticsEnabled && isProduction) {
      Lbryio.call('content_tags', 'sync', params);
    }
  },

  apiSearchFeedback: (query, vote) => {
    if (isProduction) {
      // We don't need to worry about analytics enabled here because users manually click on the button to provide feedback
      Lbryio.call('feedback', 'search', { query, vote });
    }
  },
  videoStartEvent: (claimId, duration) => {
    sendGaTimingEvent('Media', 'TimeToStart', Number((duration * 1000).toFixed(0)), claimId);
    sendPromMetric('time_to_start', duration);
  },
  videoBufferEvent: (claimId, currentTime) => {
    sendGaTimingEvent('Media', 'BufferTimestamp', currentTime * 1000, claimId);
    sendPromMetric('buffer');
  },
  tagFollowEvent: (tag, following, location) => {
    sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
  },
  channelBlockEvent: (uri, blocked, location) => {
    sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
  },
  emailProvidedEvent: () => {
    sendGaEvent('Engagement', 'Email-Provided');
  },
  emailVerifiedEvent: () => {
    sendGaEvent('Engagement', 'Email-Verified');
  },
  rewardEligibleEvent: () => {
    sendGaEvent('Engagement', 'Reward-Eligible');
  },
  openUrlEvent: (url: string) => {
    sendGaEvent('Engagement', 'Open-Url', url);
  },
  trendingAlgorithmEvent: (trendingAlgorithm: string) => {
    sendGaEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm);
  },
  startupEvent: () => {
    sendGaEvent('Startup', 'Startup');
  },
  readyEvent: (timeToReady: number) => {
    sendGaEvent('Startup', 'App-Ready');
    sendGaTimingEvent('Startup', 'App-Ready', timeToReady);
  },
};

function sendGaEvent(category, action, label, value) {
  if (thirdPartyAnalyticsEnabled && isProduction) {
    ReactGA.event(
      {
        category,
        action,
        ...(label ? { label } : {}),
        ...(value ? { value } : {}),
      },
      [SECOND_TRACKER_NAME]
    );
  }
}

function sendGaTimingEvent(category: string, action: string, timeInMs: number, label?: string) {
  if (thirdPartyAnalyticsEnabled && isProduction) {
    ReactGA.timing(
      {
        category,
        variable: action,
        value: timeInMs,
        ...(label ? { label } : {}),
      },
      [SECOND_TRACKER_NAME]
    );
  }
}

function sendPromMetric(name: string, value?: number) {
  if (IS_WEB) {
    let url = new URL(SDK_API_PATH + '/metric/ui');
    const params = { name: name, value: value ? value.toString() : '' };
    url.search = new URLSearchParams(params).toString();
    return fetch(url);
  }
}

let gaTrackers = [];

if (!IS_WEB) {
  gaTrackers.push({
    trackingId: DESKTOP_UA_ID,
  });
} else {
  gaTrackers.push({
    trackingId: LBRY_TV_UA_ID,
  });

  const { search } = window.location;
  const urlParams = new URLSearchParams(search);
  const isPirateBayUser = urlParams.get('utm_source') === 'PB';

  if (!isPirateBayUser) {
    gaTrackers.push({
      trackingId: LBRY_TV_MINUS_PIRATE_BAY_UA_ID,
      gaOptions: {
        name: SECOND_TRACKER_NAME,
      },
    });
  }
}

ReactGA.initialize(gaTrackers, {
  testMode: process.env.NODE_ENV !== 'production',
  cookieDomain: 'auto',
  siteSpeedSampleRate: 100,
  // un-comment to see events as they are sent to google
  // debug: true,
});

// Manually call the first page view
// React Router doesn't include this on `history.listen`
// @if TARGET='web'
analytics.pageView(window.location.pathname + window.location.search);
// @endif

// @if TARGET='app'
ReactGA.set({ checkProtocolTask: null });
ReactGA.set({ location: 'https://lbry.tv' });
analytics.pageView(
  window.location.pathname.split('.html')[1] + window.location.search || generateInitialUrl(window.location.hash)
);
// @endif;

// Listen for url changes and report
// This will include search queries
history.listen(location => {
  const { pathname, search } = location;

  const page = `${pathname}${search}`;
  analytics.pageView(page);
});

export default analytics;
<commit_msg>post ui metrics<commit_after>// @flow
import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga';
import * as Sentry from '@sentry/browser';
import { history } from './store';
import { SDK_API_PATH } from './index';
// @if TARGET='app'
import Native from 'native';
import ElectronCookies from '@exponent/electron-cookies';
import { generateInitialUrl } from 'util/url';
// @endif

const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
const LBRY_TV_MINUS_PIRATE_BAY_UA_ID = 'UA-60403362-16';
const LBRY_TV_UA_ID = 'UA-60403362-12';
const DESKTOP_UA_ID = 'UA-60403362-13';
const SECOND_TRACKER_NAME = 'tracker2';

export const SHARE_INTERNAL = 'shareInternal';
const SHARE_THIRD_PARTY = 'shareThirdParty';

// @if TARGET='app'
ElectronCookies.enable({
  origin: 'https://lbry.tv',
});
// @endif

type Analytics = {
  error: string => Promise<any>,
  sentryError: ({}, {}) => Promise<any>,
  pageView: string => void,
  setUser: Object => void,
  toggleInternal: (boolean, ?boolean) => void,
  toggleThirdParty: (boolean, ?boolean) => void,
  apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
  apiLogPublish: (ChannelClaim | StreamClaim) => void,
  apiSyncTags: ({}) => void,
  tagFollowEvent: (string, boolean, string) => void,
  videoStartEvent: (string, number) => void,
  videoBufferEvent: (string, number) => void,
  emailProvidedEvent: () => void,
  emailVerifiedEvent: () => void,
  rewardEligibleEvent: () => void,
  startupEvent: () => void,
  readyEvent: number => void,
  openUrlEvent: string => void,
};

type LogPublishParams = {
  uri: string,
  claim_id: string,
  outpoint: string,
  channel_claim_id?: string,
};

let internalAnalyticsEnabled: boolean = IS_WEB || false;
let thirdPartyAnalyticsEnabled: boolean = IS_WEB || false;
// @if TARGET='app'
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true;
if (window.localStorage.getItem(SHARE_THIRD_PARTY) === 'true') thirdPartyAnalyticsEnabled = true;
// @endif

const analytics: Analytics = {
  error: message => {
    return new Promise(resolve => {
      if (internalAnalyticsEnabled && isProduction) {
        return Lbryio.call('event', 'desktop_error', { error_message: message }).then(() => {
          resolve(true);
        });
      } else {
        resolve(false);
      }
    });
  },
  sentryError: (error, errorInfo) => {
    return new Promise(resolve => {
      if (internalAnalyticsEnabled && isProduction) {
        Sentry.withScope(scope => {
          scope.setExtras(errorInfo);
          const eventId = Sentry.captureException(error);
          resolve(eventId);
        });
      } else {
        resolve(null);
      }
    });
  },
  pageView: path => {
    if (thirdPartyAnalyticsEnabled) {
      ReactGA.pageview(path, [SECOND_TRACKER_NAME]);
    }
  },
  setUser: userId => {
    if (thirdPartyAnalyticsEnabled && userId) {
      ReactGA.set({
        userId,
      });

      // @if TARGET='app'
      Native.getAppVersionInfo().then(({ localVersion }) => {
        sendGaEvent('Desktop-Version', localVersion);
      });
      // @endif
    }
  },
  toggleInternal: (enabled: boolean): void => {
    // Always collect analytics on lbry.tv
    // @if TARGET='app'
    internalAnalyticsEnabled = enabled;
    window.localStorage.setItem(SHARE_INTERNAL, enabled);
    // @endif
  },

  toggleThirdParty: (enabled: boolean): void => {
    // Always collect analytics on lbry.tv
    // @if TARGET='app'
    thirdPartyAnalyticsEnabled = enabled;
    window.localStorage.setItem(SHARE_THIRD_PARTY, enabled);
    // @endif
  },

  apiLogView: (uri, outpoint, claimId, timeToStart) => {
    return new Promise((resolve, reject) => {
      if (internalAnalyticsEnabled && (isProduction || devInternalApis)) {
        const params: {
          uri: string,
          outpoint: string,
          claim_id: string,
          time_to_start?: number,
        } = {
          uri,
          outpoint,
          claim_id: claimId,
        };

        // lbry.tv streams from AWS so we don't care about the time to start
        if (timeToStart && !IS_WEB) {
          params.time_to_start = timeToStart;
        }

        resolve(Lbryio.call('file', 'view', params));
      } else {
        resolve();
      }
    });
  },
  apiLogSearch: () => {
    if (internalAnalyticsEnabled && isProduction) {
      Lbryio.call('event', 'search');
    }
  },
  apiLogPublish: (claimResult: ChannelClaim | StreamClaim) => {
    if (internalAnalyticsEnabled && isProduction) {
      const { permanent_url: uri, claim_id: claimId, txid, nout, signing_channel: signingChannel } = claimResult;
      let channelClaimId;
      if (signingChannel) {
        channelClaimId = signingChannel.claim_id;
      }
      const outpoint = `${txid}:${nout}`;
      const params: LogPublishParams = { uri, claim_id: claimId, outpoint };
      if (channelClaimId) {
        params['channel_claim_id'] = channelClaimId;
      }

      Lbryio.call('event', 'publish', params);
    }
  },

  apiSyncTags: params => {
    if (internalAnalyticsEnabled && isProduction) {
      Lbryio.call('content_tags', 'sync', params);
    }
  },

  apiSearchFeedback: (query, vote) => {
    if (isProduction) {
      // We don't need to worry about analytics enabled here because users manually click on the button to provide feedback
      Lbryio.call('feedback', 'search', { query, vote });
    }
  },
  videoStartEvent: (claimId, duration) => {
    sendGaTimingEvent('Media', 'TimeToStart', Number((duration * 1000).toFixed(0)), claimId);
    sendPromMetric('time_to_start', duration);
  },
  videoBufferEvent: (claimId, currentTime) => {
    sendGaTimingEvent('Media', 'BufferTimestamp', currentTime * 1000, claimId);
    sendPromMetric('buffer');
  },
  tagFollowEvent: (tag, following, location) => {
    sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
  },
  channelBlockEvent: (uri, blocked, location) => {
    sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
  },
  emailProvidedEvent: () => {
    sendGaEvent('Engagement', 'Email-Provided');
  },
  emailVerifiedEvent: () => {
    sendGaEvent('Engagement', 'Email-Verified');
  },
  rewardEligibleEvent: () => {
    sendGaEvent('Engagement', 'Reward-Eligible');
  },
  openUrlEvent: (url: string) => {
    sendGaEvent('Engagement', 'Open-Url', url);
  },
  trendingAlgorithmEvent: (trendingAlgorithm: string) => {
    sendGaEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm);
  },
  startupEvent: () => {
    sendGaEvent('Startup', 'Startup');
  },
  readyEvent: (timeToReady: number) => {
    sendGaEvent('Startup', 'App-Ready');
    sendGaTimingEvent('Startup', 'App-Ready', timeToReady);
  },
};

function sendGaEvent(category, action, label, value) {
  if (thirdPartyAnalyticsEnabled && isProduction) {
    ReactGA.event(
      {
        category,
        action,
        ...(label ? { label } : {}),
        ...(value ? { value } : {}),
      },
      [SECOND_TRACKER_NAME]
    );
  }
}

function sendGaTimingEvent(category: string, action: string, timeInMs: number, label?: string) {
  if (thirdPartyAnalyticsEnabled && isProduction) {
    ReactGA.timing(
      {
        category,
        variable: action,
        value: timeInMs,
        ...(label ? { label } : {}),
      },
      [SECOND_TRACKER_NAME]
    );
  }
}

function sendPromMetric(name: string, value?: number) {
  if (IS_WEB) {
    let url = new URL(SDK_API_PATH + '/metric/ui');
    const params = { name: name, value: value ? value.toString() : '' };
    url.search = new URLSearchParams(params).toString();
    return fetch(url, { method: 'post' });
  }
}

let gaTrackers = [];

if (!IS_WEB) {
  gaTrackers.push({
    trackingId: DESKTOP_UA_ID,
  });
} else {
  gaTrackers.push({
    trackingId: LBRY_TV_UA_ID,
  });

  const { search } = window.location;
  const urlParams = new URLSearchParams(search);
  const isPirateBayUser = urlParams.get('utm_source') === 'PB';

  if (!isPirateBayUser) {
    gaTrackers.push({
      trackingId: LBRY_TV_MINUS_PIRATE_BAY_UA_ID,
      gaOptions: {
        name: SECOND_TRACKER_NAME,
      },
    });
  }
}

ReactGA.initialize(gaTrackers, {
  testMode: process.env.NODE_ENV !== 'production',
  cookieDomain: 'auto',
  siteSpeedSampleRate: 100,
  // un-comment to see events as they are sent to google
  // debug: true,
});

// Manually call the first page view
// React Router doesn't include this on `history.listen`
// @if TARGET='web'
analytics.pageView(window.location.pathname + window.location.search);
// @endif

// @if TARGET='app'
ReactGA.set({ checkProtocolTask: null });
ReactGA.set({ location: 'https://lbry.tv' });
analytics.pageView(
  window.location.pathname.split('.html')[1] + window.location.search || generateInitialUrl(window.location.hash)
);
// @endif;

// Listen for url changes and report
// This will include search queries
history.listen(location => {
  const { pathname, search } = location;

  const page = `${pathname}${search}`;
  analytics.pageView(page);
});

export default analytics;
<|endoftext|>


<commit_before>// We need to remove these because they interfere with other libraires that use
// the for (x in var) loops
delete Object.prototype.toParam
delete Object.prototype.toQuery
delete Array.prototype.toParam
delete Array.prototype.toQuery

_.toParam = function(value, namespace) {
    if (value.toParam) {
        return value.toParam(namespace);
    } else if (_.isArray(value)) {
        return _.map(this, function(e) { return _.toParam(e); }).join('/');
    } else if (typeof value === 'object') {
		return _.map(this, function(value, key) {
			var namespaceWithKey = (namespace ? (namespace + "[" + key + "]") : key);

			if (value !== null && value !== undefined) {
				return _.toQuery(value, namespaceWithKey);
			}

			return escape(namespaceWithKey) + "=";

		}).join('&');
    }
}

_.toQuery = function(value, namespace) {
    if (value.toQuery) {
        return value.toQuery(namespace);
    } else if (_.isArray(value)) {
	var prefix = namespace + "[]";
	return _.map(this, function(value) { return value === null ? escape(prefix) + '=' : _.toQuery(value, prefix); }).join('&');
    } else if (typeof value === 'object') {
        return _.toParam(value, namespace);
    }
}

(function () {

    Viking.sync = function(method, model, options) {
      var type = methodMap[method];

      // Default options, unless specified.
      _.defaults(options || (options = {}), {
        emulateHTTP: Backbone.emulateHTTP,
        emulateJSON: Backbone.emulateJSON
      });

      // Default JSON-request options.
      var params = {type: type, dataType: 'json'};

      // Ensure that we have a URL.
      if (!options.url) {
        params.url = _.result(model, 'url') || urlError();
      }

      // Ensure that we have the appropriate request data.
      if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
        params.contentType = 'application/json';
        params.data = JSON.stringify(options.attrs || model.toJSON(options));
      }

      // For older servers, emulate JSON by encoding the request into an HTML-form.
      if (options.emulateJSON) {
        params.contentType = 'application/x-www-form-urlencoded';
        params.data = params.data ? {model: params.data} : {};
      }

      // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
      // And an `X-HTTP-Method-Override` header.
      if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
        params.type = 'POST';
        if (options.emulateJSON) params.data._method = type;
        var beforeSend = options.beforeSend;
        options.beforeSend = function(xhr) {
          xhr.setRequestHeader('X-HTTP-Method-Override', type);
          if (beforeSend) return beforeSend.apply(this, arguments);
        };
      }

      // Don't process data on a non-GET request.
      if (params.type !== 'GET' && !options.emulateJSON) {
        params.processData = false;
      } else if (options.data && typeof options.data === 'object') {
        options.data = _.toParam(options.data);
      }

      // If we're sending a `PATCH` request, and we're in an old Internet Explorer
      // that still has ActiveX enabled by default, override jQuery to use that
      // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
      if (params.type === 'PATCH' && noXhrPatch) {
        params.xhr = function() {
          return new ActiveXObject("Microsoft.XMLHTTP");
        };
      }

      // Make the request, allowing the user to override any Ajax options.
      var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
      model.trigger('request', model, xhr, options);
      return xhr;
    };

    var noXhrPatch =
      typeof window !== 'undefined' && !!window.ActiveXObject &&
        !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);

    // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
    var methodMap = {
      'create': 'POST',
      'update': 'PUT',
      'patch':  'PATCH',
      'delete': 'DELETE',
      'read':   'GET'
    };

}());<commit_msg>Fixed issue in ie8.js support file<commit_after>// We need to remove these because they interfere with other libraires that use
// the for (x in var) loops
delete Object.prototype.toParam
delete Object.prototype.toQuery
delete Array.prototype.toParam
delete Array.prototype.toQuery

_.toParam = function(value, namespace) {
    if (value.toParam) {
        return value.toParam(namespace);
    } else if (_.isArray(value)) {
        return _.map(value, function(e) { return _.toParam(e); }).join('/');
    } else if (typeof value === 'object') {
		return _.map(value, function(value, key) {
			var namespaceWithKey = (namespace ? (namespace + "[" + key + "]") : key);

			if (value !== null && value !== undefined) {
				return _.toQuery(value, namespaceWithKey);
			}

			return escape(namespaceWithKey) + "=";

		}).join('&');
    }
}

_.toQuery = function(value, namespace) {
    if (value.toQuery) {
        return value.toQuery(namespace);
    } else if (_.isArray(value)) {
	var prefix = namespace + "[]";
	return _.map(value, function(value) { return value === null ? escape(prefix) + '=' : _.toQuery(value, prefix); }).join('&');
    } else if (typeof value === 'object') {
        return _.toParam(value, namespace);
    }
}

(function () {

    Viking.sync = function(method, model, options) {
      var type = methodMap[method];

      // Default options, unless specified.
      _.defaults(options || (options = {}), {
        emulateHTTP: Backbone.emulateHTTP,
        emulateJSON: Backbone.emulateJSON
      });

      // Default JSON-request options.
      var params = {type: type, dataType: 'json'};

      // Ensure that we have a URL.
      if (!options.url) {
        params.url = _.result(model, 'url') || urlError();
      }

      // Ensure that we have the appropriate request data.
      if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
        params.contentType = 'application/json';
        params.data = JSON.stringify(options.attrs || model.toJSON(options));
      }

      // For older servers, emulate JSON by encoding the request into an HTML-form.
      if (options.emulateJSON) {
        params.contentType = 'application/x-www-form-urlencoded';
        params.data = params.data ? {model: params.data} : {};
      }

      // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
      // And an `X-HTTP-Method-Override` header.
      if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
        params.type = 'POST';
        if (options.emulateJSON) params.data._method = type;
        var beforeSend = options.beforeSend;
        options.beforeSend = function(xhr) {
          xhr.setRequestHeader('X-HTTP-Method-Override', type);
          if (beforeSend) return beforeSend.apply(this, arguments);
        };
      }

      // Don't process data on a non-GET request.
      if (params.type !== 'GET' && !options.emulateJSON) {
        params.processData = false;
      } else if (options.data && typeof options.data === 'object') {
        options.data = _.toParam(options.data);
      }

      // If we're sending a `PATCH` request, and we're in an old Internet Explorer
      // that still has ActiveX enabled by default, override jQuery to use that
      // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
      if (params.type === 'PATCH' && noXhrPatch) {
        params.xhr = function() {
          return new ActiveXObject("Microsoft.XMLHTTP");
        };
      }

      // Make the request, allowing the user to override any Ajax options.
      var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
      model.trigger('request', model, xhr, options);
      return xhr;
    };

    var noXhrPatch =
      typeof window !== 'undefined' && !!window.ActiveXObject &&
        !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);

    // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
    var methodMap = {
      'create': 'POST',
      'update': 'PUT',
      'patch':  'PATCH',
      'delete': 'DELETE',
      'read':   'GET'
    };

}());<|endoftext|>