<commit_before>/*
Licensed under The MIT License
==============================

Copyright (c) 2009 - 2010 Fireworks Technology Projects Inc.
[www.fireworksproject.com](http://www.fireworksproject.com)

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.
 */

/*jslint
onevar: true,
undef: true,
nomen: true,
eqeqeq: true,
plusplus: true,
bitwise: true,
strict: true,
immed: true */

/*members "@mozilla.org\/thread-manager;1", DISPATCH_NORMAL, apply,
    baseEvent, call, classes, constructor, dispatch, eventConstructor,
    getDefaulting, getPropagation, getService, id, import, interfaces,
    length, listen, mainThread, notify, nsIThread, nsIThreadManager, ok,
    promise, prototype, push, require, run, send, slice, then, toString,
    utils, watch
*/

/*global
Components: false,
setTimeout: false
*/

"use strict";

// For Mozilla JavaScript modules system.
var EXPORTED_SYMBOLS = ["exports"];

// If we are in the Mozilla module system we need to add some boilerplate to be
// CommonJS complient. This is obviously an ugly hack to allow integration with
// legacy code that uses the Mozilla module system.
if (Components) {
  var require = Components.utils.import(
        "resource://crownconstruction/modules/boot.js", null).require;
  var exports = {};
  var module = {id: "events"};
}

var ASSERT = require("assert");

var queue = (function create_queue() {
  var thread_manager = Components.classes["@mozilla.org/thread-manager;1"].
                     getService(Components.interfaces.nsIThreadManager),
      mode = Components.interfaces.nsIThread.DISPATCH_NORMAL;

  return function global_queue(continuation, event, args) {
          thread_manager.mainThread.dispatch({
                         run: function enqueue() {
                             continuation.apply(event, args);
                           }
                         }, mode);
        };
}());

exports.queue = queue;

/**
 * The base event prototype.
 */
exports.baseEvent = function event_constructor() {
  var self = {};

  self.toString = function to_string() {
    return '{"constructor": "baseEvent"}';
  };

  self.constructor = exports.baseEvent;
  return self;
};

function signal_constructor(spec) {
  spec = spec || {};
  var self = {},
      q = [],
      event_constructor = spec.eventConstructor || exports.baseEvent;

  self.listen = function signal_listen(observer) {
    q.push(observer);
  };

  self.send = function signal_send() {
    var len = q.length, i,
        event = ((this.constructor === event_constructor) && this ||
                 (typeof this.getPropagation === "function" &&
                  typeof this.getDefaulting === "function") && this ||
                 event_constructor(this));

    for (i = 0; i < len; i += 1) {
      q[i].apply(event, Array.prototype.slice.call(arguments));
    }
  };

  self.constructor = signal_constructor;
  return self;
}

function beacon_constructor(spec, id) {
  var self = {}, signals = {}, len = spec.length, i;
  id = id || "unidentified";

  for (i = 0; i < len; i += 1) {
    signals[spec[i]] = signal_constructor();
  }

  self.notify = function beacon_watch(name) {
    if (!signals[name]) {
      throw new Error("There is no signal known by '"+ name +
                      "' on beacon '"+ id +"'.");
    }
    signals[name].send.apply(
        exports.baseEvent(), Array.prototype.slice.call(arguments, 1));
  };

  self.watch = function beacon_watch(name, observer) {
    if (!signals[name]) {
      throw new Error("There is no signal known by '"+ name +
                      "' on beacon '"+ id +"'.");
    }
    if (typeof observer !== "function") {
      throw new Error("The observer passed to beacon.listen() for signal '"+
                      name +"' must be a function.");
    }
    signals[name].listen(observer);
  };

  self.constructor = beacon_constructor;
  return self;
}

exports.promise = function promise_constructor(init, id) {
  if (typeof init !== "function") {
    throw new Error(
        "init routine passed to promise() constructor must be a function.");
  }

  id = id || "unidentified";

  var self = {},
      initialized = 1,
      inprogress = 2,
      fulfilled = 3,
      exception = 4,
      beacon = beacon_constructor(["fulfill", "exception", "progress"], id),
      event,
      values,
      state = 0;

  beacon.watch("fulfill", function promise_fulfill() {
        ASSERT.ok(state >= initialized, "The promise '"+ id +"' was notified "+
          "of fulfillment before reaching the initialized state.");
        ASSERT.ok(state <= fulfilled, "The promise '"+ id +"' was notified "+
          "of fulfillment after already reaching the fulfilled state.");

        state = fulfilled;

        // The beacon will call apply() on this function, binding the
        // event object to "this".
        event = this;

        values = Array.prototype.slice.call(arguments);
      });

  beacon.watch("exception", function promise_exception() {
        ASSERT.ok(state >= initialized, "The promise '"+ id +"' was notified "+
          "of an exception before reaching the initialized state.");
        ASSERT.ok(state <= fulfilled, "The promise '"+ id +"' was notified "+
          "of an exception after already reaching the fulfilled state.");

        state = exception;

        // The beacon will call apply() on this function, binding the
        // event object to "this".
        event = this;

        values = Array.prototype.slice.call(arguments);
      });

  beacon.watch("progress", function promise_progress() {
        ASSERT.ok(state >= initialized, "The promise '"+ id +"' was notified "+
          "of progress before reaching the initialized state.");
        ASSERT.ok(state <= fulfilled, "The promise '"+ id +"' was notified "+
          "of an progress after already reaching the fulfilled state.");

        state = inprogress;

        // The beacon will call apply() on this function, binding the
        // event object to "this".
        event = this;

        values = Array.prototype.slice.call(arguments);
      });

  /**
   * Each callback continuation function passed to promise.then() will be
   * called with the arguments applied by the event initializer.  Also,
   * promise.then() returns a reference to the promise instance so that
   * promise.then() can be called in a chain like promise.then().then().then();
   * or imperitively like promise.then(); promise.then(); promise.then();. One
   * additional feature is that the internally created event object is bound to
   * "this" within each of the callback functions passed to promise.then(). The
   * last characteristic to keep in mind is that reference values like objects
   * and arrays passed to the callbacks registered with promise.then() my have
   * been mutated by a previous callback in the chain and cannot necessarily be
   * trusted.
   */
  self.then = function promise_then(fulfill, except, progress) {
    if (typeof fulfill === "function") {
      if (state === fulfilled) {
        fulfill.apply(event, values);
      } else {
        beacon.watch("fulfill", fulfill);
      }
    }
    if (typeof except === "function") {
      if (state === exception) {
        except.apply(event, values);
      } else {
        beacon.watch("exception", except);
      }
    }
    if (typeof progress === "function") {
      if (state === inprogress) {
        progress.apply(event, values);
      }
      beacon.watch("progress", progress);
    }
    return self;
  };

  self.constructor = exports.promise;
  queue(function promise_init() {
          state = initialized;
          init(beacon);
        });
  return self;
};

<commit_msg>Improvement on events module.<commit_after>/*
Licensed under The MIT License
==============================

Copyright (c) 2009 - 2010 Fireworks Technology Projects Inc.
[www.fireworksproject.com](http://www.fireworksproject.com)

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.
 */

/*jslint
onevar: true,
undef: true,
nomen: true,
eqeqeq: true,
plusplus: true,
bitwise: true,
strict: true,
immed: true */

/*members "@mozilla.org\/thread-manager;1", DISPATCH_NORMAL, apply,
    baseEvent, call, classes, concat, constructor, dispatch, eq,
    eventConstructor, getDefaulting, getPropagation, getService, id, import,
    interfaces, length, listen, mainThread, notify, nsIThread,
    nsIThreadManager, ok, promise, prototype, push, queue, require, run,
    send, slice, splice, then, toString, utils, watch
*/

/*global
Components: false,
setTimeout: false
*/

"use strict";

// For Mozilla JavaScript modules system.
var EXPORTED_SYMBOLS = ["exports"];

// If we are in the Mozilla module system we need to add some boilerplate to be
// CommonJS complient. This is obviously an ugly hack to allow integration with
// legacy code that uses the Mozilla module system.
if (Components) {
  var require = Components.utils.import(
        "resource://crownconstruction/modules/boot.js", null).require;
  var exports = {};
  var module = {id: "events"};
}

var ASSERT = require("assert");

var queue = (function create_queue() {
  var thread_manager = Components.classes["@mozilla.org/thread-manager;1"].
                     getService(Components.interfaces.nsIThreadManager),
      mode = Components.interfaces.nsIThread.DISPATCH_NORMAL;

  return function global_queue(continuation, event, args) {
          thread_manager.mainThread.dispatch({
                         run: function enqueue() {
                             continuation.apply(event, args);
                           }
                         }, mode);
        };
}());

exports.queue = queue;

/**
 * The base event prototype.
 */
exports.baseEvent = function event_constructor() {
  var self = {};

  self.toString = function to_string() {
    return '{"constructor": "baseEvent"}';
  };

  self.constructor = exports.baseEvent;
  return self;
};

function signal_constructor(spec) {
  spec = spec || {};
  var self = {},
      q = [],
      event_constructor = spec.eventConstructor || exports.baseEvent;

  self.listen = function signal_listen(observer) {
    q.push(observer);
  };

  self.send = function signal_send() {
    var len = q.length, i,
        event = ((this.constructor === event_constructor) && this ||
                 (typeof this.getPropagation === "function" &&
                  typeof this.getDefaulting === "function") && this ||
                 event_constructor(this));

    for (i = 0; i < len; i += 1) {
      q[i].apply(event, Array.prototype.slice.call(arguments));
    }
  };

  self.constructor = signal_constructor;
  return self;
}

function beacon_constructor(spec, id) {
  var self = {}, signals = {}, len = spec.length, i;
  id = id || "unidentified";

  for (i = 0; i < len; i += 1) {
    signals[spec[i]] = signal_constructor();
  }

  self.notify = function beacon_watch(name) {
    if (!signals[name]) {
      throw new Error("There is no signal known by '"+ name +
                      "' on beacon '"+ id +"'.");
    }
    signals[name].send.apply(
        exports.baseEvent(), Array.prototype.slice.call(arguments, 1));
  };

  self.watch = function beacon_watch(name, observer) {
    if (!signals[name]) {
      throw new Error("There is no signal known by '"+ name +
                      "' on beacon '"+ id +"'.");
    }
    if (typeof observer !== "function") {
      throw new Error("The observer passed to beacon.listen() for signal '"+
                      name +"' must be a function.");
    }
    signals[name].listen(observer);
  };

  self.constructor = beacon_constructor;
  return self;
}

function tuner_constructor(spec, id) {
  var self = beacon_constructor(spec, id), len = spec.length, i;

  function make_notify_method(name) {
    ASSERT.eq(typeof self[name], "undefined");

    self[name] = function () {
      var args = Array.prototype.splice.call(arguments);
      self.notify.apply(self, [name].concat(args));
    };
  }

  for (i = 0; i < len; i += 1) {
    make_notify_method(spec[i]);
  }

  self.constructor = tuner_constructor;
  return self;
}

exports.promise = function promise_constructor(init, id) {
  if (typeof init !== "function") {
    throw new Error(
        "init routine passed to promise() constructor must be a function.");
  }

  id = id || "unidentified";

  var self = {},
      initialized = 1,
      inprogress = 2,
      fulfilled = 3,
      exception = 4,
      beacon = tuner_constructor(["fulfill", "exception", "progress"], id),
      event,
      values,
      state = 0;

  beacon.watch("fulfill", function promise_fulfill() {
        ASSERT.ok(state >= initialized, "The promise '"+ id +"' was notified "+
          "of fulfillment before reaching the initialized state.");
        ASSERT.ok(state <= fulfilled, "The promise '"+ id +"' was notified "+
          "of fulfillment after already reaching the fulfilled state.");

        state = fulfilled;

        // The beacon will call apply() on this function, binding the
        // event object to "this".
        event = this;

        values = Array.prototype.slice.call(arguments);
      });

  beacon.watch("exception", function promise_exception() {
        ASSERT.ok(state >= initialized, "The promise '"+ id +"' was notified "+
          "of an exception before reaching the initialized state.");
        ASSERT.ok(state <= fulfilled, "The promise '"+ id +"' was notified "+
          "of an exception after already reaching the fulfilled state.");

        state = exception;

        // The beacon will call apply() on this function, binding the
        // event object to "this".
        event = this;

        values = Array.prototype.slice.call(arguments);
      });

  beacon.watch("progress", function promise_progress() {
        ASSERT.ok(state >= initialized, "The promise '"+ id +"' was notified "+
          "of progress before reaching the initialized state.");
        ASSERT.ok(state <= fulfilled, "The promise '"+ id +"' was notified "+
          "of an progress after already reaching the fulfilled state.");

        state = inprogress;

        // The beacon will call apply() on this function, binding the
        // event object to "this".
        event = this;

        values = Array.prototype.slice.call(arguments);
      });

  /**
   * Each callback continuation function passed to promise.then() will be
   * called with the arguments applied by the event initializer.  Also,
   * promise.then() returns a reference to the promise instance so that
   * promise.then() can be called in a chain like promise.then().then().then();
   * or imperitively like promise.then(); promise.then(); promise.then();. One
   * additional feature is that the internally created event object is bound to
   * "this" within each of the callback functions passed to promise.then(). The
   * last characteristic to keep in mind is that reference values like objects
   * and arrays passed to the callbacks registered with promise.then() my have
   * been mutated by a previous callback in the chain and cannot necessarily be
   * trusted.
   */
  self.then = function promise_then(fulfill, except, progress) {
    if (typeof fulfill === "function") {
      if (state === fulfilled) {
        fulfill.apply(event, values);
      } else {
        beacon.watch("fulfill", fulfill);
      }
    }
    if (typeof except === "function") {
      if (state === exception) {
        except.apply(event, values);
      } else {
        beacon.watch("exception", except);
      }
    }
    if (typeof progress === "function") {
      if (state === inprogress) {
        progress.apply(event, values);
      }
      beacon.watch("progress", progress);
    }
    return self;
  };

  self.constructor = exports.promise;
  queue(function promise_init() {
          state = initialized;
          init(beacon);
        });
  return self;
};

<|endoftext|>


<commit_before>/**
 *
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

class Cards {
  constructor () {
    this.cards = document.querySelectorAll('.card');

    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);
    this.update = this.update.bind(this);
    this.targetBCR = null;
    this.target = null;
    this.startX = 0;
    this.currentX = 0;
    this.screenX = 0;
    this.targetX = 0;
    this.draggingCard = false;

    this.addEventListeners();

    requestAnimationFrame(this.update);
  }

  addEventListeners () {
    document.addEventListener('touchstart', this.onStart);
    document.addEventListener('touchmove', this.onMove);
    document.addEventListener('touchend', this.onEnd);

    document.addEventListener('mousedown', this.onStart);
    document.addEventListener('mousemove', this.onMove);
    document.addEventListener('mouseup', this.onEnd);
  }

  onStart (evt) {
    if (this.target)
      return;

    if (!evt.target.classList.contains('card'))
      return;

    this.target = evt.target;
    this.targetBCR = this.target.getBoundingClientRect();

    this.startX = evt.pageX || evt.touches[0].pageX;
    this.currentX = this.startX;

    this.draggingCard = true;
    this.target.style.willChange = 'transform';

    evt.preventDefault();
  }

  onMove (evt) {
    if (!this.target)
      return;

    this.currentX = evt.pageX || evt.touches[0].pageX;
  }

  onEnd (evt) {
    if (!this.target)
      return;

    this.targetX = 0;
    let screenX = this.currentX - this.startX;
    if (Math.abs(screenX) > this.targetBCR.width * 0.35) {
      this.targetX
          = (screenX > 0) ? this.targetBCR.width : -this.targetBCR.width;
    }

    this.draggingCard = false;
  }

  update () {

    requestAnimationFrame(this.update);

    if (!this.target)
      return;

    if (this.draggingCard) {
      this.screenX = this.currentX - this.startX;
    } else {
      this.screenX += (this.targetX - this.screenX) / 4;
    }

    const normalizedDragDistance =
        (Math.abs(this.screenX) / this.targetBCR.width);
    const opacity = 1 - Math.pow(normalizedDragDistance, 3);

    this.target.style.transform = `translateX(${this.screenX}px)`;
    this.target.style.opacity = opacity;

    // User has finished dragging.
    if (this.draggingCard)
      return;

    const isNearlyAtStart = (Math.abs(this.screenX) < 0.1);
    const isNearlyInvisible = (opacity < 0.01);

    // If the card is nearly gone.
    if (isNearlyInvisible) {

      // Bail if there's no target or it's not attached to a parent anymore.
      if (!this.target || !this.target.parentNode)
        return;

      this.target.parentNode.removeChild(this.target);

      // Slide all the other cards.
      this.animateOtherCardsIntoPosition();

    } else if (isNearlyAtStart) {
      this.resetTarget();
    }
  }

  animateOtherCardsIntoPosition () {
    let isAfterCurrentTarget = false;

    const onTransitionEnd = evt => {
      this.resetTarget();

      evt.target.style.transition = 'none';
      evt.target.removeEventListener('transitionend', onTransitionEnd);
    };

    for (let i = 0; i < this.cards.length; i++) {
      const card = this.cards[i];

      // Wait until we find the target card.
      if (card === this.target) {
        isAfterCurrentTarget = true;
        continue;
      }

      if (!isAfterCurrentTarget)
        continue;

      // Move the card, then wait a frame and then slide it up.
      card.style.transform = `translateY(${this.targetBCR.height + 20}px)`;
      requestAnimationFrame(_ => {
        card.style.transition = 'transform 0.15s cubic-bezier(0,0,0.31,1)';
        card.style.transform = 'none';
      });

      card.addEventListener('transitionend', onTransitionEnd);
    }

    // If we didn't find any cards to slide remove the target.
    if (!isAfterCurrentTarget) {
      this.resetTarget();
    }
  }

  resetTarget () {
    if (!this.target)
      return;

    this.target.style.willChange = 'initial';
    this.target.style.transform = 'none';
    this.target = null;
  }
}

window.addEventListener('load', () => new Cards());
<commit_msg>Bugfix and use of web animation API in the animateOtherCardsIntoPosition function<commit_after>/**
 *
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

class Cards {
  constructor () {
    this.cards = Array.from(document.querySelectorAll('.card'));

    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);
    this.update = this.update.bind(this);
    this.targetBCR = null;
    this.target = null;
    this.startX = 0;
    this.currentX = 0;
    this.screenX = 0;
    this.targetX = 0;
    this.draggingCard = false;

    this.addEventListeners();

    requestAnimationFrame(this.update);
  }

  addEventListeners () {
    document.addEventListener('touchstart', this.onStart);
    document.addEventListener('touchmove', this.onMove);
    document.addEventListener('touchend', this.onEnd);

    document.addEventListener('mousedown', this.onStart);
    document.addEventListener('mousemove', this.onMove);
    document.addEventListener('mouseup', this.onEnd);
  }

  onStart (evt) {
    if (this.target)
      return;

    if (!evt.target.classList.contains('card'))
      return;

    this.target = evt.target;
    this.targetBCR = this.target.getBoundingClientRect();

    this.startX = evt.pageX || evt.touches[0].pageX;
    this.currentX = this.startX;

    this.draggingCard = true;
    this.target.style.willChange = 'transform';

    evt.preventDefault();
  }

  onMove (evt) {
    if (!this.target)
      return;

    this.currentX = evt.pageX || evt.touches[0].pageX;
  }

  onEnd (evt) {
    if (!this.target)
      return;

    this.targetX = 0;
    let screenX = this.currentX - this.startX;
    if (Math.abs(screenX) > this.targetBCR.width * 0.35) {
      this.targetX
          = (screenX > 0) ? this.targetBCR.width : -this.targetBCR.width;
    }

    this.draggingCard = false;
  }

  update () {

    requestAnimationFrame(this.update);

    if (!this.target)
      return;

    if (this.draggingCard) {
      this.screenX = this.currentX - this.startX;
    } else {
      this.screenX += (this.targetX - this.screenX) / 4;
    }

    const normalizedDragDistance =
        (Math.abs(this.screenX) / this.targetBCR.width);
    const opacity = 1 - Math.pow(normalizedDragDistance, 3);

    this.target.style.transform = `translateX(${this.screenX}px)`;
    this.target.style.opacity = opacity;

    // User has finished dragging.
    if (this.draggingCard)
      return;

    const isNearlyAtStart = (Math.abs(this.screenX) < 0.1);
    const isNearlyInvisible = (opacity < 0.01);

    // If the card is nearly gone.
    if (isNearlyInvisible) {

      // Bail if there's no target or it's not attached to a parent anymore.
      if (!this.target || !this.target.parentNode)
        return;

      this.target.parentNode.removeChild(this.target);

      const targetIndex = this.cards.indexOf(this.target);
      this.cards.splice(targetIndex, 1);

      // Slide all the other cards.
      this.animateOtherCardsIntoPosition(targetIndex);

    } else if (isNearlyAtStart) {
      this.resetTarget();
    }
  }

  animateOtherCardsIntoPosition (startIndex) {
    // If removed card was the last one, there is nothing to animate. Remove target.
    if (startIndex === this.cards.length) {
      this.resetTarget();
      return;
    }

    const frames = [{
      transform: `translateY(${this.targetBCR.height + 20}px)`
    }, {
      transform: 'none'
    }];
    const options = {
      easing: 'cubic-bezier(0,0,0.31,1)',
      duration: 150
    };
    const callback = () => this.resetTarget();

    for (let i = startIndex; i < this.cards.length; i++) {
      const card = this.cards[i];

      // Move the card down then slide it up.
      card
        .animate(frames, options)
        .addEventListener('finish', callback);
    }
  }

  resetTarget () {
    if (!this.target)
      return;

    this.target.style.willChange = 'initial';
    this.target.style.transform = 'none';
    this.target = null;
  }
}

window.addEventListener('load', () => new Cards());
<|endoftext|>


<commit_before>/*
 * Copyright 2012 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

(function(scope) {

scope = scope || {};

// imports

var ShadowDOM = scope.ShadowDOM;

// custom element definition registry (name: definition)

var registry = {
};

// SECTION 4

var instantiate = function(inPrototype) {
  // 4.a.1. Create a new object that implements PROTOTYPE
  // 4.a.2. Let ELEMENT by this new object
  //
  // the custom element instantiation algorithm must also ensure that the
  // output is a valid DOM element with the proper wrapper in place.
  //
  var element = domCreateElement(inPrototype.is);
  element.__proto__ = inPrototype;
  //
  // OUTPUT
  return element;
};

var initialize = function(inElement, inDefinition) {
  inElement.__proto__ = inDefinition.prototype;
};

var finalize = function(inElement, inDefinition) {
  // flag to prevent re-upgrades
  inElement.__upgraded__ = true;
  // TODO(sjmiles): OFF SPEC: attach 'is' attribute and property
  inElement.setAttribute("is", inDefinition.name);
  inElement.is = inDefinition.name;
  //
  // 4.a.3. Let CHAIN be ELEMENT's prototype chain, in reverse order
  // (starting with the most-derived object)
  // 4.a.4. For each item in CHAIN:
  //  1. Let ITEM be this item
  //  2. Let DEFINITION be a registered element definition that has ITEM as
  //  element prototype.
  //  3. If DEFINITION was not found, stop.
  //  4. If DEFINITION has element template:
  //    1. Let TEMPLATE be this template
  //    2. Create a shadow root with ELEMENT as its host
  //    3. Clone TEMPLATE as contents of this shadow root
  //
  // build a chain of derived objects (including this one)
  // first element is most-derived object
  var chain = getAncestorChain(inDefinition);
  //
  // cache shadows so we can call 'shadowRootCreated' asynchronously
  var shadows = [];
  //
  // walk the chain to create shadows
  chain.forEach(function(definition) {
    // create shadow dom
    var root = createShadowDOM(inElement, definition)
    // cache the root
    shadows.push(root);
    // upgrade elements now so that references created
    // during distribution do not become stale.
    upgradeAll(root);
  });
  //
  // do shadow dom distribution (for shims that do this imperatively)
  if (inDefinition.template) {
    // trigger distribution (for ShadowRoot impls that require it)
    ShadowDOM.distribute(inElement);
  }
  //
  // TODO(sjmiles): OFF SPEC:
  // walk ancestor chain again in support of lifecycle.shadowRootCreated
  // and lifecycle.attributeChanged
  chain.forEach(function(definition, i) {
    var fn = definition.lifecycle.shadowRootCreated;
    if (fn) {
      fn.call(inElement, shadows[i]);
    }
    // TODO(sjmiles): OFF SPEC: support lifecycle.attributeChanged
    // TODO(sjmiles): redesign so we only do this once
    observeAttributeChanges(inElement, definition);
  });
  //
  // upgrade custom elements that haven't been upgraded yet (due to race
  // condition in light dom upgrade, or user manipulation)
  upgradeAll(inElement);
  //
  // TODO(sjmiles): OFF SPEC: support lifecycle.created
  if (inDefinition.lifecycle.created) {
    // TODO(sjmiles): OFF SPEC: inDefinition.prototype.extendsName
    var ancestor = registry[inDefinition.prototype.extendsName];
    inDefinition.lifecycle.created.call(inElement,
      ancestor && ancestor.lifecycle.created);
  }
};

var getAncestorChain = function(inDefinition) {
  // build a chain of derived definitions (including inDefintion)
  // first element is most-derived object
  var chain = [inDefinition];
  var p = inDefinition.prototype;
  while (registry[p.extendsName]) {
    chain.unshift(registry[p.extendsName]);
    p = p.__proto__;
  }
  return chain;
};

var createShadowDOM = function(inElement, inDefinition) {
  if (inDefinition.template) {
    // 4.a.3.1 create a shadow root with ELEMENT as it's host
    // 4.a.3.2. clone template as contents of this shadow root
    //
    // ShadowDOM is an import
    var shadowRoot = new ShadowDOM.ShadowRoot(inElement);
    // TODO(sjmiles): check spec: .host not set automatically
    if (!shadowRoot.host) {
      shadowRoot.host = inElement;
    }
    var contents = inDefinition.template.content.cloneNode(true);
    shadowRoot.appendChild(contents);
    return shadowRoot;
  }
  return shadow;
};

var observeAttributeChanges = function(inElement, inDefinition) {
  // Setup mutation observer for attribute changes
  //
  // TODO(sjmiles): attaches a fresh observer for each
  // inherited definition. Instead, build one observer and have it walk
  // the definition chain to find change handlers.
  //
  var lc = inDefinition.lifecycle;
  if (lc.attributeChanged && window.WebKitMutationObserver){
    var observer = new WebKitMutationObserver(function(mutations) {
      mutations.forEach(function(m) {
        lc.attributeChanged.call(inElement, m.attributeName,
          m.oldValue, m.target.getAttribute(m.attributeName));
      });
    });
    //
    //console.log("attaching mutation observer to ", inElement)
    observer.observe(inElement, {
      attributes: true,
      attributeOldValue: true
    });
  }
};

var generateConstructor = function(inDefinition) {
  // 4.b.1. Generate a function object which, when called:
  // 4.b.1.1. Runs the custom element instantiation algorithm with PROTOTYPE
  // and TEMPLATE as arguments.
  // 4.b.1.2. Returns algorithm's output as result
  // 4.b.2. Let CONSTRUCTOR be that function object
  var constructor = function() {
    var element = instantiate(inDefinition.prototype);
    finalize(element, inDefinition);
    return element;
  };
  // TODO(sjmiles): OFF SPEC: flag this constructor so we can identify it
  // in instantiate above
  //constructor.generated = true;
  // 4.b.3. Set PROTOTYPE as the prototype property on CONSTRUCTOR
  constructor.prototype = inDefinition.prototype;
  // 4.b.3. Set CONSTRUCTOR as the constructor property on PROTOTYPE.
  inDefinition.prototype.constructor = constructor;
  return constructor;
};

// SECTION 5

var generatePrototype = function(inExtends, inProperties) {
  // 5.a.1 If EXTENDS is an invalid HTML tag name, throw an
  // InvalidCharacterError exception.
  if (!inExtends) {
    throw "5.a.1. InvalidCharacterError: extends must be a valid HTML tag name";
  }
  // 5.a.2. If EXTENDS is a custom element name, let BASE be the element
  // prototype of the custom DOM element with the custom element name EXTENDS
  if (registry[inExtends]) {
    var base = registry[inExtends].prototype;
  }
  else {
    // 5.a.3. Otherwise
    // 5.a.3.1 If EXTENDS is defined in HTML specification or other applicable
    // specifications, let BASE be the interface prototype object for the
    // element type corresponding to the HTML tag name of EXTENDS
    // 5.a.3.2 Otherwise, throw a NotSupportedError exception.
    // TODO(sjmiles): validation
    base = document.createElement(inExtends).__proto__;
  }
  //
  // TODO(sjmiles): this is improper implementation of 'define properties'
  // the spec means Object.defineProperties
  // we are using prototype swizzling instead, which also
  // means we don't need 'object that implements BASE'
  // as in spec.
  //
  // 5.a.4 Create a new object that implements BASE
  // 5.a.5 Let PROTOTYPE be this new object
  //var prototype = Object.create(base);
  //
  // 5.a.3.5 If PROPERTIES is present and not undefined, define properties on
  // PROTOTYPE using PROPERTIES
  //
  // TODO(sjmiles): handle undefined inProperties
  //
  // strategy: properties as prototype
  //
  var prototype = inProperties || {};
  //
  // chain the prototype to base
  //
  prototype.__proto__ = base;
  //
  // TODO(sjmiles): OFF SPEC: we need to store our extends name somewhere
  // so we can look up ancestor properties during initialization
  //
  prototype.extendsName = inExtends;
  //
  // OUTPUT
  return prototype;
};

var upgradeElement = function(inElement, inDefinition) {
  // do not re-upgrade
  if (inElement && inElement.__upgraded__) {
     return inElement;
  }
  //console.log('upgrading', inElement);
  // 5.b.2.3. Let UPGRADE be the result of running custom element
  // instantiation algorithm with PROTOTYPE and TEMPLATE as arguments
  /*
  var upgrade = instantiate(inDefinition.prototype);
  // 5.b.2.4 Replace ELEMENT with UPGRADE in TREE
  if (inElement) {
    transplantNode(upgrade, inElement);
  }
  */
  // TODO(sjmiles): OFFSPEC: it's more convenient for
  // polyfill to upgrade in-place, instead of creating
  // a new element.
  var upgrade = inElement;
  initialize(upgrade, inDefinition);
  //
  // complete element setup (compute redistributions)
  finalize(upgrade, inDefinition);
  // we need to upgrade any custom elements that appeared
  // as a result of this upgrade
  upgradeAll(upgrade);
  // 5.b.3 On UPGRADE, fire an event named elementupgrade with its bubbles
  // attribute set to true.
  // TODO(sjmiles): implement elementupgrade event
  return upgrade;
};

var upgradeElements = function(inTree, inDefinition) {
  // 6.b.1 Let NAME be the custom element name part of DEFINITION
  var name = inDefinition.name;
  // 6.b.2 For each element ELEMENT in TREE whose custom element name is NAME:
  var elements = inTree.querySelectorAll(name);
  for (var i=0, element; element=elements[i]; i++) {
    // when an element is upgraded, its children are upgraded. This makes
    // stale elements in this list that are children of components. Avoid
    // trying to upgrading them by checking if they have a parentNode.
    if (element.parentNode) {
      upgradeElement(element, inDefinition);
    }
  }
};

var	upgradeAll = function(inNode) {
	for (var n in registry) {
		upgradeElements(inNode, registry[n]);
	}
};

// SECTION 6

// polyfill UA parsing HTML by watching dom for changes via mutations observer
// and upgrading if any are detected.
var watchDOM = function() {
  var observer = new WebKitMutationObserver(function(mutations) {
		mutations.forEach(function(mxn){
			if (mxn.addedNodes.length) {
				upgradeAll(document);
			}
    });
  });
  observer.observe(document.body, {childList: true, subtree: true});
}

// SECTION 7.1

var validateArguments = function(inName, inOptions) {
  // The custom element name must start with a U+0078 LATIN SMALL LETTER X,
  // followed by U+002D HYPHEN-MINUS
  // TODO(sjmiles): wrong test
  if (!inName) {
    // 7.1.1. If NAME is an invalid custom element name, throw an
    // InvalidCharacterError exception.
    // TODO(sjmiles): wrong exception
    throw("name required");
  }
  // The element prototype itself must inherit from the HTMLElement interface
  var p = inOptions.prototype;
  if (p && !(p instanceof HTMLElement)) {
    // 7.1.3. if PROTOTYPE does not inherit from the HTMLElement interface,
    // throw a TypeMismatchError exception.
    throw "7.1.3. TypeMismatchError:  element prototype must inherit from HTMLElement";
  }
};

var register = function(inName, inOptions) {
  //
  // 7. Input
  //	DOCUMENT, the document on which the method is called
  //	NAME, the custom element name of the element being registered
  //	PROTOTYPE, the element prototype, optional
  //	TEMPLATE, the custom element template, optional
  //
  // TODO(sjmiles): resolve discrepancy between input and the formal arguments
  //
  validateArguments(inName, inOptions);
  var template = inOptions.template;
  // 7.1.2 If PROTOTYPE is missing, let PROTOTYPE be the interface prototype
  // object for the HTMLSpanElement interface
  var prototype = inOptions.prototype || HTMLSpanElement.prototype;
  // TODO(sjmiles): putting name on prototype not in spec
  prototype.is = inName;
  // TODO(sjmiles): lifecycle not in spec
  var lifecycle = inOptions.lifecycle || {};
  // 7.1.4 Let DEFINITION be the tuple of (PROTOTYPE, TEMPLATE, NAME)
  var definition = {
    prototype: prototype,
    template: template,
    name: inName,
    // TODO(sjmiles): OFF SPEC: lifecycle not in spec
    lifecycle: lifecycle
  };
  //
  // TODO(sjmiles): OFF SPEC: get tricky with lifecycle
  var ancestor = registry[prototype.extendsName];
  if (ancestor) {
    lifecycle.__proto__ = ancestor.lifecycle;
  }
  //
  // 7.1.5: Register the DEFINITION with DOCUMENT
  registry[inName] = definition;
  //
  // TODO(sjmiles): OFFSPEC: re-ordering the flow
  // so that prototype has the correct constructor on it
  // at instantiate time (when we call upgradeElements)
  //
  // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
  // and TEMPLATE as arguments
  // 7.1.8. Return the output of the previous step.
  // 7. Output: CONSTRUCTOR, the custom element constructor
  var ctor = generateConstructor(definition);
  // TODO(sjmiles): OFF SPEC: for deubgging only
  ctor.tag = inName;
  //
  // 7.1.6: For DOCUMENT tree and every shadow DOM subtree enclosed by
  // DOCUMENT tree:
  //
  // TODO(sjmiles): "and every shadow DOM subtree" may not be possible
  // from polyfill. We will ensure this happens for shadow subtrees
  // created by this polyfill, but any others are invisible.
  //
  // 7.1.6.1. Let TREE be this tree
  // 7.1.6.2. Run element upgrade algorithm with TREE and DEFINITION as
  // arguments
  //
  upgradeElements(document, definition);
  //
  return ctor;
};

// SECTION 7.2

// see HTMLElementElement.js

// exports

scope.CustomDOMElements = {
  registry: registry,
  instantiate: instantiate,
  generateConstructor: generateConstructor,
  generatePrototype: generatePrototype,
  upgradeElements: upgradeElements,
  upgradeAll: upgradeAll,
  watchDOM: watchDOM,
  validateArguments: validateArguments,
  register: register
};

// new public API

document.register = register;

// rewrite public API

var domCreateElement = document.createElement.bind(document);

document.createElement = function(inTagName) {
  var elt = domCreateElement(inTagName);
  var def = registry[inTagName];
  return def ? upgradeElement(elt, def) : elt;
  //return def ? upgradeElement(null, def) : domCreateElement(inTagName);
}

})(window.__exported_components_polyfill_scope__);
<commit_msg>null check in upgrade, bug fix<commit_after>/*
 * Copyright 2012 The Toolkitchen Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

(function(scope) {

scope = scope || {};

// imports

var ShadowDOM = scope.ShadowDOM;

// custom element definition registry (name: definition)

var registry = {
};

// SECTION 4

var instantiate = function(inPrototype) {
  // 4.a.1. Create a new object that implements PROTOTYPE
  // 4.a.2. Let ELEMENT by this new object
  //
  // the custom element instantiation algorithm must also ensure that the
  // output is a valid DOM element with the proper wrapper in place.
  //
  var element = domCreateElement(inPrototype.is);
  element.__proto__ = inPrototype;
  //
  // OUTPUT
  return element;
};

var initialize = function(inElement, inDefinition) {
  inElement.__proto__ = inDefinition.prototype;
};

var finalize = function(inElement, inDefinition) {
  // flag to prevent re-upgrades
  inElement.__upgraded__ = true;
  // TODO(sjmiles): OFF SPEC: attach 'is' attribute and property
  inElement.setAttribute("is", inDefinition.name);
  inElement.is = inDefinition.name;
  //
  // 4.a.3. Let CHAIN be ELEMENT's prototype chain, in reverse order
  // (starting with the most-derived object)
  // 4.a.4. For each item in CHAIN:
  //  1. Let ITEM be this item
  //  2. Let DEFINITION be a registered element definition that has ITEM as
  //  element prototype.
  //  3. If DEFINITION was not found, stop.
  //  4. If DEFINITION has element template:
  //    1. Let TEMPLATE be this template
  //    2. Create a shadow root with ELEMENT as its host
  //    3. Clone TEMPLATE as contents of this shadow root
  //
  // build a chain of derived objects (including this one)
  // first element is most-derived object
  var chain = getAncestorChain(inDefinition);
  //
  // cache shadows so we can call 'shadowRootCreated' asynchronously
  var shadows = [];
  //
  // walk the chain to create shadows
  chain.forEach(function(definition) {
    // create shadow dom
    var root = createShadowDOM(inElement, definition);
    if (root) {
      // cache the root
      shadows.push(root);
      // upgrade elements now so that references created
      // during distribution do not become stale.
      upgradeAll(root);
    }
  });
  //
  // do shadow dom distribution (for shims that do this imperatively)
  if (inDefinition.template) {
    // trigger distribution (for ShadowRoot impls that require it)
    ShadowDOM.distribute(inElement);
  }
  //
  // TODO(sjmiles): OFF SPEC:
  // walk ancestor chain again in support of lifecycle.shadowRootCreated
  // and lifecycle.attributeChanged
  chain.forEach(function(definition, i) {
    var fn = definition.lifecycle.shadowRootCreated;
    if (fn) {
      fn.call(inElement, shadows[i]);
    }
    // TODO(sjmiles): OFF SPEC: support lifecycle.attributeChanged
    // TODO(sjmiles): redesign so we only do this once
    observeAttributeChanges(inElement, definition);
  });
  //
  // upgrade custom elements that haven't been upgraded yet (due to race
  // condition in light dom upgrade, or user manipulation)
  upgradeAll(inElement);
  //
  // TODO(sjmiles): OFF SPEC: support lifecycle.created
  if (inDefinition.lifecycle.created) {
    // TODO(sjmiles): OFF SPEC: inDefinition.prototype.extendsName
    var ancestor = registry[inDefinition.prototype.extendsName];
    inDefinition.lifecycle.created.call(inElement,
      ancestor && ancestor.lifecycle.created);
  }
};

var getAncestorChain = function(inDefinition) {
  // build a chain of derived definitions (including inDefintion)
  // first element is most-derived object
  var chain = [inDefinition];
  var p = inDefinition.prototype;
  while (registry[p.extendsName]) {
    chain.unshift(registry[p.extendsName]);
    p = p.__proto__;
  }
  return chain;
};

var createShadowDOM = function(inElement, inDefinition) {
  if (inDefinition.template) {
    // 4.a.3.1 create a shadow root with ELEMENT as it's host
    // 4.a.3.2. clone template as contents of this shadow root
    //
    // ShadowDOM is an import
    var shadowRoot = new ShadowDOM.ShadowRoot(inElement);
    // TODO(sjmiles): check spec: .host not set automatically
    if (!shadowRoot.host) {
      shadowRoot.host = inElement;
    }
    var contents = inDefinition.template.content.cloneNode(true);
    shadowRoot.appendChild(contents);
  }
  return shadowRoot;
};

var observeAttributeChanges = function(inElement, inDefinition) {
  // Setup mutation observer for attribute changes
  //
  // TODO(sjmiles): attaches a fresh observer for each
  // inherited definition. Instead, build one observer and have it walk
  // the definition chain to find change handlers.
  //
  var lc = inDefinition.lifecycle;
  if (lc.attributeChanged && window.WebKitMutationObserver){
    var observer = new WebKitMutationObserver(function(mutations) {
      mutations.forEach(function(m) {
        lc.attributeChanged.call(inElement, m.attributeName,
          m.oldValue, m.target.getAttribute(m.attributeName));
      });
    });
    //
    //console.log("attaching mutation observer to ", inElement)
    observer.observe(inElement, {
      attributes: true,
      attributeOldValue: true
    });
  }
};

var generateConstructor = function(inDefinition) {
  // 4.b.1. Generate a function object which, when called:
  // 4.b.1.1. Runs the custom element instantiation algorithm with PROTOTYPE
  // and TEMPLATE as arguments.
  // 4.b.1.2. Returns algorithm's output as result
  // 4.b.2. Let CONSTRUCTOR be that function object
  var constructor = function() {
    var element = instantiate(inDefinition.prototype);
    finalize(element, inDefinition);
    return element;
  };
  // TODO(sjmiles): OFF SPEC: flag this constructor so we can identify it
  // in instantiate above
  //constructor.generated = true;
  // 4.b.3. Set PROTOTYPE as the prototype property on CONSTRUCTOR
  constructor.prototype = inDefinition.prototype;
  // 4.b.3. Set CONSTRUCTOR as the constructor property on PROTOTYPE.
  inDefinition.prototype.constructor = constructor;
  return constructor;
};

// SECTION 5

var generatePrototype = function(inExtends, inProperties) {
  // 5.a.1 If EXTENDS is an invalid HTML tag name, throw an
  // InvalidCharacterError exception.
  if (!inExtends) {
    throw "5.a.1. InvalidCharacterError: extends must be a valid HTML tag name";
  }
  // 5.a.2. If EXTENDS is a custom element name, let BASE be the element
  // prototype of the custom DOM element with the custom element name EXTENDS
  if (registry[inExtends]) {
    var base = registry[inExtends].prototype;
  }
  else {
    // 5.a.3. Otherwise
    // 5.a.3.1 If EXTENDS is defined in HTML specification or other applicable
    // specifications, let BASE be the interface prototype object for the
    // element type corresponding to the HTML tag name of EXTENDS
    // 5.a.3.2 Otherwise, throw a NotSupportedError exception.
    // TODO(sjmiles): validation
    base = document.createElement(inExtends).__proto__;
  }
  //
  // TODO(sjmiles): this is improper implementation of 'define properties'
  // the spec means Object.defineProperties
  // we are using prototype swizzling instead, which also
  // means we don't need 'object that implements BASE'
  // as in spec.
  //
  // 5.a.4 Create a new object that implements BASE
  // 5.a.5 Let PROTOTYPE be this new object
  //var prototype = Object.create(base);
  //
  // 5.a.3.5 If PROPERTIES is present and not undefined, define properties on
  // PROTOTYPE using PROPERTIES
  //
  // TODO(sjmiles): handle undefined inProperties
  //
  // strategy: properties as prototype
  //
  var prototype = inProperties || {};
  //
  // chain the prototype to base
  //
  prototype.__proto__ = base;
  //
  // TODO(sjmiles): OFF SPEC: we need to store our extends name somewhere
  // so we can look up ancestor properties during initialization
  //
  prototype.extendsName = inExtends;
  //
  // OUTPUT
  return prototype;
};

var upgradeElement = function(inElement, inDefinition) {
  // do not re-upgrade
  if (inElement && inElement.__upgraded__) {
     return inElement;
  }
  //console.log('upgrading', inElement);
  // 5.b.2.3. Let UPGRADE be the result of running custom element
  // instantiation algorithm with PROTOTYPE and TEMPLATE as arguments
  /*
  var upgrade = instantiate(inDefinition.prototype);
  // 5.b.2.4 Replace ELEMENT with UPGRADE in TREE
  if (inElement) {
    transplantNode(upgrade, inElement);
  }
  */
  // TODO(sjmiles): OFFSPEC: it's more convenient for
  // polyfill to upgrade in-place, instead of creating
  // a new element.
  var upgrade = inElement;
  initialize(upgrade, inDefinition);
  //
  // complete element setup (compute redistributions)
  finalize(upgrade, inDefinition);
  // we need to upgrade any custom elements that appeared
  // as a result of this upgrade
  upgradeAll(upgrade);
  // 5.b.3 On UPGRADE, fire an event named elementupgrade with its bubbles
  // attribute set to true.
  // TODO(sjmiles): implement elementupgrade event
  return upgrade;
};

var upgradeElements = function(inTree, inDefinition) {
  // 6.b.1 Let NAME be the custom element name part of DEFINITION
  var name = inDefinition.name;
  // 6.b.2 For each element ELEMENT in TREE whose custom element name is NAME:
  var elements = inTree.querySelectorAll(name);
  for (var i=0, element; element=elements[i]; i++) {
    // when an element is upgraded, its children are upgraded. This makes
    // stale elements in this list that are children of components. Avoid
    // trying to upgrading them by checking if they have a parentNode.
    if (element.parentNode) {
      upgradeElement(element, inDefinition);
    }
  }
};

var	upgradeAll = function(inNode) {
	for (var n in registry) {
		upgradeElements(inNode, registry[n]);
	}
};

// SECTION 6

// polyfill UA parsing HTML by watching dom for changes via mutations observer
// and upgrading if any are detected.
var watchDOM = function() {
  var observer = new WebKitMutationObserver(function(mutations) {
		mutations.forEach(function(mxn){
			if (mxn.addedNodes.length) {
				upgradeAll(document);
			}
    });
  });
  observer.observe(document.body, {childList: true, subtree: true});
}

// SECTION 7.1

var validateArguments = function(inName, inOptions) {
  // The custom element name must start with a U+0078 LATIN SMALL LETTER X,
  // followed by U+002D HYPHEN-MINUS
  // TODO(sjmiles): wrong test
  if (!inName) {
    // 7.1.1. If NAME is an invalid custom element name, throw an
    // InvalidCharacterError exception.
    // TODO(sjmiles): wrong exception
    throw("name required");
  }
  // The element prototype itself must inherit from the HTMLElement interface
  var p = inOptions.prototype;
  if (p && !(p instanceof HTMLElement)) {
    // 7.1.3. if PROTOTYPE does not inherit from the HTMLElement interface,
    // throw a TypeMismatchError exception.
    throw "7.1.3. TypeMismatchError:  element prototype must inherit from HTMLElement";
  }
};

var register = function(inName, inOptions) {
  //
  // 7. Input
  //	DOCUMENT, the document on which the method is called
  //	NAME, the custom element name of the element being registered
  //	PROTOTYPE, the element prototype, optional
  //	TEMPLATE, the custom element template, optional
  //
  // TODO(sjmiles): resolve discrepancy between input and the formal arguments
  //
  validateArguments(inName, inOptions);
  var template = inOptions.template;
  // 7.1.2 If PROTOTYPE is missing, let PROTOTYPE be the interface prototype
  // object for the HTMLSpanElement interface
  var prototype = inOptions.prototype || HTMLSpanElement.prototype;
  // TODO(sjmiles): putting name on prototype not in spec
  prototype.is = inName;
  // TODO(sjmiles): lifecycle not in spec
  var lifecycle = inOptions.lifecycle || {};
  // 7.1.4 Let DEFINITION be the tuple of (PROTOTYPE, TEMPLATE, NAME)
  var definition = {
    prototype: prototype,
    template: template,
    name: inName,
    // TODO(sjmiles): OFF SPEC: lifecycle not in spec
    lifecycle: lifecycle
  };
  //
  // TODO(sjmiles): OFF SPEC: get tricky with lifecycle
  var ancestor = registry[prototype.extendsName];
  if (ancestor) {
    lifecycle.__proto__ = ancestor.lifecycle;
  }
  //
  // 7.1.5: Register the DEFINITION with DOCUMENT
  registry[inName] = definition;
  //
  // TODO(sjmiles): OFFSPEC: re-ordering the flow
  // so that prototype has the correct constructor on it
  // at instantiate time (when we call upgradeElements)
  //
  // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
  // and TEMPLATE as arguments
  // 7.1.8. Return the output of the previous step.
  // 7. Output: CONSTRUCTOR, the custom element constructor
  var ctor = generateConstructor(definition);
  // TODO(sjmiles): OFF SPEC: for deubgging only
  ctor.tag = inName;
  //
  // 7.1.6: For DOCUMENT tree and every shadow DOM subtree enclosed by
  // DOCUMENT tree:
  //
  // TODO(sjmiles): "and every shadow DOM subtree" may not be possible
  // from polyfill. We will ensure this happens for shadow subtrees
  // created by this polyfill, but any others are invisible.
  //
  // 7.1.6.1. Let TREE be this tree
  // 7.1.6.2. Run element upgrade algorithm with TREE and DEFINITION as
  // arguments
  //
  upgradeElements(document, definition);
  //
  return ctor;
};

// SECTION 7.2

// see HTMLElementElement.js

// exports

scope.CustomDOMElements = {
  registry: registry,
  instantiate: instantiate,
  generateConstructor: generateConstructor,
  generatePrototype: generatePrototype,
  upgradeElements: upgradeElements,
  upgradeAll: upgradeAll,
  watchDOM: watchDOM,
  validateArguments: validateArguments,
  register: register
};

// new public API

document.register = register;

// rewrite public API

var domCreateElement = document.createElement.bind(document);

document.createElement = function(inTagName) {
  var elt = domCreateElement(inTagName);
  var def = registry[inTagName];
  return def ? upgradeElement(elt, def) : elt;
  //return def ? upgradeElement(null, def) : domCreateElement(inTagName);
}

})(window.__exported_components_polyfill_scope__);
<|endoftext|>


<commit_before>/* jslint node: true, esnext: true */

"use strict";

/*
 * Possible step states with their transitions
 */

const stopTransition = {
  timeout: 10,
  during: "stopping",
  final: "stopped"
};

const states = [{
  name: "starting",
  transitions: {
    stop: stopTransition
  }
}, {
  name: "running",
  description: "endpoints are able process requests",
  transitions: {
    stop: stopTransition
  }
}, {
  name: "stopped",
  description: "endpoints do not process requests",
  transitions: {
    start: {
      timeout: 10,
      during: "starting",
      final: "running"
    },
    remove: {
      final: "removed"
    }
  }
}, {
  name: "failed",
  description: "error occured"
}, {
  name: "removed",
  description: "final state before removing"
}];

/*
 * Transitions extracted from states above
 */
const transitions = {};

states.forEach(state => {
  for (const t in state.transitions) {
    let st = transitions[t];
    if (!st) {
      st = state.transitions[t];
      st.name = t;
      transitions[t] = st;
      st.current = {};
    }
    st.current[state.name] = {};
  }
});

//console.log(`${JSON.stringify(transitions,true,2)}`);

/**
 * Deliver a function to perfom a state transition.
 * States are traversed in the following way:
 * current -> during -> final
 * If the step is not in one of the transitions current
 * states and also not already in the transitions final
 * state a rejecting promise will be delivered from the
 * generated function. In the 'during' state a function
 * named '_' + <transitions name> (sample: '_start()')
 * will be called first.
 * It is expected that this function delivers a promise.
 * Special handling of consequent transitions:
 * While in a during state the former delivered primise will be
 * delivered again. This enshures that several consequent
 * transitions in a row will be fullfiled by the same promise.
 * There can only be one transition in place at a given point in time.
 * @param {Object} transition object describing the state transition
 * @return {Function} function when executed will perfom the state
 *         transition on a given step
 * @api private
 */
exports.transitionFunction = function (transition) {
  return function () {
    if (transition.current[this.state]) {

      this.state = transition.during;

      if (transition.timeout) {
        //console.log(`Guard ${transition.name} transition timeout ${transition.timeout}s`);
        this._transitionTimeoutID = setTimeout(() => {
          this._transitionTimeoutID = undefined;
          if (this._transitionPromise) {
            this.log(`${transition.name} transition timed out`);
            // TODO how to report
          }
        }, transition.timeout * 1000);
      }

      return this._transitionPromise = this['_' + transition.name]().then(
        () => {
          this.state = transition.final;
          this._transitionPromise = undefined;
          if (this._transitionTimeoutID) {
            clearTimeout(this._transitionTimeoutID);
            this._transitionTimeoutID = undefined;
          }

          return this;
        }, (reject) => {
          this.state = 'failed';
          this._transitionPromise = undefined;

          if (this._transitionTimeoutID) {
            clearTimeout(this._transitionTimeoutID);
            this._transitionTimeoutID = undefined;
          }

          return Promise.reject(reject);
        }
      );
    }
    if (this.state === transition.during) {
      return this._transitionPromise;
    }
    if (this.state === transition.final) {
      return Promise.resolve(this);
    }

    return this.rejectWrongState(transition.name);
  };
};

exports.transitions = transitions;
<commit_msg>fix: use this.error with arrow function for logging<commit_after>/* jslint node: true, esnext: true */

"use strict";

/*
 * Possible step states with their transitions
 */

const stopTransition = {
  timeout: 10,
  during: "stopping",
  final: "stopped"
};

const states = [{
  name: "starting",
  transitions: {
    stop: stopTransition
  }
}, {
  name: "running",
  description: "endpoints are able process requests",
  transitions: {
    stop: stopTransition
  }
}, {
  name: "stopped",
  description: "endpoints do not process requests",
  transitions: {
    start: {
      timeout: 10,
      during: "starting",
      final: "running"
    },
    remove: {
      final: "removed"
    }
  }
}, {
  name: "failed",
  description: "error occured"
}, {
  name: "removed",
  description: "final state before removing"
}];

/*
 * Transitions extracted from states above
 */
const transitions = {};

states.forEach(state => {
  for (const t in state.transitions) {
    let st = transitions[t];
    if (!st) {
      st = state.transitions[t];
      st.name = t;
      transitions[t] = st;
      st.current = {};
    }
    st.current[state.name] = {};
  }
});

//console.log(`${JSON.stringify(transitions,true,2)}`);

/**
 * Deliver a function to perfom a state transition.
 * States are traversed in the following way:
 * current -> during -> final
 * If the step is not in one of the transitions current
 * states and also not already in the transitions final
 * state a rejecting promise will be delivered from the
 * generated function. In the 'during' state a function
 * named '_' + <transitions name> (sample: '_start()')
 * will be called first.
 * It is expected that this function delivers a promise.
 * Special handling of consequent transitions:
 * While in a during state the former delivered primise will be
 * delivered again. This enshures that several consequent
 * transitions in a row will be fullfiled by the same promise.
 * There can only be one transition in place at a given point in time.
 * @param {Object} transition object describing the state transition
 * @return {Function} function when executed will perfom the state
 *         transition on a given step
 * @api private
 */
exports.transitionFunction = function (transition) {
  return function () {
    if (transition.current[this.state]) {

      this.state = transition.during;

      if (transition.timeout) {
        //console.log(`Guard ${transition.name} transition timeout ${transition.timeout}s`);
        this._transitionTimeoutID = setTimeout(() => {
          this._transitionTimeoutID = undefined;
          if (this._transitionPromise) {
            this.error(level => `${transition.name} transition timed out`);
            // TODO how to report
          }
        }, transition.timeout * 1000);
      }

      return this._transitionPromise = this['_' + transition.name]().then(
        () => {
          this.state = transition.final;
          this._transitionPromise = undefined;
          if (this._transitionTimeoutID) {
            clearTimeout(this._transitionTimeoutID);
            this._transitionTimeoutID = undefined;
          }

          return this;
        }, (reject) => {
          this.state = 'failed';
          this._transitionPromise = undefined;

          if (this._transitionTimeoutID) {
            clearTimeout(this._transitionTimeoutID);
            this._transitionTimeoutID = undefined;
          }

          return Promise.reject(reject);
        }
      );
    }
    if (this.state === transition.during) {
      return this._transitionPromise;
    }
    if (this.state === transition.final) {
      return Promise.resolve(this);
    }

    return this.rejectWrongState(transition.name);
  };
};

exports.transitions = transitions;
<|endoftext|>


<commit_before>/* Janus Event Tracer (C) 2022 QXIP BV */

/* eslint-disable camelcase */
/* eslint-disable semi */
/* eslint quotes: 0 */
/* eslint-disable quote-props */
/* eslint-disable dot-notation */

var base_filter = require('@pastash/pastash').base_filter
var util = require('util')
var logger = require('@pastash/pastash').logger

const QuickLRU = require("quick-lru");

const recordCache = require("record-cache");
const fetch = require('cross-fetch');

const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
const { MeterProvider } = require('@opentelemetry/sdk-metrics-base');

function nano_now (date) { return parseInt(date.toString().padEnd(16, '0')) }
function just_now (date) { return nano_now(date || new Date().getTime()) }
function spanid () { return Math.floor(10000000 + Math.random() * 90000000).toString() }

function FilterAppJanusTracer () {
  base_filter.BaseFilter.call(this);
  this.mergeConfig({
    name: 'AppJanusTracer',
    optional_params: ['debug', 'cacheSize', 'cacheAge', 'endpoint', 'bypass', 'port', 'metrics', 'service_name', 'interval'],
    default_values: {
      'cacheSize': 50000,
      'cacheAge': 60000,
      'endpoint': 'http://localhost:3100/tempo/api/push',
      'metrics': false,
      'service_name': 'pastash-janus',
      'interval': 10000,
      'port': 9090,
      'bypass': true,
      'debug': false
    },
    start_hook: this.start.bind(this)
  });
}

util.inherits(FilterAppJanusTracer, base_filter.BaseFilter);

FilterAppJanusTracer.prototype.start = async function (callback) {
  // Event cache
  var cache = recordCache({
    maxSize: this.cacheSize,
    maxAge: this.cacheAge,
    onStale: false
  });
  this.cache = cache;

  // Session cache
  var sessions = recordCache({
    maxSize: this.cacheSize,
    maxAge: this.cacheAge,
    onStale: false
  });
  this.sessions = sessions;
  this.lru = new QuickLRU({ maxSize: 10000, maxAge: 3600000, onEviction: false });

  if (this.metrics) {
    // Initialize Service
    const options = { port: this.port, startServer: true };
    const exporter = new PrometheusExporter(options);

    // Register the exporter
    this.meter = new MeterProvider({
      exporter,
      interval: this.interval
    }).getMeter(this.service_name)
    this.counters = {};

    // Register counters
    this.counters['s'] = this.meter.createUpDownCounter('sessions', {
      description: 'Session Counters'
    });
    this.counters['e'] = this.meter.createUpDownCounter('events', {
      description: 'Event Counters'
    });

    logger.info('Initialized Janus Prometheus Exporter :' + this.port + '/metrics');
  }
  logger.info('Initialized App Janus Span Tracer with: ' + this.endpoint);
  callback();
};

FilterAppJanusTracer.prototype.process = function (data) {
  // bypass
  if (this.bypass) this.emit('output', data)
  if (!data.message) return;
  var event = {};
  var line = JSON.parse(data.message);
  logger.info('Incoming line', line.type, line.event)
  /* Ignore all other events */
  if (line.type === 128 || line.type === 8 || line.type === 16 || line.type === 32) return;
  logger.info('Filtered to 1, 2, 64', line.type, line.session_id, line.handle_id)
  event = {
    name: line.event.name,
    event: line.event.data.event || line.event.name,
    session_id: line.session_id || line.event.data.id,
    id: line.event.data.id || line.session_id,
    spanId: spanid(),
    timestamp: line.timestamp || nano_now(new Date().getTime())
  }
  /*
  TYPE 1

  Create Session and Destroy Session events are tracked
  */
  if (line.type === 1) {
    event.traceId = event.session_id
    event.duration = 1000
    /* CREATE event */
    if (event.name === "created") {
      // create root span
      this.lru.set(event.session_id, event);
      // start root trace, do not update
      this.sessions.add(event.session_id, just_now(event.timestamp));
      this.sessions.add('uuid_' + event.session_id, event.traceId)
      this.sessions.add('span_' + event.session_id, event.spanId)
      this.sessions.add('parent_' + event.session_id, event.spanId)
      if (this.metrics) this.counters['s'].add(1, line.event);
    /* DESTROY event */
    } else if (event.name === "destroyed") {
      const previous_ts = this.sessions.get(event.session_id, 1)[0] || nano_now(new Date().getTime());
      event.duration = just_now(event.timestamp) - parseInt(previous_ts);
      /* name the event Session */
      event.name = "Session " + event.session_id
      // delete root span
      this.lru.delete(event.session_id);
      // end root trace
      this.sessions.remove(event.session_id);
      this.sessions.remove('uuid_' + event.session_id);
      if (this.metrics) this.counters['s'].add(-1, line.event);
    }

    tracegen(event, this.endpoint)
  /*
  TYPE 2

  Client Attachment and Detachment is tracked
  */
  } else if (line.type === 2) {
    if (!line.event.data) return;
    // session tracing + reset
    event.traceId = this.sessions.get('uuid_' + event.session_id, 1)[0] || line.session_id;
    event.spanId = this.sessions.get('span_' + event.session_id, 1)[0] || spanid();
    var previous_ts = this.sessions.get(event.session_id, 1)[0] || nano_now(new Date().getTime());
    event.duration = just_now(line.timestamp) - parseInt(previous_ts);
    this.sessions.add(event.session_id, just_now(line.timestamp));

    if (event.name === "attached") {
      // session_id, handle_id, opaque_id
    } else if (event.name === "detached") {
      // session_id, handle_id, opaque_id
      this.sessions.remove(event.handle_id);
    }
    event.parentId = this.sessions.get(event.session_id, 1)[0]

    tracegen(event, this.endpoint)
  /*
  TYPE 64

  Users Joining or Leaving Sessions
  */
  } else if (line.type === 64) {
    if (!line.event.data) return;
    // session tracing + reset
    event.traceId = this.sessions.get('uuid_' + event.session_id, 1)[0] || line.session_id;
    event.spanId = this.sessions.get('span_' + event.session_id, 1)[0] || spanid();
    const previous_ts = this.sessions.get(event.session_id, 1)[0] || nano_now(new Date().getTime());
    event.duration = just_now(event.timestamp) - parseInt(previous_ts);
    this.sessions.add(event.session_id, just_now(line.timestamp));

    logger.info("trace 64: ", line)
    if (event.event === "joined") {
      // session_id, handle_id, opaque_id, event.data.id
      // correlate: session_id --> event.data.id
      this.cache.add(event.id, event.session_id);
      this.lru.set(event.id, event.session_id);
      // increase tag counter
      if (this.metrics) this.counters['e'].add(1, line.event.data);
    } else if (event.event === "configured") {
      // session_id, handle_id, opaque_id, event.data.id
    } else if (event.event === "published") {
      // session_id, handle_id, opaque_id, event.data.id
      this.cache.add(event.id, event.session_id);
      this.lru.set(event.id, event.session_id);
    } else if (event.event === "unpublished") {
      // correlate: event.data.id --> session_id
      // event.session_id = this.cache.get(event.id, 1)[0] || false;
      event.session_id = this.lru.get(event.id) || false;
      line.session_id = event.session_id;
    } else if (event.event === "leaving") {
      // correlate: event.data.id --> session_id
      logger.info("missing session id", event.id)
      // event.session_id = this.cache.get(event.id, 1)[0] || false;
      event.session_id = this.lru.get(event.id) || false;
      logger.info("fetched event id", event.session_id)
      line.session_id = event.session_id;
      this.cache.remove(event.id, event.session_id)
      // decrease tag counter
      if (this.metrics) this.counters['e'].add(-1, line.event.data);
    }
    event.parentId = this.sessions.get(event.session_id, 1)[0]
    tracegen(event, this.endpoint)
  }
};

exports.create = function () {
  return new FilterAppJanusTracer();
};

// TODO: replace trace mocker with opentelemetry-js
// Link: https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-exporter-zipkin/README.md

async function tracegen (event, endpoint) {
  // mock a zipkin span
  var trace = [{
    "id": event.spanId || spanid(),
    "traceId": event.traceId.toString(),
    "timestamp": nano_now(event.timestamp),
    "duration": event.duration,
    "name": event.event,
    "localEndpoint": {
      "serviceName": event.name
    }
  }]
  if (event.parentId) { trace[0].parentId = event.parentId }
  if (event.tags) {
    trace[0].tags = event.tags
  } else {
    trace[0].tags = { event: 'hello' }
  }

  logger.info("trace: ", trace);
  // send event to endpoint
  if (endpoint) {
    const response = fetch(endpoint, {
      method: 'POST',
      body: JSON.stringify(trace),
      headers: { 'Content-Type': 'application/json' }
    })
      .then(res => {
        return res
      })
      .catch(err => {
        logger.error(err)
      });
    if (this.debug) logger.info(response);
  }
}
<commit_msg>added logger<commit_after>/* Janus Event Tracer (C) 2022 QXIP BV */

/* eslint-disable camelcase */
/* eslint-disable semi */
/* eslint quotes: 0 */
/* eslint-disable quote-props */
/* eslint-disable dot-notation */

var base_filter = require('@pastash/pastash').base_filter
var util = require('util')
var logger = require('@pastash/pastash').logger

const QuickLRU = require("quick-lru");

const recordCache = require("record-cache");
const fetch = require('cross-fetch');

const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
const { MeterProvider } = require('@opentelemetry/sdk-metrics-base');

function nano_now (date) { return parseInt(date.toString().padEnd(16, '0')) }
function just_now (date) { return nano_now(date || new Date().getTime()) }
function spanid () { return Math.floor(10000000 + Math.random() * 90000000).toString() }

function FilterAppJanusTracer () {
  base_filter.BaseFilter.call(this);
  this.mergeConfig({
    name: 'AppJanusTracer',
    optional_params: ['debug', 'cacheSize', 'cacheAge', 'endpoint', 'bypass', 'port', 'metrics', 'service_name', 'interval'],
    default_values: {
      'cacheSize': 50000,
      'cacheAge': 60000,
      'endpoint': 'http://localhost:3100/tempo/api/push',
      'metrics': false,
      'service_name': 'pastash-janus',
      'interval': 10000,
      'port': 9090,
      'bypass': true,
      'debug': false
    },
    start_hook: this.start.bind(this)
  });
}

util.inherits(FilterAppJanusTracer, base_filter.BaseFilter);

FilterAppJanusTracer.prototype.start = async function (callback) {
  // Event cache
  var cache = recordCache({
    maxSize: this.cacheSize,
    maxAge: this.cacheAge,
    onStale: false
  });
  this.cache = cache;

  // Session cache
  var sessions = recordCache({
    maxSize: this.cacheSize,
    maxAge: this.cacheAge,
    onStale: false
  });
  this.sessions = sessions;
  this.lru = new QuickLRU({ maxSize: 10000, maxAge: 3600000, onEviction: false });

  if (this.metrics) {
    // Initialize Service
    const options = { port: this.port, startServer: true };
    const exporter = new PrometheusExporter(options);

    // Register the exporter
    this.meter = new MeterProvider({
      exporter,
      interval: this.interval
    }).getMeter(this.service_name)
    this.counters = {};

    // Register counters
    this.counters['s'] = this.meter.createUpDownCounter('sessions', {
      description: 'Session Counters'
    });
    this.counters['e'] = this.meter.createUpDownCounter('events', {
      description: 'Event Counters'
    });

    logger.info('Initialized Janus Prometheus Exporter :' + this.port + '/metrics');
  }
  logger.info('Initialized App Janus Span Tracer with: ' + this.endpoint);
  callback();
};

FilterAppJanusTracer.prototype.process = function (data) {
  // bypass
  if (this.bypass) this.emit('output', data)
  if (!data.message) return;
  var event = {};
  var line = JSON.parse(data.message);
  logger.info('Incoming line', line.type, line.event)
  /* Ignore all other events */
  if (line.type === 128 || line.type === 8 || line.type === 16 || line.type === 32) return;
  logger.info('Filtered to 1, 2, 64', line.type, line.session_id, line.handle_id)
  event = {
    name: line.event.name,
    event: line.event.data.event || line.event.name,
    session_id: line.session_id || line.event.data.id,
    id: line.event.data.id || line.session_id,
    spanId: spanid(),
    timestamp: line.timestamp || nano_now(new Date().getTime())
  }
  logger.info('created event', event)
  /*
  TYPE 1

  Create Session and Destroy Session events are tracked
  */
  if (line.type == 1) {
    event.traceId = event.session_id
    event.duration = 1000
    /* CREATE event */
    if (event.name === "created") {
      // create root span
      this.lru.set(event.session_id, event);
      // start root trace, do not update
      this.sessions.add(event.session_id, just_now(event.timestamp));
      this.sessions.add('uuid_' + event.session_id, event.traceId)
      this.sessions.add('span_' + event.session_id, event.spanId)
      this.sessions.add('parent_' + event.session_id, event.spanId)
      if (this.metrics) this.counters['s'].add(1, line.event);
    /* DESTROY event */
    } else if (event.name === "destroyed") {
      const previous_ts = this.sessions.get(event.session_id, 1)[0] || nano_now(new Date().getTime());
      event.duration = just_now(event.timestamp) - parseInt(previous_ts);
      /* name the event Session */
      event.name = "Session " + event.session_id
      // delete root span
      this.lru.delete(event.session_id);
      // end root trace
      this.sessions.remove(event.session_id);
      this.sessions.remove('uuid_' + event.session_id);
      if (this.metrics) this.counters['s'].add(-1, line.event);
    }

    tracegen(event, this.endpoint)
  /*
  TYPE 2

  Client Attachment and Detachment is tracked
  */
  } else if (line.type === 2) {
    if (!line.event.data) return;
    // session tracing + reset
    event.traceId = this.sessions.get('uuid_' + event.session_id, 1)[0] || line.session_id;
    event.spanId = this.sessions.get('span_' + event.session_id, 1)[0] || spanid();
    var previous_ts = this.sessions.get(event.session_id, 1)[0] || nano_now(new Date().getTime());
    event.duration = just_now(line.timestamp) - parseInt(previous_ts);
    this.sessions.add(event.session_id, just_now(line.timestamp));

    if (event.name === "attached") {
      // session_id, handle_id, opaque_id
    } else if (event.name === "detached") {
      // session_id, handle_id, opaque_id
      this.sessions.remove(event.handle_id);
    }
    event.parentId = this.sessions.get(event.session_id, 1)[0]

    tracegen(event, this.endpoint)
  /*
  TYPE 64

  Users Joining or Leaving Sessions
  */
  } else if (line.type === 64) {
    if (!line.event.data) return;
    // session tracing + reset
    event.traceId = this.sessions.get('uuid_' + event.session_id, 1)[0] || line.session_id;
    event.spanId = this.sessions.get('span_' + event.session_id, 1)[0] || spanid();
    const previous_ts = this.sessions.get(event.session_id, 1)[0] || nano_now(new Date().getTime());
    event.duration = just_now(event.timestamp) - parseInt(previous_ts);
    this.sessions.add(event.session_id, just_now(line.timestamp));

    logger.info("trace 64: ", line)
    if (event.event === "joined") {
      // session_id, handle_id, opaque_id, event.data.id
      // correlate: session_id --> event.data.id
      this.cache.add(event.id, event.session_id);
      this.lru.set(event.id, event.session_id);
      // increase tag counter
      if (this.metrics) this.counters['e'].add(1, line.event.data);
    } else if (event.event === "configured") {
      // session_id, handle_id, opaque_id, event.data.id
    } else if (event.event === "published") {
      // session_id, handle_id, opaque_id, event.data.id
      this.cache.add(event.id, event.session_id);
      this.lru.set(event.id, event.session_id);
    } else if (event.event === "unpublished") {
      // correlate: event.data.id --> session_id
      // event.session_id = this.cache.get(event.id, 1)[0] || false;
      event.session_id = this.lru.get(event.id) || false;
      line.session_id = event.session_id;
    } else if (event.event === "leaving") {
      // correlate: event.data.id --> session_id
      logger.info("missing session id", event.id)
      // event.session_id = this.cache.get(event.id, 1)[0] || false;
      event.session_id = this.lru.get(event.id) || false;
      logger.info("fetched event id", event.session_id)
      line.session_id = event.session_id;
      this.cache.remove(event.id, event.session_id)
      // decrease tag counter
      if (this.metrics) this.counters['e'].add(-1, line.event.data);
    }
    event.parentId = this.sessions.get(event.session_id, 1)[0]
    tracegen(event, this.endpoint)
  }
};

exports.create = function () {
  return new FilterAppJanusTracer();
};

// TODO: replace trace mocker with opentelemetry-js
// Link: https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-exporter-zipkin/README.md

async function tracegen (event, endpoint) {
  // mock a zipkin span
  var trace = [{
    "id": event.spanId || spanid(),
    "traceId": event.traceId.toString(),
    "timestamp": nano_now(event.timestamp),
    "duration": event.duration,
    "name": event.event,
    "localEndpoint": {
      "serviceName": event.name
    }
  }]
  if (event.parentId) { trace[0].parentId = event.parentId }
  if (event.tags) {
    trace[0].tags = event.tags
  } else {
    trace[0].tags = { event: 'hello' }
  }

  logger.info("trace: ", trace);
  // send event to endpoint
  if (endpoint) {
    const response = fetch(endpoint, {
      method: 'POST',
      body: JSON.stringify(trace),
      headers: { 'Content-Type': 'application/json' }
    })
      .then(res => {
        return res
      })
      .catch(err => {
        logger.error(err)
      });
    if (this.debug) logger.info(response);
  }
}
<|endoftext|>


<commit_before>var path = require('path');
var _ = require('underscore');
var q = require('q');

var config = require('./config');
var utils = require('./utils');
var parse = require('./parsers');
var crawl = exports;

crawl.invoke = function(filename, options, fn) {
  if (arguments.length < 3) {
    fn = options;
    options = {};
  }

  return q
    .all([filename, parse.file(filename, options)])
    .spread(fn);
};

crawl.each = function(dirname, options, fn) {
  if (arguments.length < 3) {
    fn = options;
    options = {};
  }

  return parse.dir(dirname, options).then(function(metadata) {
    options = _(options).clone();
    options.defaults = metadata;

    return q
      .all([
        crawl.each.file(dirname, options, fn),
        crawl.each.dir(dirname, options, fn)])
      .then();
  });
};

crawl.each.file = function(dirname, options, fn) {
  if (arguments.length < 3) {
    fn = options;
    options = {};
  }

  return utils.listFiles(dirname).then(function(filenames) {
    filenames = _.difference(filenames, config.manifests.map(function(m) {
      return path.join(dirname, m);
    }));

    var i = -1;
    var n = filenames.length;
    var results = [];

    while (++i < n) {
      results.push(crawl.invoke(filenames[i], options, fn));
    }

    return q.all(results);
  });
};

crawl.each.dir = function(dirname, options, fn) {
  if (arguments.length < 3) {
    fn = options;
    options = {};
  }

  return utils.listDirs(dirname).then(function(dirnames) {
    var i = -1;
    var n = dirnames.length;
    var results = [];

    while (++i < n) {
      results.push(crawl.each(dirnames[i], options, fn));
    }

    return q.all(results);
  });
};

crawl.map = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      results.push(fn(filename, metadata));
    })
    .then(function() { return q.all(results); });
};

crawl.all = function(dirname, options, fn) {
  return crawl.map(dirname, options, function(filename, metadata) {
    return {
      filename: filename,
      metadata: metadata
    };
  });
};

crawl.all.metadata = function(dirname, options, fn) {
  return crawl.map(dirname, options, function(filename, metadata) {
    return metadata;
  });
};

crawl.filter = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      if (fn(filename, metadata)) {
        results.push({
          filename: filename,
          metadata: metadata
        });
      }
    })
    .then(function() { return q.all(results); });
};

crawl.filter.filenames = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      if (fn(filename, metadata)) {
        results.push(filename);
      }
    })
    .then(function() { return q.all(results); });
};

crawl.filter.metadata = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      if (fn(filename, metadata)) {
        results.push(metadata);
      }
    })
    .then(function() { return q.all(results); });
};
<commit_msg>Change crawl.each.file and .dir to be internal methods (._file and ._dir)<commit_after>var path = require('path');
var _ = require('underscore');
var q = require('q');

var config = require('./config');
var utils = require('./utils');
var parse = require('./parsers');
var crawl = exports;

crawl.invoke = function(filename, options, fn) {
  if (arguments.length < 3) {
    fn = options;
    options = {};
  }

  return q
    .all([filename, parse.file(filename, options)])
    .spread(fn);
};

crawl.each = function(dirname, options, fn) {
  if (arguments.length < 3) {
    fn = options;
    options = {};
  }

  return parse.dir(dirname, options).then(function(metadata) {
    options = _(options).clone();
    options.defaults = metadata;

    return q
      .all([
        crawl.each._file(dirname, options, fn),
        crawl.each._dir(dirname, options, fn)])
      .then();
  });
};

crawl.each._file = function(dirname, options, fn) {
  return utils.listFiles(dirname).then(function(filenames) {
    filenames = _.difference(filenames, config.manifests.map(function(m) {
      return path.join(dirname, m);
    }));

    var i = -1;
    var n = filenames.length;
    var results = [];

    while (++i < n) {
      results.push(crawl.invoke(filenames[i], options, fn));
    }

    return q.all(results);
  });
};

crawl.each._dir = function(dirname, options, fn) {
  return utils.listDirs(dirname).then(function(dirnames) {
    var i = -1;
    var n = dirnames.length;
    var results = [];

    while (++i < n) {
      results.push(crawl.each(dirnames[i], options, fn));
    }

    return q.all(results);
  });
};

crawl.map = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      results.push(fn(filename, metadata));
    })
    .then(function() { return q.all(results); });
};

crawl.all = function(dirname, options, fn) {
  return crawl.map(dirname, options, function(filename, metadata) {
    return {
      filename: filename,
      metadata: metadata
    };
  });
};

crawl.all.metadata = function(dirname, options, fn) {
  return crawl.map(dirname, options, function(filename, metadata) {
    return metadata;
  });
};

crawl.filter = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      if (fn(filename, metadata)) {
        results.push({
          filename: filename,
          metadata: metadata
        });
      }
    })
    .then(function() { return q.all(results); });
};

crawl.filter.filenames = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      if (fn(filename, metadata)) {
        results.push(filename);
      }
    })
    .then(function() { return q.all(results); });
};

crawl.filter.metadata = function(dirname, options, fn) {
  var results = [];

  return crawl
    .each(dirname, options, function(filename, metadata) {
      if (fn(filename, metadata)) {
        results.push(metadata);
      }
    })
    .then(function() { return q.all(results); });
};
<|endoftext|>