<commit_before>import PatchBuffer from './patch-buffer';
import Hunk from './hunk';
import File, {nullFile} from './file';
import Patch, {TOO_LARGE, EXPANDED} from './patch';
import {Unchanged, Addition, Deletion, NoNewline} from './region';
import FilePatch from './file-patch';
import MultiFilePatch from './multi-file-patch';

export const DEFAULT_OPTIONS = {
  // Number of lines after which we consider the diff "large"
  // TODO: Set this based on performance measurements
  largeDiffThreshold: 100,

  // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE)
  renderStatusOverrides: {},
};

export function buildFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();

  let filePatch;
  if (diffs.length === 0) {
    filePatch = emptyDiffFilePatch();
  } else if (diffs.length === 1) {
    filePatch = singleDiffFilePatch(diffs[0], patchBuffer, opts);
  } else if (diffs.length === 2) {
    filePatch = dualDiffFilePatch(diffs[0], diffs[1], patchBuffer, opts);
  } else {
    throw new Error(`Unexpected number of diffs: ${diffs.length}`);
  }

  return new MultiFilePatch({patchBuffer, filePatches: [filePatch]});
}

export function buildMultiFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();
  const byPath = new Map();
  const actions = [];

  let index = 0;
  for (const diff of diffs) {
    const thePath = diff.oldPath || diff.newPath;

    if (diff.status === 'added' || diff.status === 'deleted') {
      // Potential paired diff. Either a symlink deletion + content addition or a symlink addition +
      // content deletion.
      const otherHalf = byPath.get(thePath);
      if (otherHalf) {
        // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes.
        const [otherDiff, otherIndex] = otherHalf;
        actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, patchBuffer, opts);
        byPath.delete(thePath);
      } else {
        // The first half we've seen.
        byPath.set(thePath, [diff, index]);
        index++;
      }
    } else {
      actions[index] = () => singleDiffFilePatch(diff, patchBuffer, opts);
      index++;
    }
  }

  // Populate unpaired diffs that looked like they could be part of a pair, but weren't.
  for (const [unpairedDiff, originalIndex] of byPath.values()) {
    actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, patchBuffer, opts);
  }

  const filePatches = actions.map(action => action());
  return new MultiFilePatch({patchBuffer, filePatches});
}

function emptyDiffFilePatch() {
  return FilePatch.createNull();
}

function singleDiffFilePatch(diff, patchBuffer, opts) {
  const wasSymlink = diff.oldMode === File.modes.SYMLINK;
  const isSymlink = diff.newMode === File.modes.SYMLINK;

  let oldSymlink = null;
  let newSymlink = null;
  if (wasSymlink && !isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (!wasSymlink && isSymlink) {
    newSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (wasSymlink && isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
    newSymlink = diff.hunks[0].lines[2].slice(1);
  }

  const oldFile = diff.oldPath !== null || diff.oldMode !== null
    ? new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink})
    : nullFile;
  const newFile = diff.newPath !== null || diff.newMode !== null
    ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink})
    : nullFile;

  const renderStatusOverride =
    (oldFile.isPresent() && opts.renderStatusOverrides[oldFile.getPath()]) ||
    (newFile.isPresent() && opts.renderStatusOverrides[newFile.getPath()]) ||
    undefined;

  const renderStatus = renderStatusOverride ||
    (isDiffLarge([diff], opts) && TOO_LARGE) ||
    EXPANDED;

  if (renderStatus === TOO_LARGE) {
    const insertRow = patchBuffer.getBuffer().getLastRow();
    const patchMarker = patchBuffer.markPosition(
      Patch.layerName,
      [insertRow, 0],
      {invalidate: 'never', exclusive: false},
    );

    return FilePatch.createDelayedFilePatch(
      oldFile, newFile, patchMarker, TOO_LARGE,
      () => {
        const [hunks] = buildHunks(diff, patchBuffer);
        return new Patch({status: diff.status, hunks, marker: patchMarker});
      },
    );
  } else {
    const [hunks, patchMarker] = buildHunks(diff, patchBuffer);
    const patch = new Patch({status: diff.status, hunks, marker: patchMarker});

    return new FilePatch(oldFile, newFile, patch);
  }
}

function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) {
  let modeChangeDiff, contentChangeDiff;
  if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) {
    modeChangeDiff = diff1;
    contentChangeDiff = diff2;
  } else {
    modeChangeDiff = diff2;
    contentChangeDiff = diff1;
  }

  const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath;
  const symlink = modeChangeDiff.hunks[0].lines[0].slice(1);

  let status;
  let oldMode, newMode;
  let oldSymlink = null;
  let newSymlink = null;
  if (modeChangeDiff.status === 'added') {
    // contents were deleted and replaced with symlink
    status = 'deleted';
    oldMode = contentChangeDiff.oldMode;
    newMode = modeChangeDiff.newMode;
    newSymlink = symlink;
  } else if (modeChangeDiff.status === 'deleted') {
    // contents were added after symlink was deleted
    status = 'added';
    oldMode = modeChangeDiff.oldMode;
    oldSymlink = symlink;
    newMode = contentChangeDiff.newMode;
  } else {
    throw new Error(`Invalid mode change diff status: ${modeChangeDiff.status}`);
  }

  const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink});
  const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink});

  if (isDiffLarge([diff1, diff2], opts)) {
    // TODO: do something with large diff too
  }

  const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer);
  const patch = new Patch({status, hunks, marker: patchMarker});

  return new FilePatch(oldFile, newFile, patch);
}

const CHANGEKIND = {
  '+': Addition,
  '-': Deletion,
  ' ': Unchanged,
  '\\': NoNewline,
};

function buildHunks(diff, patchBuffer) {
  const inserter = patchBuffer.createInserterAtEnd()
    .keepBefore(patchBuffer.findAllMarkers({endPosition: patchBuffer.getInsertionPoint()}));

  let patchMarker = null;
  let firstHunk = true;
  const hunks = [];

  // Separate multiple patches on the same buffer with an unmarked newline
  if (patchBuffer.getBuffer().getLength() > 0) {
    console.log('inserting newline to separate Patches');
    inserter.insert('\n');
  }

  inserter.markWhile(Patch.layerName, () => {
    for (const rawHunk of diff.hunks) {
      let firstRegion = true;
      const regions = [];

      // Separate hunks with an unmarked newline
      if (firstHunk) {
        firstHunk = false;
      } else {
        console.log('inserting newline to separate Hunks');
        inserter.insert('\n');
      }

      inserter.markWhile(Hunk.layerName, () => {
        let currentRegionText = '';
        let CurrentRegionKind = null;

        function finishRegion() {
          if (currentRegionText === '' || CurrentRegionKind === null) {
            return;
          }

          // Separate regions with an unmarked newline
          if (firstRegion) {
            firstRegion = false;
          } else {
            console.log('inserting newline to separate Regions');
            inserter.insert('\n');
          }

          inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, {
            invalidate: 'never',
            exclusive: false,
            callback: (function(_regions, _CurrentRegionKind) {
              return regionMarker => { _regions.push(new _CurrentRegionKind(regionMarker)); };
            })(regions, CurrentRegionKind),
          });
        }

        for (const rawLine of rawHunk.lines) {
          const NextRegionKind = CHANGEKIND[rawLine[0]];
          if (NextRegionKind === undefined) {
            throw new Error(`Unknown diff status character: "${rawLine[0]}"`);
          }
          const nextLine = rawLine.slice(1);

          if (NextRegionKind === CurrentRegionKind) {
            if (currentRegionText !== '') {
              currentRegionText += '\n';
            }
            currentRegionText += nextLine;
            continue;
          } else {
            finishRegion();

            CurrentRegionKind = NextRegionKind;
            currentRegionText = nextLine;
          }
        }
        finishRegion();
      }, {
        invalidate: 'never',
        exclusive: false,
        callback: (function(_hunks, _rawHunk, _regions) {
          return hunkMarker => {
            _hunks.push(new Hunk({
              oldStartRow: _rawHunk.oldStartLine,
              newStartRow: _rawHunk.newStartLine,
              oldRowCount: _rawHunk.oldLineCount,
              newRowCount: _rawHunk.newLineCount,
              sectionHeading: _rawHunk.heading,
              marker: hunkMarker,
              regions: _regions,
            }));
          };
        })(hunks, rawHunk, regions),
      });
    }
  }, {
    invalidate: 'never',
    exclusive: false,
    callback: marker => { patchMarker = marker; },
  });
  inserter.apply();

  return [hunks, patchMarker];
}

function isDiffLarge(diffs, opts) {
  const size = diffs.reduce((diffSizeCounter, diff) => {
    return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => {
      return hunkSizeCounter + hunk.lines.length;
    }, 0);
  }, 0);

  return size > opts.largeDiffThreshold;
}
<commit_msg>Remove console.logs :eyes:<commit_after>import PatchBuffer from './patch-buffer';
import Hunk from './hunk';
import File, {nullFile} from './file';
import Patch, {TOO_LARGE, EXPANDED} from './patch';
import {Unchanged, Addition, Deletion, NoNewline} from './region';
import FilePatch from './file-patch';
import MultiFilePatch from './multi-file-patch';

export const DEFAULT_OPTIONS = {
  // Number of lines after which we consider the diff "large"
  // TODO: Set this based on performance measurements
  largeDiffThreshold: 100,

  // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE)
  renderStatusOverrides: {},
};

export function buildFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();

  let filePatch;
  if (diffs.length === 0) {
    filePatch = emptyDiffFilePatch();
  } else if (diffs.length === 1) {
    filePatch = singleDiffFilePatch(diffs[0], patchBuffer, opts);
  } else if (diffs.length === 2) {
    filePatch = dualDiffFilePatch(diffs[0], diffs[1], patchBuffer, opts);
  } else {
    throw new Error(`Unexpected number of diffs: ${diffs.length}`);
  }

  return new MultiFilePatch({patchBuffer, filePatches: [filePatch]});
}

export function buildMultiFilePatch(diffs, options) {
  const opts = {...DEFAULT_OPTIONS, ...options};

  const patchBuffer = new PatchBuffer();
  const byPath = new Map();
  const actions = [];

  let index = 0;
  for (const diff of diffs) {
    const thePath = diff.oldPath || diff.newPath;

    if (diff.status === 'added' || diff.status === 'deleted') {
      // Potential paired diff. Either a symlink deletion + content addition or a symlink addition +
      // content deletion.
      const otherHalf = byPath.get(thePath);
      if (otherHalf) {
        // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes.
        const [otherDiff, otherIndex] = otherHalf;
        actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, patchBuffer, opts);
        byPath.delete(thePath);
      } else {
        // The first half we've seen.
        byPath.set(thePath, [diff, index]);
        index++;
      }
    } else {
      actions[index] = () => singleDiffFilePatch(diff, patchBuffer, opts);
      index++;
    }
  }

  // Populate unpaired diffs that looked like they could be part of a pair, but weren't.
  for (const [unpairedDiff, originalIndex] of byPath.values()) {
    actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, patchBuffer, opts);
  }

  const filePatches = actions.map(action => action());
  return new MultiFilePatch({patchBuffer, filePatches});
}

function emptyDiffFilePatch() {
  return FilePatch.createNull();
}

function singleDiffFilePatch(diff, patchBuffer, opts) {
  const wasSymlink = diff.oldMode === File.modes.SYMLINK;
  const isSymlink = diff.newMode === File.modes.SYMLINK;

  let oldSymlink = null;
  let newSymlink = null;
  if (wasSymlink && !isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (!wasSymlink && isSymlink) {
    newSymlink = diff.hunks[0].lines[0].slice(1);
  } else if (wasSymlink && isSymlink) {
    oldSymlink = diff.hunks[0].lines[0].slice(1);
    newSymlink = diff.hunks[0].lines[2].slice(1);
  }

  const oldFile = diff.oldPath !== null || diff.oldMode !== null
    ? new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink})
    : nullFile;
  const newFile = diff.newPath !== null || diff.newMode !== null
    ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink})
    : nullFile;

  const renderStatusOverride =
    (oldFile.isPresent() && opts.renderStatusOverrides[oldFile.getPath()]) ||
    (newFile.isPresent() && opts.renderStatusOverrides[newFile.getPath()]) ||
    undefined;

  const renderStatus = renderStatusOverride ||
    (isDiffLarge([diff], opts) && TOO_LARGE) ||
    EXPANDED;

  if (renderStatus === TOO_LARGE) {
    const insertRow = patchBuffer.getBuffer().getLastRow();
    const patchMarker = patchBuffer.markPosition(
      Patch.layerName,
      [insertRow, 0],
      {invalidate: 'never', exclusive: false},
    );

    return FilePatch.createDelayedFilePatch(
      oldFile, newFile, patchMarker, TOO_LARGE,
      () => {
        const [hunks] = buildHunks(diff, patchBuffer);
        return new Patch({status: diff.status, hunks, marker: patchMarker});
      },
    );
  } else {
    const [hunks, patchMarker] = buildHunks(diff, patchBuffer);
    const patch = new Patch({status: diff.status, hunks, marker: patchMarker});

    return new FilePatch(oldFile, newFile, patch);
  }
}

function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) {
  let modeChangeDiff, contentChangeDiff;
  if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) {
    modeChangeDiff = diff1;
    contentChangeDiff = diff2;
  } else {
    modeChangeDiff = diff2;
    contentChangeDiff = diff1;
  }

  const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath;
  const symlink = modeChangeDiff.hunks[0].lines[0].slice(1);

  let status;
  let oldMode, newMode;
  let oldSymlink = null;
  let newSymlink = null;
  if (modeChangeDiff.status === 'added') {
    // contents were deleted and replaced with symlink
    status = 'deleted';
    oldMode = contentChangeDiff.oldMode;
    newMode = modeChangeDiff.newMode;
    newSymlink = symlink;
  } else if (modeChangeDiff.status === 'deleted') {
    // contents were added after symlink was deleted
    status = 'added';
    oldMode = modeChangeDiff.oldMode;
    oldSymlink = symlink;
    newMode = contentChangeDiff.newMode;
  } else {
    throw new Error(`Invalid mode change diff status: ${modeChangeDiff.status}`);
  }

  const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink});
  const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink});

  if (isDiffLarge([diff1, diff2], opts)) {
    // TODO: do something with large diff too
  }

  const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer);
  const patch = new Patch({status, hunks, marker: patchMarker});

  return new FilePatch(oldFile, newFile, patch);
}

const CHANGEKIND = {
  '+': Addition,
  '-': Deletion,
  ' ': Unchanged,
  '\\': NoNewline,
};

function buildHunks(diff, patchBuffer) {
  const inserter = patchBuffer.createInserterAtEnd()
    .keepBefore(patchBuffer.findAllMarkers({endPosition: patchBuffer.getInsertionPoint()}));

  let patchMarker = null;
  let firstHunk = true;
  const hunks = [];

  // Separate multiple patches on the same buffer with an unmarked newline
  if (patchBuffer.getBuffer().getLength() > 0) {
    inserter.insert('\n');
  }

  inserter.markWhile(Patch.layerName, () => {
    for (const rawHunk of diff.hunks) {
      let firstRegion = true;
      const regions = [];

      // Separate hunks with an unmarked newline
      if (firstHunk) {
        firstHunk = false;
      } else {
        inserter.insert('\n');
      }

      inserter.markWhile(Hunk.layerName, () => {
        let currentRegionText = '';
        let CurrentRegionKind = null;

        function finishRegion() {
          if (currentRegionText === '' || CurrentRegionKind === null) {
            return;
          }

          // Separate regions with an unmarked newline
          if (firstRegion) {
            firstRegion = false;
          } else {
            inserter.insert('\n');
          }

          inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, {
            invalidate: 'never',
            exclusive: false,
            callback: (function(_regions, _CurrentRegionKind) {
              return regionMarker => { _regions.push(new _CurrentRegionKind(regionMarker)); };
            })(regions, CurrentRegionKind),
          });
        }

        for (const rawLine of rawHunk.lines) {
          const NextRegionKind = CHANGEKIND[rawLine[0]];
          if (NextRegionKind === undefined) {
            throw new Error(`Unknown diff status character: "${rawLine[0]}"`);
          }
          const nextLine = rawLine.slice(1);

          if (NextRegionKind === CurrentRegionKind) {
            if (currentRegionText !== '') {
              currentRegionText += '\n';
            }
            currentRegionText += nextLine;
            continue;
          } else {
            finishRegion();

            CurrentRegionKind = NextRegionKind;
            currentRegionText = nextLine;
          }
        }
        finishRegion();
      }, {
        invalidate: 'never',
        exclusive: false,
        callback: (function(_hunks, _rawHunk, _regions) {
          return hunkMarker => {
            _hunks.push(new Hunk({
              oldStartRow: _rawHunk.oldStartLine,
              newStartRow: _rawHunk.newStartLine,
              oldRowCount: _rawHunk.oldLineCount,
              newRowCount: _rawHunk.newLineCount,
              sectionHeading: _rawHunk.heading,
              marker: hunkMarker,
              regions: _regions,
            }));
          };
        })(hunks, rawHunk, regions),
      });
    }
  }, {
    invalidate: 'never',
    exclusive: false,
    callback: marker => { patchMarker = marker; },
  });
  inserter.apply();

  return [hunks, patchMarker];
}

function isDiffLarge(diffs, opts) {
  const size = diffs.reduce((diffSizeCounter, diff) => {
    return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => {
      return hunkSizeCounter + hunk.lines.length;
    }, 0);
  }, 0);

  return size > opts.largeDiffThreshold;
}
<|endoftext|>


<commit_before>'use strict';

const EventEmitter = require('events').EventEmitter;
const randomString = require('random-string');
const logger = require('../../logger');
const InvalidStateError = require('../../errors').InvalidStateError;
const RTCSessionDescription = require('../RTCSessionDescription');
const sdpUtils = require('./sdpUtils');

const NEGOTIATION_NEEDED_DELAY = 1500;

class RTCPeerConnectionCommon extends EventEmitter
{
	constructor(options)
	{
		super();
		this.setMaxListeners(Infinity);

		this._logger = logger(`webrtc:RTCPeerConnection:${options.peer.name}`);
		this._logger.debug('constructor() [options:%o]', options);

		// Options.
		this._options = options;

		// Peer instance.
		this._peer = options.peer;

		// Local RTCSessionDescription.
		this._localDescription = null;

		// Remote RTCSessionDescription.
		this._remoteDescription = null;

		// Initial signaling state.
		this._signalingState = 'stable';

		// Busy flag.
		this._busy = false;

		// Whether the initial offer has been created.
		this._initialOfferCreated = false;

		// Whether first SDP O/A has been done.
		this._initialNegotiationDone = false;

		// Negotiation needed flag.
		this._negotiationNeeded = false;

		// Negotiation timer to collect them all.
		this._negotiationNeededTimer = null;

		// createOffer options for the initial offer.
		this._initialCreateOfferOptions =
		{
			offerToReceiveAudio : 1,
			offerToReceiveVideo : 1
		};

		// SDP global fields.
		this._sdpGlobalFields =
		{
			id      : randomString({ letters: false, length: 16 }),
			version : 0
		};

		// Handle peer 'close' event.
		this._peer.on('close', (error) =>
		{
			this.emit('close', error);
		});

		// Handle peer 'newrtpsender' event once first SDP O/A is done.
		this._peer.on('newrtpsender', () =>
		{
			this._logger.debug('peer "newrtpsender" event');

			// Set negotiation flag (unless the initial offer was not yet created).
			if (this._initialOfferCreated)
				this._negotiationNeeded = true;

			// Try to renegotiate if the initial SDP O/A was already done.
			if (this._initialNegotiationDone)
				this._mayRenegotiate();
		});
	}

	get closed()
	{
		return this._peer.closed;
	}

	get peer()
	{
		return this._peer;
	}

	get localDescription()
	{
		return this._localDescription;
	}

	get remoteDescription()
	{
		return this._remoteDescription;
	}

	get signalingState()
	{
		return this._signalingState;
	}

	close()
	{
		this._logger.debug('close()');

		// Cancel the negotiation timer.
		clearTimeout(this._negotiationNeededTimer);

		this._peer.close();
	}

	/**
	 * Set the peer's capabilities.
	 * @param {string} sdp - SDP offer generated by the remote peer.
	 */
	setCapabilities(sdp)
	{
		this._logger.debug('setCapabilities()');

		if (this.closed)
			throw new InvalidStateError('closed');

		if (this._peer.capabilities)
			throw new InvalidStateError('capabilities are ready set');

		let desc;

		try
		{
			desc = new RTCSessionDescription({ type: 'offer', sdp: sdp });
		}
		catch (error)
		{
			return Promise.reject(new Error(`invalid capabilities SDP: ${error}`));
		}

		// Set busy flag.
		this._busy = true;

		return Promise.resolve()
			// First set peer's capabilities.
			.then(() =>
			{
				let capabilities = sdpUtils.descToCapabilities(desc.parsed);

				return this._peer.setCapabilities(capabilities);
			})
			// Then create a Transport instance.
			.then(() =>
			{
				return this._peer.createTransport(this._options.transportOptions);
			})
			.then(() =>
			{
				this._logger.debug('setCapabilities() | succeed');

				// Unset busy flag.
				this._busy = false;
			})
			.catch((error) =>
			{
				this._logger.error('setCapabilities() | failed: %s', error);

				// Unset busy flag.
				this._busy = false;

				throw error;
			});
	}

	createOffer(options)
	{
		this._logger.debug('createOffer() [options:%o]');

		// Just for the initial SDP O/A.
		if (!this._initialNegotiationDone && options)
		{
			if (options.hasOwnProperty('offerToReceiveAudio'))
				this._initialCreateOfferOptions.offerToReceiveAudio = options.offerToReceiveAudio;

			if (options.hasOwnProperty('offerToReceiveVideo'))
				this._initialCreateOfferOptions.offerToReceiveVideo = options.offerToReceiveVideo;
		}

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (!this._peer.capabilities)
			return Promise.reject(new InvalidStateError('capabilities not yet set'));

		if (this._signalingState !== 'stable')
		{
			return Promise.reject(new InvalidStateError(`invalid signaling state [signalingState:${this._signalingState}]`));
		}

		// Set busy flag.
		this._busy = true;

		return this._setUpOffer()
			.then(() =>
			{
				this._logger.debug('createOffer() | succeed');

				// Unset busy flag.
				this._busy = false;

				// Create an offer.
				let localDescription = this._createLocalDescription('offer');

				// Update flag.
				this._initialOfferCreated = true;

				// Resolve with it.
				return Promise.resolve(localDescription);
			})
			.catch((error) =>
			{
				this._logger.error('createOffer() | failed: %s', error);

				// Unset busy flag.
				this._busy = false;

				throw error;
			});
	}

	createAnswer()
	{
		this._logger.debug('createAnswer()');

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (this._signalingState !== 'have-remote-offer')
		{
			return Promise.reject(new InvalidStateError(`invalid signaling state [signalingState:${this._signalingState}]`));
		}

		// Create an answer.
		let localDescription = this._createLocalDescription('answer');

		// Resolve with it.
		return Promise.resolve(localDescription);
	}

	/**
	 * NOTE: This method assumes that the given desc is the one previously
	 * created by createOffer() or createAnswer().
	 */
	setLocalDescription(desc)
	{
		this._logger.debug('setLocalDescription()');

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (!this._peer.capabilities)
			return Promise.reject(new InvalidStateError('capabilities not yet set'));

		let remoteDescription = desc;
		let newSignalingState;

		switch (remoteDescription.type)
		{
			case 'offer':
			{
				if (this._signalingState !== 'stable')
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));

				newSignalingState = 'have-local-offer';

				break;
			}

			case 'answer':
			{
				if (this._signalingState !== 'have-remote-offer')
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));

				newSignalingState = 'stable';

				break;
			}

			default:
			{
				return Promise.reject(new Error(`invalid RTCSessionDescription.type [type:${remoteDescription.type}]`));
			}
		}

		// Update the local description.
		this._localDescription = desc;

		// Update signaling state.
		this._setSignalingState(newSignalingState);

		// Resolve.
		return Promise.resolve();
	}

	setRemoteDescription(desc)
	{
		this._logger.debug('setRemoteDescription()');

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (!this._peer.capabilities)
			return Promise.reject(new InvalidStateError('capabilities not yet set'));

		let remoteDescription;

		try
		{
			remoteDescription = new RTCSessionDescription(desc);
		}
		catch (error)
		{
			return Promise.reject(new Error(`invalid RTCSessionDescriptionInit: ${error}`));
		}

		switch (remoteDescription.type)
		{
			case 'offer':
			{
				if (this._signalingState !== 'stable')
				{
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));
				}

				// Initial offer received from the client.
				if (!this._remoteDescription)
				{
					return Promise.reject(new Error('initial offer from the endpoint not supported'));
				}
				// Re-offer received from the client.
				else
				{
					return Promise.reject(new Error('re-offer from the endpoint not supported'));
				}
			}

			case 'answer':
			{
				if (this._signalingState !== 'have-local-offer')
				{
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));
				}

				// Initial answer received from the client.
				if (!this._remoteDescription)
				{
					// Set busy flag.
					this._busy = true;

					return this._handleRemoteInitialAnswer(remoteDescription)
						.then(() =>
						{
							this._logger.debug('setRemoteDescription() | succeed');

							// Unset busy flag.
							this._busy = false;

							// Set remote description.
							this._remoteDescription = remoteDescription;

							// Initial SDP O/A done.
							this._initialNegotiationDone = true;

							// Update signaling state.
							this._setSignalingState('stable');
						})
						.catch((error) =>
						{
							this._logger.error('setRemoteDescription() | failed: %s', error);

							// Unset busy flag.
							this._busy = false;

							throw error;
						});
				}
				// Re-answer received from the client.
				else
				{
					// Set busy flag.
					this._busy = true;

					return this._handleRemoteReAnswer(remoteDescription)
						.then(() =>
						{
							this._logger.debug('setRemoteDescription() | succeed');

							// Unset busy flag.
							this._busy = false;

							// Set remote description.
							this._remoteDescription = remoteDescription;

							// Update signaling state.
							this._setSignalingState('stable');
						})
						.catch((error) =>
						{
							this._logger.error('setRemoteDescription() | failed: %s', error);

							// Unset busy flag.
							this._busy = false;

							throw error;
						});
				}
			}

			default:
			{
				return Promise.reject(new Error(`invalid RTCSessionDescription.type [type:${remoteDescription.type}]`));
			}
		}
	}

	_mayRenegotiate()
	{
		// If already scheduled, ignore.
		if (this._negotiationNeededTimer)
			return;

		// If busy, ignore.
		if (this._busy)
			return;

		// Ignore if capabilities are not yet set.
		if (!this._peer.capabilities)
			return;

		// Ignore if signalingState is not 'stable'.
		if (this._signalingState !== 'stable')
			return;

		// Schedule the task.
		this._negotiationNeededTimer = setTimeout(() =>
		{
			this._negotiationNeededTimer = null;

			// Ignore if closed.
			if (this.closed)
				return;

			// If busy, ignore.
			if (this._busy)
				return;

			if (this._negotiationNeeded)
			{
				// Reset flag.
				this._negotiationNeeded = false;

				// Emit event.
				this.emit('negotiationneeded');
			}
		}, NEGOTIATION_NEEDED_DELAY);
	}

	_setSignalingState(signalingState)
	{
		this._logger.debug('_setSignalingState() [signalingState:%s]', signalingState);

		if (this._signalingState === signalingState)
			return;

		this._signalingState = signalingState;

		// Emit 'signalingstatechange'.
		this.emit('signalingstatechange');

		// Things may have happened while busy, so check it.
		if (this._signalingState === 'stable')
		{
			process.nextTick(() =>
			{
				this._mayRenegotiate();
			});
		}
	}

	/**
	 * To be implemented by child classes.
	 */
	_setUpOffer()
	{

	}

	/**
	 * To be implemented by child classes.
	 */
	_handleRemoteInitialAnswer(remoteDescription) // eslint-disable-line no-unused-vars
	{

	}

	/**
	 * To be implemented by child classes.
	 */
	_handleRemoteReAnswer(remoteDescription) // eslint-disable-line no-unused-vars
	{

	}

	/**
	 * To be implemented by child classes.
	 */
	_createLocalDescription(type) // eslint-disable-line no-unused-vars
	{

	}
}

module.exports = RTCPeerConnectionCommon;
<commit_msg>WebRTC PeerConnection: reduce NEGOTIATION_NEEDED_DELAY to 500ms<commit_after>'use strict';

const EventEmitter = require('events').EventEmitter;
const randomString = require('random-string');
const logger = require('../../logger');
const InvalidStateError = require('../../errors').InvalidStateError;
const RTCSessionDescription = require('../RTCSessionDescription');
const sdpUtils = require('./sdpUtils');

const NEGOTIATION_NEEDED_DELAY = 500;

class RTCPeerConnectionCommon extends EventEmitter
{
	constructor(options)
	{
		super();
		this.setMaxListeners(Infinity);

		this._logger = logger(`webrtc:RTCPeerConnection:${options.peer.name}`);
		this._logger.debug('constructor() [options:%o]', options);

		// Options.
		this._options = options;

		// Peer instance.
		this._peer = options.peer;

		// Local RTCSessionDescription.
		this._localDescription = null;

		// Remote RTCSessionDescription.
		this._remoteDescription = null;

		// Initial signaling state.
		this._signalingState = 'stable';

		// Busy flag.
		this._busy = false;

		// Whether the initial offer has been created.
		this._initialOfferCreated = false;

		// Whether first SDP O/A has been done.
		this._initialNegotiationDone = false;

		// Negotiation needed flag.
		this._negotiationNeeded = false;

		// Negotiation timer to collect them all.
		this._negotiationNeededTimer = null;

		// createOffer options for the initial offer.
		this._initialCreateOfferOptions =
		{
			offerToReceiveAudio : 1,
			offerToReceiveVideo : 1
		};

		// SDP global fields.
		this._sdpGlobalFields =
		{
			id      : randomString({ letters: false, length: 16 }),
			version : 0
		};

		// Handle peer 'close' event.
		this._peer.on('close', (error) =>
		{
			this.emit('close', error);
		});

		// Handle peer 'newrtpsender' event once first SDP O/A is done.
		this._peer.on('newrtpsender', () =>
		{
			this._logger.debug('peer "newrtpsender" event');

			// Set negotiation flag (unless the initial offer was not yet created).
			if (this._initialOfferCreated)
				this._negotiationNeeded = true;

			// Try to renegotiate if the initial SDP O/A was already done.
			if (this._initialNegotiationDone)
				this._mayRenegotiate();
		});
	}

	get closed()
	{
		return this._peer.closed;
	}

	get peer()
	{
		return this._peer;
	}

	get localDescription()
	{
		return this._localDescription;
	}

	get remoteDescription()
	{
		return this._remoteDescription;
	}

	get signalingState()
	{
		return this._signalingState;
	}

	close()
	{
		this._logger.debug('close()');

		// Cancel the negotiation timer.
		clearTimeout(this._negotiationNeededTimer);

		this._peer.close();
	}

	/**
	 * Set the peer's capabilities.
	 * @param {string} sdp - SDP offer generated by the remote peer.
	 */
	setCapabilities(sdp)
	{
		this._logger.debug('setCapabilities()');

		if (this.closed)
			throw new InvalidStateError('closed');

		if (this._peer.capabilities)
			throw new InvalidStateError('capabilities are ready set');

		let desc;

		try
		{
			desc = new RTCSessionDescription({ type: 'offer', sdp: sdp });
		}
		catch (error)
		{
			return Promise.reject(new Error(`invalid capabilities SDP: ${error}`));
		}

		// Set busy flag.
		this._busy = true;

		return Promise.resolve()
			// First set peer's capabilities.
			.then(() =>
			{
				let capabilities = sdpUtils.descToCapabilities(desc.parsed);

				return this._peer.setCapabilities(capabilities);
			})
			// Then create a Transport instance.
			.then(() =>
			{
				return this._peer.createTransport(this._options.transportOptions);
			})
			.then(() =>
			{
				this._logger.debug('setCapabilities() | succeed');

				// Unset busy flag.
				this._busy = false;
			})
			.catch((error) =>
			{
				this._logger.error('setCapabilities() | failed: %s', error);

				// Unset busy flag.
				this._busy = false;

				throw error;
			});
	}

	createOffer(options)
	{
		this._logger.debug('createOffer() [options:%o]');

		// Just for the initial SDP O/A.
		if (!this._initialNegotiationDone && options)
		{
			if (options.hasOwnProperty('offerToReceiveAudio'))
				this._initialCreateOfferOptions.offerToReceiveAudio = options.offerToReceiveAudio;

			if (options.hasOwnProperty('offerToReceiveVideo'))
				this._initialCreateOfferOptions.offerToReceiveVideo = options.offerToReceiveVideo;
		}

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (!this._peer.capabilities)
			return Promise.reject(new InvalidStateError('capabilities not yet set'));

		if (this._signalingState !== 'stable')
		{
			return Promise.reject(new InvalidStateError(`invalid signaling state [signalingState:${this._signalingState}]`));
		}

		// Set busy flag.
		this._busy = true;

		return this._setUpOffer()
			.then(() =>
			{
				this._logger.debug('createOffer() | succeed');

				// Unset busy flag.
				this._busy = false;

				// Create an offer.
				let localDescription = this._createLocalDescription('offer');

				// Update flag.
				this._initialOfferCreated = true;

				// Resolve with it.
				return Promise.resolve(localDescription);
			})
			.catch((error) =>
			{
				this._logger.error('createOffer() | failed: %s', error);

				// Unset busy flag.
				this._busy = false;

				throw error;
			});
	}

	createAnswer()
	{
		this._logger.debug('createAnswer()');

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (this._signalingState !== 'have-remote-offer')
		{
			return Promise.reject(new InvalidStateError(`invalid signaling state [signalingState:${this._signalingState}]`));
		}

		// Create an answer.
		let localDescription = this._createLocalDescription('answer');

		// Resolve with it.
		return Promise.resolve(localDescription);
	}

	/**
	 * NOTE: This method assumes that the given desc is the one previously
	 * created by createOffer() or createAnswer().
	 */
	setLocalDescription(desc)
	{
		this._logger.debug('setLocalDescription()');

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (!this._peer.capabilities)
			return Promise.reject(new InvalidStateError('capabilities not yet set'));

		let remoteDescription = desc;
		let newSignalingState;

		switch (remoteDescription.type)
		{
			case 'offer':
			{
				if (this._signalingState !== 'stable')
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));

				newSignalingState = 'have-local-offer';

				break;
			}

			case 'answer':
			{
				if (this._signalingState !== 'have-remote-offer')
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));

				newSignalingState = 'stable';

				break;
			}

			default:
			{
				return Promise.reject(new Error(`invalid RTCSessionDescription.type [type:${remoteDescription.type}]`));
			}
		}

		// Update the local description.
		this._localDescription = desc;

		// Update signaling state.
		this._setSignalingState(newSignalingState);

		// Resolve.
		return Promise.resolve();
	}

	setRemoteDescription(desc)
	{
		this._logger.debug('setRemoteDescription()');

		if (this._busy)
			return Promise.reject(new InvalidStateError('busy'));

		if (!this._peer.capabilities)
			return Promise.reject(new InvalidStateError('capabilities not yet set'));

		let remoteDescription;

		try
		{
			remoteDescription = new RTCSessionDescription(desc);
		}
		catch (error)
		{
			return Promise.reject(new Error(`invalid RTCSessionDescriptionInit: ${error}`));
		}

		switch (remoteDescription.type)
		{
			case 'offer':
			{
				if (this._signalingState !== 'stable')
				{
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));
				}

				// Initial offer received from the client.
				if (!this._remoteDescription)
				{
					return Promise.reject(new Error('initial offer from the endpoint not supported'));
				}
				// Re-offer received from the client.
				else
				{
					return Promise.reject(new Error('re-offer from the endpoint not supported'));
				}
			}

			case 'answer':
			{
				if (this._signalingState !== 'have-local-offer')
				{
					return Promise.reject(new InvalidStateError(`invalid RTCSessionDescription.type [type:${remoteDescription.type}, signalingState:${this._signalingState}]`));
				}

				// Initial answer received from the client.
				if (!this._remoteDescription)
				{
					// Set busy flag.
					this._busy = true;

					return this._handleRemoteInitialAnswer(remoteDescription)
						.then(() =>
						{
							this._logger.debug('setRemoteDescription() | succeed');

							// Unset busy flag.
							this._busy = false;

							// Set remote description.
							this._remoteDescription = remoteDescription;

							// Initial SDP O/A done.
							this._initialNegotiationDone = true;

							// Update signaling state.
							this._setSignalingState('stable');
						})
						.catch((error) =>
						{
							this._logger.error('setRemoteDescription() | failed: %s', error);

							// Unset busy flag.
							this._busy = false;

							throw error;
						});
				}
				// Re-answer received from the client.
				else
				{
					// Set busy flag.
					this._busy = true;

					return this._handleRemoteReAnswer(remoteDescription)
						.then(() =>
						{
							this._logger.debug('setRemoteDescription() | succeed');

							// Unset busy flag.
							this._busy = false;

							// Set remote description.
							this._remoteDescription = remoteDescription;

							// Update signaling state.
							this._setSignalingState('stable');
						})
						.catch((error) =>
						{
							this._logger.error('setRemoteDescription() | failed: %s', error);

							// Unset busy flag.
							this._busy = false;

							throw error;
						});
				}
			}

			default:
			{
				return Promise.reject(new Error(`invalid RTCSessionDescription.type [type:${remoteDescription.type}]`));
			}
		}
	}

	_mayRenegotiate()
	{
		// If already scheduled, ignore.
		if (this._negotiationNeededTimer)
			return;

		// If busy, ignore.
		if (this._busy)
			return;

		// Ignore if capabilities are not yet set.
		if (!this._peer.capabilities)
			return;

		// Ignore if signalingState is not 'stable'.
		if (this._signalingState !== 'stable')
			return;

		// Schedule the task.
		this._negotiationNeededTimer = setTimeout(() =>
		{
			this._negotiationNeededTimer = null;

			// Ignore if closed.
			if (this.closed)
				return;

			// If busy, ignore.
			if (this._busy)
				return;

			if (this._negotiationNeeded)
			{
				// Reset flag.
				this._negotiationNeeded = false;

				// Emit event.
				this.emit('negotiationneeded');
			}
		}, NEGOTIATION_NEEDED_DELAY);
	}

	_setSignalingState(signalingState)
	{
		this._logger.debug('_setSignalingState() [signalingState:%s]', signalingState);

		if (this._signalingState === signalingState)
			return;

		this._signalingState = signalingState;

		// Emit 'signalingstatechange'.
		this.emit('signalingstatechange');

		// Things may have happened while busy, so check it.
		if (this._signalingState === 'stable')
		{
			process.nextTick(() =>
			{
				this._mayRenegotiate();
			});
		}
	}

	/**
	 * To be implemented by child classes.
	 */
	_setUpOffer()
	{

	}

	/**
	 * To be implemented by child classes.
	 */
	_handleRemoteInitialAnswer(remoteDescription) // eslint-disable-line no-unused-vars
	{

	}

	/**
	 * To be implemented by child classes.
	 */
	_handleRemoteReAnswer(remoteDescription) // eslint-disable-line no-unused-vars
	{

	}

	/**
	 * To be implemented by child classes.
	 */
	_createLocalDescription(type) // eslint-disable-line no-unused-vars
	{

	}
}

module.exports = RTCPeerConnectionCommon;
<|endoftext|>


<commit_before>/**
 * Functional Programming Framework of Calculation in Javascript.
 * https://github.com/KerneLab/Canal
 */
(function()
{
	var ROOT = (typeof global === "object" && global) || this;

	function Pond()
	{
		this.downstream = null;
	}
	Pond.prototype.begin = function() // Void
	{
		if (this.downstream != null)
		{
			this.downstream.begin();
		}
	};
	Pond.prototype.accept = function(data) // Boolean
	{
	};
	Pond.prototype.done = function() // Void
	{
		if (this.downstream != null)
		{
			this.downstream.done();
		}
	};

	function Desilter()
	{
		this.sediment = undefined;
	}
	Desilter.prototype = new Pond();
	Desilter.prototype.settling = null; // () -> new Sediment
	Desilter.prototype.begin = function()
	{
		if (this.settling != null)
		{
			this.settle(this.settling());
		}
		if (this.downstream != null)
		{
			this.downstream.begin();
		}
	};
	Desilter.prototype.settle = function() // access Sediment
	{
		if (arguments.length > 0)
		{
			this.sediment = arguments[0];
			return this;
		}
		else
		{
			return this.sediment;
		}
	};

	function Grouper()
	{
	}
	Grouper.prototype = new Desilter();
	Grouper.prototype.settling = function()
	{
		return {};
	};
	Grouper.prototype.keyOf = function(d)
	{
	};
	Grouper.prototype.valOf = function(d)
	{
		return d;
	};
	Grouper.prototype.accept = function(d)
	{
		var key = this.keyOf(d);
		var settle = this.settle();
		var group = settle[key];
		if (group == null)
		{
			group = [];
			settle[key] = group;
		}
		group.push(this.valOf(d));
		return true;
	};
	Grouper.prototype.done = function()
	{
		if (this.downstream != null)
		{
			var settle = this.settle();

			for (group in settle)
			{
				if (!this.downstream.accept([ group, settle[group] ]))
				{
					break;
				}
			}

			this.downstream.done();
		}
	};

	// Operators

	function Operator()
	{
		this.upstream = null;
	}
	Operator.prototype.newPond = function() // () -> Pond
	{
		return new Pond();
	};

	// Intermediate Operators

	function DistinctOp(cmp)
	{
		cmp = cmp != null // (a,b) -> 0(=)
		? cmp : function(a, b)
		{
			return a == b ? 0 : 1;
		};

		function DistinctPond()
		{
		}
		DistinctPond.prototype = new Desilter();
		DistinctPond.prototype.settling = function()
		{
			return [];
		};
		DistinctPond.prototype.accept = function(d)
		{
			var found = false;
			var settle = this.settle();
			for (i in settle)
			{
				if (cmp(settle[i], d) == 0)
				{
					found = true;
					break;
				}
			}
			if (!found)
			{
				settle.push(d);
			}
			return true;
		};
		DistinctPond.prototype.done = function()
		{
			if (this.downstream != null)
			{
				var settle = this.settle();
				for (i in settle)
				{
					if (!this.downstream.accept(settle[i]))
					{
						break;
					}
				}
				this.downstream.done();
			}
		};

		this.newPond = function()
		{
			return new DistinctPond();
		};
	}
	DistinctOp.prototype = new Operator();

	function FilterOp(pred)
	{
		function FilterPond()
		{
		}
		FilterPond.prototype = new Pond();
		FilterPond.prototype.accept = function(d)
		{
			if (pred(d))
			{
				return this.downstream.accept(d);
			}
			else
			{
				return true;
			}
		};

		this.newPond = function()
		{
			return new FilterPond();
		};
	}
	FilterOp.prototype = new Operator();

	function FlatMapOp(fn)
	{
		function FlatMapPond()
		{
		}
		FlatMapPond.prototype = new Pond();
		FlatMapPond.prototype.accept = function(d)
		{
			var data = fn(d);
			if (data instanceof Array)
			{
				for (i in data)
				{
					if (!this.downstream.accept(data[i]))
					{
						return false;
					}
				}
				return true;
			}
			else
			{
				return this.downstream.accept(data);
			}
		};

		this.newPond = function()
		{
			return new FlatMapPond();
		};
	}
	FlatMapOp.prototype = new Operator();

	function ForeachOp(fn) // (data) -> Void
	{
		function ForeachPond()
		{
		}
		ForeachPond.prototype = new Pond();
		ForeachPond.prototype.accept = function(d)
		{
			fn(d);
			return this.downstream.accept(d);
		};

		this.newPond = function()
		{
			return new ForeachPond();
		}
	}
	ForeachOp.prototype = new Operator();

	function GroupOp(key, val) // (data) -> key, [(data) -> val]
	{
		function GroupPond()
		{
		}
		GroupPond.prototype = new Grouper();
		GroupPond.prototype.keyOf = function(d)
		{
			return key == null ? d[0] : key(d);
		};
		GroupPond.prototype.valOf = function(d)
		{
			return val == null ? d[1] : val(d);
		};

		this.newPond = function()
		{
			return new GroupPond();
		};
	}
	GroupOp.prototype = new Operator();

	function JoinOp(canal, keyL, keyR)
	{
		keyL = keyL != null ? keyL : function(d)
		{
			return d[0];
		};
		keyR = keyR != null ? keyR : function(d)
		{
			return d[0];
		};

		function JoinPond()
		{

		}
		JoinPond.prototype = new Grouper();
		JoinPond.prototype.keyOf = function(d)
		{
			return keyL(d);
		};
		JoinPond.prototype.valOf = function(d)
		{
			return d[1];
		};
		JoinPond.prototype.done = function()
		{
			if (this.downstream != null)
			{
				var left = this.settle();
				console.log(left);
				var rightData = canal.groupBy(keyR, null).collect();
				var right = {};
				for (i in rightData)
				{
					var d = rightData[i];
					right[d[0]] = d[1];
				}

				out: for (k in left)
				{
					var ls = left[k][0];
					if (ls != null)
					{
						var rs = right[k];
						if (rs != null)
						{
							for (l in ls)
							{
								for (r in rs)
								{
									if (!this.downstream
											.accept([ ls[l], rs[r] ]))
									{
										break out;
									}
								}
							}
						}
					}
				}

				this.downstream.done();
			}
		};

		this.newPond = function()
		{
			return new JoinPond();
		};
	}
	JoinOp.prototype = new Operator();

	function MapOp(fn)
	{
		function MapPond()
		{
		}
		MapPond.prototype = new Pond();
		MapPond.prototype.accept = function(d)
		{
			return this.downstream.accept(fn(d));
		};

		this.newPond = function()
		{
			return new MapPond();
		};
	}
	MapOp.prototype = new Operator();

	function MapValuesOp(fn) // ([val..]) -> Value
	{
		function MapValuesPond()
		{
		}
		MapValuesPond.prototype = new Pond();
		MapValuesPond.prototype.accept = function(d)
		{
			return this.downstream.accept([ d[0], fn(d[1]) ]);
		};

		this.newPond = function()
		{
			return new MapValuesPond();
		};
	}
	MapValuesOp.prototype = new Operator();

	function SortOp(cmp) // (a,b) -> 0(=) -1(<) 1(>)
	{
		function SortPond()
		{
		}
		SortPond.prototype = new Desilter();
		SortPond.prototype.settling = function()
		{
			return [];
		};
		SortPond.prototype.accept = function(d)
		{
			this.settle().push(d);
			return true;
		};
		SortPond.prototype.done = function(d)
		{
			if (this.downstream != null)
			{
				var settle = this.settle();
				if (cmp != null)
				{
					settle.sort(cmp);
				}
				else
				{
					settle.sort();
				}
				for (i in settle)
				{
					if (!this.downstream.accept(settle[i]))
					{
						break;
					}
				}
				this.downstream.done();
			}
		};

		this.newPond = function()
		{
			return new SortPond();
		};
	}
	SortOp.prototype = new Operator();

	// Terminate Operators

	function Terminal()
	{
	}
	Terminal.prototype = new Desilter();
	Terminal.prototype.get = function()
	{
		return this.settle();
	};

	function CollectOp()
	{
		function CollectPond()
		{
		}
		CollectPond.prototype = new Terminal();
		CollectPond.prototype.settling = function()
		{
			return [];
		}
		CollectPond.prototype.accept = function(d)
		{
			this.settle().push(d);
			return true;
		};

		this.newPond = function()
		{
			return new CollectPond();
		}
	}
	CollectOp.prototype = new Operator();

	function ReduceOp(init, reducer) // (res,data) -> res
	{
		function ReducePond()
		{
		}
		ReducePond.prototype = new Terminal();
		ReducePond.prototype.settling = function()
		{
			return init;
		};
		ReducePond.prototype.accept = function(d)
		{
			this.settle(reducer(this.settle(), d));
			return true;
		};

		this.newPond = function()
		{
			return new ReducePond();
		};
	}
	ReduceOp.prototype = new Operator();

	function TakeOp(num)
	{
		function TakePond()
		{
		}
		TakePond.prototype = new Terminal();
		TakePond.prototype.settling = function()
		{
			return [];
		};
		TakePond.prototype.accept = function(d)
		{
			if (this.settle().length < num)
			{
				this.settle().push(d);
				return true;
			}
			else
			{
				return false;
			}
		};

		this.newPond = function()
		{
			return new TakePond();
		};
	}
	TakeOp.prototype = new Operator();

	function Canal()
	{
		var self = this;
		var tail = null;
		var data = null;

		var chain = function(pond)
		{
			for (prev = tail; prev != null; prev = prev.upstream)
			{
				var temp = prev.newPond();
				temp.downstream = pond;
				pond = temp;
			}
			return pond;
		};

		var calc = function(entr, data)
		{
			entr.begin();

			for (i in data)
			{
				if (!entr.accept(data[i]))
				{
					break;
				}
			}

			entr.done();
		};

		this.evaluate = function(op) // T -> Value
		{
			if (op != null)
			{
				var data = arguments.length > 1 ? arguments[1] : this.source();

				if (data != null)
				{
					var pond = op.newPond();

					calc(chain(pond), data);

					return pond.get();
				}
				else
				{
					return undefined;
				}
			}
			else
			{
				return undefined;
			}
		};

		this.add = function(op)
		{
			op.upstream = tail;
			tail = op;
			return self;
		};

		this.source = function() // access data
		{
			if (arguments.length > 0)
			{
				data = arguments[0];
				return self;
			}
			else
			{
				return data;
			}
		};

		// Intermediate Operations

		this.distinct = function()
		{
			return this.add(new DistinctOp(arguments[0]));
		}

		this.filter = function(pred)
		{
			return this.add(new FilterOp(pred));
		};

		this.flatMap = function(fn)
		{
			return this.add(new FlatMapOp(fn));
		};

		this.foreach = function(fn)
		{
			return this.add(new ForeachOp(fn));
		};

		this.groupBy = function()
		{
			return this.add(new GroupOp(arguments[0], arguments[1]));
		};

		this.join = function(canal)
		{
			return this.add(new JoinOp(canal, arguments[1], arguments[2]));
		};

		this.map = function(fn)
		{
			return this.add(new MapOp(fn));
		};

		this.mapValues = function(fn)
		{
			return this.add(new MapValuesOp(fn));
		};

		this.sort = function()
		{
			return this.add(new SortOp(arguments.length > 0 ? arguments[0]
					: null));
		};

		// Terminate Operations

		this.collect = function()
		{
			return this.evaluate(new CollectOp());
		};

		this.count = function()
		{
			return this.map(function(d)
			{
				return 1;
			}).evaluate(new ReduceOp(0, function(res, dat)
			{
				return res + dat;
			}));
		};

		this.reduce = function(init, reducer)
		{
			return this.evaluate(new ReduceOp(init, reducer));
		};

		this.take = function(num)
		{
			return this.evaluate(new TakeOp(num));
		};
	}

	Canal.of = function(data)
	{
		return new Canal().source(data);
	};

	ROOT.Canal = Canal;

}.call(this));
<commit_msg>Remove log in JoinOp<commit_after>/**
 * Functional Programming Framework of Calculation in Javascript.
 * https://github.com/KerneLab/Canal
 */
(function()
{
	var ROOT = (typeof global === "object" && global) || this;

	function Pond()
	{
		this.downstream = null;
	}
	Pond.prototype.begin = function() // Void
	{
		if (this.downstream != null)
		{
			this.downstream.begin();
		}
	};
	Pond.prototype.accept = function(data) // Boolean
	{
	};
	Pond.prototype.done = function() // Void
	{
		if (this.downstream != null)
		{
			this.downstream.done();
		}
	};

	function Desilter()
	{
		this.sediment = undefined;
	}
	Desilter.prototype = new Pond();
	Desilter.prototype.settling = null; // () -> new Sediment
	Desilter.prototype.begin = function()
	{
		if (this.settling != null)
		{
			this.settle(this.settling());
		}
		if (this.downstream != null)
		{
			this.downstream.begin();
		}
	};
	Desilter.prototype.settle = function() // access Sediment
	{
		if (arguments.length > 0)
		{
			this.sediment = arguments[0];
			return this;
		}
		else
		{
			return this.sediment;
		}
	};

	function Grouper()
	{
	}
	Grouper.prototype = new Desilter();
	Grouper.prototype.settling = function()
	{
		return {};
	};
	Grouper.prototype.keyOf = function(d)
	{
	};
	Grouper.prototype.valOf = function(d)
	{
		return d;
	};
	Grouper.prototype.accept = function(d)
	{
		var key = this.keyOf(d);
		var settle = this.settle();
		var group = settle[key];
		if (group == null)
		{
			group = [];
			settle[key] = group;
		}
		group.push(this.valOf(d));
		return true;
	};
	Grouper.prototype.done = function()
	{
		if (this.downstream != null)
		{
			var settle = this.settle();

			for (group in settle)
			{
				if (!this.downstream.accept([ group, settle[group] ]))
				{
					break;
				}
			}

			this.downstream.done();
		}
	};

	// Operators

	function Operator()
	{
		this.upstream = null;
	}
	Operator.prototype.newPond = function() // () -> Pond
	{
		return new Pond();
	};

	// Intermediate Operators

	function DistinctOp(cmp)
	{
		cmp = cmp != null // (a,b) -> 0(=)
		? cmp : function(a, b)
		{
			return a == b ? 0 : 1;
		};

		function DistinctPond()
		{
		}
		DistinctPond.prototype = new Desilter();
		DistinctPond.prototype.settling = function()
		{
			return [];
		};
		DistinctPond.prototype.accept = function(d)
		{
			var found = false;
			var settle = this.settle();
			for (i in settle)
			{
				if (cmp(settle[i], d) == 0)
				{
					found = true;
					break;
				}
			}
			if (!found)
			{
				settle.push(d);
			}
			return true;
		};
		DistinctPond.prototype.done = function()
		{
			if (this.downstream != null)
			{
				var settle = this.settle();
				for (i in settle)
				{
					if (!this.downstream.accept(settle[i]))
					{
						break;
					}
				}
				this.downstream.done();
			}
		};

		this.newPond = function()
		{
			return new DistinctPond();
		};
	}
	DistinctOp.prototype = new Operator();

	function FilterOp(pred)
	{
		function FilterPond()
		{
		}
		FilterPond.prototype = new Pond();
		FilterPond.prototype.accept = function(d)
		{
			if (pred(d))
			{
				return this.downstream.accept(d);
			}
			else
			{
				return true;
			}
		};

		this.newPond = function()
		{
			return new FilterPond();
		};
	}
	FilterOp.prototype = new Operator();

	function FlatMapOp(fn)
	{
		function FlatMapPond()
		{
		}
		FlatMapPond.prototype = new Pond();
		FlatMapPond.prototype.accept = function(d)
		{
			var data = fn(d);
			if (data instanceof Array)
			{
				for (i in data)
				{
					if (!this.downstream.accept(data[i]))
					{
						return false;
					}
				}
				return true;
			}
			else
			{
				return this.downstream.accept(data);
			}
		};

		this.newPond = function()
		{
			return new FlatMapPond();
		};
	}
	FlatMapOp.prototype = new Operator();

	function ForeachOp(fn) // (data) -> Void
	{
		function ForeachPond()
		{
		}
		ForeachPond.prototype = new Pond();
		ForeachPond.prototype.accept = function(d)
		{
			fn(d);
			return this.downstream.accept(d);
		};

		this.newPond = function()
		{
			return new ForeachPond();
		}
	}
	ForeachOp.prototype = new Operator();

	function GroupOp(key, val) // (data) -> key, [(data) -> val]
	{
		function GroupPond()
		{
		}
		GroupPond.prototype = new Grouper();
		GroupPond.prototype.keyOf = function(d)
		{
			return key == null ? d[0] : key(d);
		};
		GroupPond.prototype.valOf = function(d)
		{
			return val == null ? d[1] : val(d);
		};

		this.newPond = function()
		{
			return new GroupPond();
		};
	}
	GroupOp.prototype = new Operator();

	function JoinOp(canal, keyL, keyR)
	{
		keyL = keyL != null ? keyL : function(d)
		{
			return d[0];
		};
		keyR = keyR != null ? keyR : function(d)
		{
			return d[0];
		};

		function JoinPond()
		{

		}
		JoinPond.prototype = new Grouper();
		JoinPond.prototype.keyOf = function(d)
		{
			return keyL(d);
		};
		JoinPond.prototype.valOf = function(d)
		{
			return d[1];
		};
		JoinPond.prototype.done = function()
		{
			if (this.downstream != null)
			{
				var left = this.settle();
				var rightData = canal.groupBy(keyR, null).collect();
				var right = {};
				for (i in rightData)
				{
					var d = rightData[i];
					right[d[0]] = d[1];
				}

				out: for (k in left)
				{
					var ls = left[k];
					if (ls != null)
					{
						var rs = right[k];
						if (rs != null)
						{
							for (l in ls)
							{
								for (r in rs)
								{
									if (!this.downstream
											.accept([ ls[l], rs[r] ]))
									{
										break out;
									}
								}
							}
						}
					}
				}

				this.downstream.done();
			}
		};

		this.newPond = function()
		{
			return new JoinPond();
		};
	}
	JoinOp.prototype = new Operator();

	function MapOp(fn)
	{
		function MapPond()
		{
		}
		MapPond.prototype = new Pond();
		MapPond.prototype.accept = function(d)
		{
			return this.downstream.accept(fn(d));
		};

		this.newPond = function()
		{
			return new MapPond();
		};
	}
	MapOp.prototype = new Operator();

	function MapValuesOp(fn) // ([val..]) -> Value
	{
		function MapValuesPond()
		{
		}
		MapValuesPond.prototype = new Pond();
		MapValuesPond.prototype.accept = function(d)
		{
			return this.downstream.accept([ d[0], fn(d[1]) ]);
		};

		this.newPond = function()
		{
			return new MapValuesPond();
		};
	}
	MapValuesOp.prototype = new Operator();

	function SortOp(cmp) // (a,b) -> 0(=) -1(<) 1(>)
	{
		function SortPond()
		{
		}
		SortPond.prototype = new Desilter();
		SortPond.prototype.settling = function()
		{
			return [];
		};
		SortPond.prototype.accept = function(d)
		{
			this.settle().push(d);
			return true;
		};
		SortPond.prototype.done = function(d)
		{
			if (this.downstream != null)
			{
				var settle = this.settle();
				if (cmp != null)
				{
					settle.sort(cmp);
				}
				else
				{
					settle.sort();
				}
				for (i in settle)
				{
					if (!this.downstream.accept(settle[i]))
					{
						break;
					}
				}
				this.downstream.done();
			}
		};

		this.newPond = function()
		{
			return new SortPond();
		};
	}
	SortOp.prototype = new Operator();

	// Terminate Operators

	function Terminal()
	{
	}
	Terminal.prototype = new Desilter();
	Terminal.prototype.get = function()
	{
		return this.settle();
	};

	function CollectOp()
	{
		function CollectPond()
		{
		}
		CollectPond.prototype = new Terminal();
		CollectPond.prototype.settling = function()
		{
			return [];
		}
		CollectPond.prototype.accept = function(d)
		{
			this.settle().push(d);
			return true;
		};

		this.newPond = function()
		{
			return new CollectPond();
		}
	}
	CollectOp.prototype = new Operator();

	function ReduceOp(init, reducer) // (res,data) -> res
	{
		function ReducePond()
		{
		}
		ReducePond.prototype = new Terminal();
		ReducePond.prototype.settling = function()
		{
			return init;
		};
		ReducePond.prototype.accept = function(d)
		{
			this.settle(reducer(this.settle(), d));
			return true;
		};

		this.newPond = function()
		{
			return new ReducePond();
		};
	}
	ReduceOp.prototype = new Operator();

	function TakeOp(num)
	{
		function TakePond()
		{
		}
		TakePond.prototype = new Terminal();
		TakePond.prototype.settling = function()
		{
			return [];
		};
		TakePond.prototype.accept = function(d)
		{
			if (this.settle().length < num)
			{
				this.settle().push(d);
				return true;
			}
			else
			{
				return false;
			}
		};

		this.newPond = function()
		{
			return new TakePond();
		};
	}
	TakeOp.prototype = new Operator();

	function Canal()
	{
		var self = this;
		var tail = null;
		var data = null;

		var chain = function(pond)
		{
			for (prev = tail; prev != null; prev = prev.upstream)
			{
				var temp = prev.newPond();
				temp.downstream = pond;
				pond = temp;
			}
			return pond;
		};

		var calc = function(entr, data)
		{
			entr.begin();

			for (i in data)
			{
				if (!entr.accept(data[i]))
				{
					break;
				}
			}

			entr.done();
		};

		this.evaluate = function(op) // T -> Value
		{
			if (op != null)
			{
				var data = arguments.length > 1 ? arguments[1] : this.source();

				if (data != null)
				{
					var pond = op.newPond();

					calc(chain(pond), data);

					return pond.get();
				}
				else
				{
					return undefined;
				}
			}
			else
			{
				return undefined;
			}
		};

		this.add = function(op)
		{
			op.upstream = tail;
			tail = op;
			return self;
		};

		this.source = function() // access data
		{
			if (arguments.length > 0)
			{
				data = arguments[0];
				return self;
			}
			else
			{
				return data;
			}
		};

		// Intermediate Operations

		this.distinct = function()
		{
			return this.add(new DistinctOp(arguments[0]));
		}

		this.filter = function(pred)
		{
			return this.add(new FilterOp(pred));
		};

		this.flatMap = function(fn)
		{
			return this.add(new FlatMapOp(fn));
		};

		this.foreach = function(fn)
		{
			return this.add(new ForeachOp(fn));
		};

		this.groupBy = function()
		{
			return this.add(new GroupOp(arguments[0], arguments[1]));
		};

		this.join = function(canal)
		{
			return this.add(new JoinOp(canal, arguments[1], arguments[2]));
		};

		this.map = function(fn)
		{
			return this.add(new MapOp(fn));
		};

		this.mapValues = function(fn)
		{
			return this.add(new MapValuesOp(fn));
		};

		this.sort = function()
		{
			return this.add(new SortOp(arguments.length > 0 ? arguments[0]
					: null));
		};

		// Terminate Operations

		this.collect = function()
		{
			return this.evaluate(new CollectOp());
		};

		this.count = function()
		{
			return this.map(function(d)
			{
				return 1;
			}).evaluate(new ReduceOp(0, function(res, dat)
			{
				return res + dat;
			}));
		};

		this.reduce = function(init, reducer)
		{
			return this.evaluate(new ReduceOp(init, reducer));
		};

		this.take = function(num)
		{
			return this.evaluate(new TakeOp(num));
		};
	}

	Canal.of = function(data)
	{
		return new Canal().source(data);
	};

	ROOT.Canal = Canal;

}.call(this));
<|endoftext|>


<commit_before>var createTableView = (function() {
  var render = function(elem) {
    var tpl = $.getTemplate(
        "views/datastore_table",
        "zbase_datastore_tables_create_table_tpl");

    var columns = $(".columns tbody", tpl);

    $.onClick($(".add_column", tpl), function() {
      renderColumnRow(columns);
    });

    renderColumnRow(columns);
    renderColumnRow(columns);

    $.replaceContent(elem, tpl);

    $(".create_table .table input").focus();
  };

  var renderColumnRow = function(elem) {
    var tpl = $.getTemplate(
        "views/datastore_table",
        "zbase_datastore_tables_create_table_column_tpl");

    var nested_columns_tr = $("tr.nested", tpl);
    var nested_columns = $("tr.nested .columns", tpl);

    $("z-dropdown.type", tpl).addEventListener("change", function() {
      if (this.getValue() == "object") {
        nested_columns_tr.classList.remove("hidden");
        nested_columns.innerHTML = "";
        renderColumnRow(nested_columns);
        renderColumnRow(nested_columns);
      } else {
        nested_columns_tr.classList.add("hidden");
      }
    }, false);

    $.onClick($(".add_column", nested_columns_tr), function() {
      renderColumnRow(nested_columns);
    });

    elem.appendChild(tpl);
  };

  return {
    render: render
  }
})();
<commit_msg>get nested table schema<commit_after>var createTableView = (function() {
  var render = function(elem) {
    var tpl = $.getTemplate(
        "views/datastore_table",
        "zbase_datastore_tables_create_table_tpl");

    var columns = $(".columns tbody", tpl);

    $.onClick($(".add_column", tpl), function() {
      renderColumnRow(columns);
    });

    renderColumnRow(columns);
    renderColumnRow(columns);

    $.onClick($("button.submit", tpl), createTable);

    $.replaceContent(elem, tpl);

    $(".create_table .table input").focus();
  };

  var renderColumnRow = function(elem) {
    var tpl = $.getTemplate(
        "views/datastore_table",
        "zbase_datastore_tables_create_table_column_tpl");

    var nested_columns_tr = $("tr.nested", tpl);
    var nested_columns = $("tr.nested .columns", tpl);

    $("z-dropdown.type", tpl).addEventListener("change", function() {
      if (this.getValue() == "object") {
        nested_columns_tr.classList.remove("hidden");
        nested_columns.innerHTML = "";
        renderColumnRow(nested_columns);
        renderColumnRow(nested_columns);
      } else {
        nested_columns_tr.classList.add("hidden");
      }
    }, false);

    $.onClick($(".add_column", nested_columns_tr), function() {
      renderColumnRow(nested_columns);
    });

    elem.appendChild(tpl);
  };

  var createTable = function() {
    var table_name_input = $(".create_table .table input");
    if (table_name_input.value == 0) {
      $(".create_table .table .error_note.table_name").classList.remove("hidden");
      table_name_input.classList.add("error");
      table_name_input.focus();
      return;
    }

    var json = {
      table_name: table_name_input.value,
      table_type: $(".create_table .table z-dropdown.table_type").getValue(),
      schema: {
        columns: []
      },
      update: false
    };

    json.schema.columns = getColumnsSchema(
      document.querySelectorAll(".create_table .main > .column"));

    //set column ids
    indexColumns(json.schema.columns, 1);
    console.log(json);
  };

  var indexColumns = function(columns, id) {
    for (var i = 0; i < columns.length; i++) {
      columns[i].id = id;
      id++;

      if (columns[i].schema && columns[i].schema.columns) {
        id = indexColumns(columns[i].schema.columns, id);
      }
    }

    return id;
  };

  var getColumnsSchema = function(columns) {
    var schema = [];

    for (var i = 0; i < columns.length; i++) {
      var c_input = $(".selection input", columns[i]);
      if (c_input.value == 0) {
        continue;
      }

      var column = {
        name: c_input.value,
        type: $(".selection z-dropdown.type", columns[i]).getValue(),
        optional: $(".selection z-dropdown.is_nullable", columns[i]).getValue()
      };

      if (column.type == "object") {
        column.schema = {columns: getColumnsSchema(
            $(".nested .columns", columns[i]).children)};
      }

      schema.push(column);
    }
    return schema;
  };

  return {
    render: render
  }
})();
<|endoftext|>


<commit_before>import { readdir, exists } from 'fs';
import { waterfall, filter, map } from 'async';
import { join } from 'path';
import vhost from 'vhost';
import { log } from 'magic-log';
import skeleton from 'magic-skeleton';

const cwd = process.cwd();
const hostRootDir = join(cwd, 'hosts');
let M = null;

const noop = () => {};

export const mount =
  (magic, cb = noop) => {
    M = magic;

    waterfall([
      findHosts,
      mountHosts,
    ], cb);
  };

export const findHosts =
  (cb = noop) => {
    const args = {};

    readdir(
      hostRootDir,
      (err, files) => {
        filter(
          files,
          hostFilter,
          hosts => {
            args.hosts = hosts;
            cb(err, args);
          }
        );
      }
    );
  };

const hostFilter =
  (file, cb = noop) => {
    const fileDir = join(hostRootDir, file, 'H.js');
    exists(fileDir, exists => {
      cb(exists);
    });
  };

const mountHosts =
  (args, cb = noop) => {
    map(args.hosts, mountHost, err => {
      cb(err, args);
    });
  };

const mountHost =
  (host, cb = noop) => {
    log('host', host);
    const hostDir = join(hostRootDir, host);
    const hostApp = require(join(hostDir, 'H.js'));
    const skel = skeleton(M, hostApp, hostDir);
    const config = require(join(hostDir, 'config'));
    const env = (skel.get('env') || 'production');
    const hosts = (config.hosts[env] ? config.hosts[env] : []);

    if (!hosts) {
      return cb('config.js needs an attribute named hosts.');
    }

    hosts.forEach(
      host => {
        M.use(vhost(host, skel));
        log('vhosts started for subhost ' + host);
      });

    cb(null);
  };
<commit_msg>fix hosts imports<commit_after>import { readdir, exists } from 'fs';
import { waterfall, filter, map } from 'async';
import { join } from 'path';
import vhost from 'vhost';
import { log } from 'magic-server-log';
import skeleton from './skeleton';

const cwd = process.cwd();
const hostRootDir = join(cwd, 'hosts');
let M = null;

const noop = () => {};

export const mount =
  (magic, cb = noop) => {
    M = magic;

    waterfall([
      findHosts,
      mountHosts,
    ], cb);
  };

export const findHosts =
  (cb = noop) => {
    const args = {};

    readdir(
      hostRootDir,
      (err, files) => {
        filter(
          files,
          hostFilter,
          hosts => {
            args.hosts = hosts;
            cb(err, args);
          }
        );
      }
    );
  };

const hostFilter =
  (file, cb = noop) => {
    const fileDir = join(hostRootDir, file, 'H.js');
    exists(fileDir, exists => {
      cb(exists);
    });
  };

const mountHosts =
  (args, cb = noop) => {
    map(args.hosts, mountHost, err => {
      cb(err, args);
    });
  };

const mountHost =
  (host, cb = noop) => {
    log('host', host);
    const hostDir = join(hostRootDir, host);
    const hostApp = require(join(hostDir, 'H.js'));
    const skel = skeleton(M, hostApp, hostDir);
    const config = require(join(hostDir, 'config'));
    const env = (skel.get('env') || 'production');
    const hosts = (config.hosts[env] ? config.hosts[env] : []);

    if (!hosts) {
      return cb('config.js needs an attribute named hosts.');
    }

    hosts.forEach(
      host => {
        M.use(vhost(host, skel));
        log('vhosts started for subhost ' + host);
      });

    cb(null);
  };
<|endoftext|>


<commit_before>//     keymaster.js
//     (c) 2011-2013 Thomas Fuchs
//     keymaster.js may be freely distributed under the MIT license.
//     modified by Quizlet to add stacked-based scopes and a class-based filter override

;(function(global){
  var k,
    _handlers = {},
    _mods = { 16: false, 18: false, 17: false, 91: false },
    _scope = [],
    // modifier keys
    _MODIFIERS = {
      '⇧': 16, shift: 16,
      '⌥': 18, alt: 18, option: 18,
      '⌃': 17, ctrl: 17, control: 17,
      '⌘': 91, command: 91
    },
    // special keys
    _MAP = {
      backspace: 8, tab: 9, clear: 12,
      enter: 13, 'return': 13,
      esc: 27, escape: 27, space: 32,
      left: 37, up: 38,
      right: 39, down: 40,
      del: 46, 'delete': 46,
      home: 36, end: 35,
      pageup: 33, pagedown: 34,
      ',': 188, '.': 190, '/': 191,
      '`': 192, '-': 189, '=': 187,
      ';': 186, '\'': 222,
      '[': 219, ']': 221, '\\': 220
    },
    _NUMPAD_TO_NORMAL = {
     // numeric pad numbers have different keycodes,
     // map them to their number counter part
     // 0 to 9
      96: 48, // 0
      97: 49, // 1
      98: 50, // 2
      99: 51, // 3
      100: 52, // 4
      101: 53, // 5
      102: 54, // 6
      103: 55, // 7
      104: 56, // 8
      105: 57 // 9
    }
    code = function(x){
      return _MAP[x] || x.toUpperCase().charCodeAt(0);
    },
    _downKeys = [];

  for(k=1;k<20;k++) _MAP['f'+k] = 111+k;

  // IE doesn't support Array#indexOf, so have a simple replacement
  function index(array, item){
    var i = array.length;
    while(i--) if(array[i]===item) return i;
    return -1;
  }

  // for comparing mods before unassignment
  function compareArray(a1, a2) {
    if (a1.length != a2.length) return false;
    for (var i = 0; i < a1.length; i++) {
        if (a1[i] !== a2[i]) return false;
    }
    return true;
  }

  var modifierMap = {
      16:'shiftKey',
      18:'altKey',
      17:'ctrlKey',
      91:'metaKey'
  };
  function updateModifierKey(event) {
      for(k in _mods) _mods[k] = event[modifierMap[k]];
  };

  // handle keydown event
  function dispatch(event) {
    var key, handler, k, i, modifiersMatch, scope;
    key = _NUMPAD_TO_NORMAL[event.keyCode] || event.keyCode;

    if (index(_downKeys, key) == -1) {
        _downKeys.push(key);
    }

    // if a modifier key, set the key.<modifierkeyname> property to true and return
    if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
    if(key in _mods) {
      _mods[key] = true;
      // 'assignKey' from inside this closure is exported to window.key
      for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
      return;
    }
    updateModifierKey(event);

    // see if we need to ignore the keypress (filter() can can be overridden)
    // by default ignore key presses if a select, textarea, or input is focused
    if(!assignKey.filter.call(this, event)) return;

    // abort if no potentially matching shortcuts found
    if (!(key in _handlers)) return;

    scope = getScope();

    // for each potential shortcut
    for (i = 0; i < _handlers[key].length; i++) {
      handler = _handlers[key][i];

      // see if it's in the current scope
      if(handler.scope == scope || handler.scope == 'all'){
        // check if modifiers match if any
        modifiersMatch = handler.mods.length > 0;
        for(k in _mods)
          if((!_mods[k] && index(handler.mods, +k) > -1) ||
            (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
        // call the handler and stop the event if neccessary
        if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
          if(handler.method(event, handler)===false){
            if(event.preventDefault) event.preventDefault();
              else event.returnValue = false;
            if(event.stopPropagation) event.stopPropagation();
            if(event.cancelBubble) event.cancelBubble = true;
          }
        }
      }
    }
  };

  // unset modifier keys on keyup
  function clearModifier(event){
    var key = event.keyCode, k,
        i = index(_downKeys, key);

    // remove key from _downKeys
    if (i >= 0) {
        _downKeys.splice(i, 1);
    }

    if(key == 93 || key == 224) key = 91;
    if(key in _mods) {
      _mods[key] = false;
      for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
    }
  };

  function resetModifiers() {
    for(k in _mods) _mods[k] = false;
    for(k in _MODIFIERS) assignKey[k] = false;
  };

  // parse and assign shortcut
  function assignKey(key, scope, method){
    var keys, mods;
    keys = getKeys(key);
    if (method === undefined) {
      method = scope;
      scope = 'all';
    }

    // for each shortcut
    for (var i = 0; i < keys.length; i++) {
      // set modifier keys if any
      mods = [];
      key = keys[i].split('+');
      if (key.length > 1){
        mods = getMods(key);
        key = [key[key.length-1]];
      }
      // convert to keycode and...
      key = key[0]
      key = code(key);
      // ...store handler
      if (!(key in _handlers)) _handlers[key] = [];
      _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
    }
  };

  // unbind all handlers for given key in current scope
  function unbindKey(key, scope) {
    var multipleKeys, keys,
      mods = [],
      i, j, obj;

    multipleKeys = getKeys(key);

    for (j = 0; j < multipleKeys.length; j++) {
      keys = multipleKeys[j].split('+');

      if (keys.length > 1) {
        mods = getMods(keys);
      }

      key = keys[keys.length - 1];
      key = code(key);

      if (scope === undefined) {
        scope = getScope();
      }
      if (!_handlers[key]) {
        return;
      }
      for (i = 0; i < _handlers[key].length; i++) {
        obj = _handlers[key][i];
        // only clear handlers if correct scope and mods match
        if (obj.scope === scope && compareArray(obj.mods, mods)) {
          _handlers[key][i] = {};
        }
      }
    }
  };

  // Returns true if the key with code 'keyCode' is currently down
  // Converts strings into key codes.
  function isPressed(keyCode) {
      if (typeof(keyCode)=='string') {
        keyCode = code(keyCode);
      }
      return index(_downKeys, keyCode) != -1;
  }

  function getPressedKeyCodes() {
      return _downKeys.slice(0);
  }

  // different behavior from canonical repo to allow us to watch events on more
  // element types
  function filter(event){
    var el = (event.target || event.srcElement);
    // HTML documnet does not contain tagName and classList. Instead, we set
    // the element as body tag.
    if (el instanceof HTMLDocument) var el = el.activeElement;
    var tagName = el.tagName;
    var classList = el.classList;

    if (! (classList instanceof DOMTokenList)) {
      console.info('Event', event);
      throw new Error('Browser does not support classList');
    }

    var hasAllowedTag = tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA';
    var hasAllowedClass = classList.contains('js-keymaster-allow');

    return hasAllowedTag || hasAllowedClass;
  }

  // initialize key.<modifier> to false
  for(k in _MODIFIERS) assignKey[k] = false;

  // set current scope (default 'all')
  function setScope(scope){
    if (scope) {
      _scope = [scope];
    } else {
      _scope = [];
    }
  };

  function getScope(){ return _scope.length ? _scope[_scope.length-1] : 'all' };
  function pushScope(scope) { _scope.push(scope); };

  function popScope(scope) {
    newScope = [];
    for (var i = 0; i < _scope.length; i++) {
      if (_scope[i] !== scope) {
        newScope.push(_scope[i]);
      } else {
        break;
      }
    }
    _scope = newScope;
  };

  // delete all handlers for a given scope
  function deleteScope(scope){
    var key, handlers, i;

    for (key in _handlers) {
      handlers = _handlers[key];
      for (i = 0; i < handlers.length; ) {
        if (handlers[i].scope === scope) handlers.splice(i, 1);
        else i++;
      }
    }
  };

  // abstract key logic for assign and unassign
  function getKeys(key) {
    var keys;
    key = key.replace(/\s/g, '');
    keys = key.split(',');
    if ((keys[keys.length - 1]) == '') {
      keys[keys.length - 2] += ',';
    }
    return keys;
  }

  // abstract mods logic for assign and unassign
  function getMods(key) {
    var mods = key.slice(0, key.length - 1);
    for (var mi = 0; mi < mods.length; mi++)
    mods[mi] = _MODIFIERS[mods[mi]];
    return mods;
  }

  // cross-browser events
  function addEvent(object, event, method) {
    if (object.addEventListener)
      object.addEventListener(event, method, false);
    else if(object.attachEvent)
      object.attachEvent('on'+event, function(){ method(window.event) });
  };

  // set the handlers globally on document
  addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
  addEvent(document, 'keyup', clearModifier);

  // reset modifiers to false whenever the window is (re)focused.
  addEvent(window, 'focus', resetModifiers);

  // store previously defined key
  var previousKey = global.key;

  // restore previously defined key and return reference to our key object
  function noConflict() {
    var k = global.key;
    global.key = previousKey;
    return k;
  }

  // set window.key and window.key.set/get/deleteScope, and the default filter
  global.key = assignKey;
  global.key.setScope = setScope;
  global.key.getScope = getScope;
  global.key.pushScope = pushScope;
  global.key.popScope = popScope;
  global.key.deleteScope = deleteScope;
  global.key.filter = filter;
  global.key.isPressed = isPressed;
  global.key.getPressedKeyCodes = getPressedKeyCodes;
  global.key.noConflict = noConflict;
  global.key.unbind = unbindKey;

  if(typeof module !== 'undefined') module.exports = assignKey;

})(this);
<commit_msg>fix typo<commit_after>//     keymaster.js
//     (c) 2011-2013 Thomas Fuchs
//     keymaster.js may be freely distributed under the MIT license.
//     modified by Quizlet to add stacked-based scopes and a class-based filter override

;(function(global){
  var k,
    _handlers = {},
    _mods = { 16: false, 18: false, 17: false, 91: false },
    _scope = [],
    // modifier keys
    _MODIFIERS = {
      '⇧': 16, shift: 16,
      '⌥': 18, alt: 18, option: 18,
      '⌃': 17, ctrl: 17, control: 17,
      '⌘': 91, command: 91
    },
    // special keys
    _MAP = {
      backspace: 8, tab: 9, clear: 12,
      enter: 13, 'return': 13,
      esc: 27, escape: 27, space: 32,
      left: 37, up: 38,
      right: 39, down: 40,
      del: 46, 'delete': 46,
      home: 36, end: 35,
      pageup: 33, pagedown: 34,
      ',': 188, '.': 190, '/': 191,
      '`': 192, '-': 189, '=': 187,
      ';': 186, '\'': 222,
      '[': 219, ']': 221, '\\': 220
    },
    _NUMPAD_TO_NORMAL = {
     // numeric pad numbers have different keycodes,
     // map them to their number counter part
     // 0 to 9
      96: 48, // 0
      97: 49, // 1
      98: 50, // 2
      99: 51, // 3
      100: 52, // 4
      101: 53, // 5
      102: 54, // 6
      103: 55, // 7
      104: 56, // 8
      105: 57 // 9
    }
    code = function(x){
      return _MAP[x] || x.toUpperCase().charCodeAt(0);
    },
    _downKeys = [];

  for(k=1;k<20;k++) _MAP['f'+k] = 111+k;

  // IE doesn't support Array#indexOf, so have a simple replacement
  function index(array, item){
    var i = array.length;
    while(i--) if(array[i]===item) return i;
    return -1;
  }

  // for comparing mods before unassignment
  function compareArray(a1, a2) {
    if (a1.length != a2.length) return false;
    for (var i = 0; i < a1.length; i++) {
        if (a1[i] !== a2[i]) return false;
    }
    return true;
  }

  var modifierMap = {
      16:'shiftKey',
      18:'altKey',
      17:'ctrlKey',
      91:'metaKey'
  };
  function updateModifierKey(event) {
      for(k in _mods) _mods[k] = event[modifierMap[k]];
  };

  // handle keydown event
  function dispatch(event) {
    var key, handler, k, i, modifiersMatch, scope;
    key = _NUMPAD_TO_NORMAL[event.keyCode] || event.keyCode;

    if (index(_downKeys, key) == -1) {
        _downKeys.push(key);
    }

    // if a modifier key, set the key.<modifierkeyname> property to true and return
    if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
    if(key in _mods) {
      _mods[key] = true;
      // 'assignKey' from inside this closure is exported to window.key
      for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
      return;
    }
    updateModifierKey(event);

    // see if we need to ignore the keypress (filter() can can be overridden)
    // by default ignore key presses if a select, textarea, or input is focused
    if(!assignKey.filter.call(this, event)) return;

    // abort if no potentially matching shortcuts found
    if (!(key in _handlers)) return;

    scope = getScope();

    // for each potential shortcut
    for (i = 0; i < _handlers[key].length; i++) {
      handler = _handlers[key][i];

      // see if it's in the current scope
      if(handler.scope == scope || handler.scope == 'all'){
        // check if modifiers match if any
        modifiersMatch = handler.mods.length > 0;
        for(k in _mods)
          if((!_mods[k] && index(handler.mods, +k) > -1) ||
            (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
        // call the handler and stop the event if neccessary
        if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
          if(handler.method(event, handler)===false){
            if(event.preventDefault) event.preventDefault();
              else event.returnValue = false;
            if(event.stopPropagation) event.stopPropagation();
            if(event.cancelBubble) event.cancelBubble = true;
          }
        }
      }
    }
  };

  // unset modifier keys on keyup
  function clearModifier(event){
    var key = event.keyCode, k,
        i = index(_downKeys, key);

    // remove key from _downKeys
    if (i >= 0) {
        _downKeys.splice(i, 1);
    }

    if(key == 93 || key == 224) key = 91;
    if(key in _mods) {
      _mods[key] = false;
      for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
    }
  };

  function resetModifiers() {
    for(k in _mods) _mods[k] = false;
    for(k in _MODIFIERS) assignKey[k] = false;
  };

  // parse and assign shortcut
  function assignKey(key, scope, method){
    var keys, mods;
    keys = getKeys(key);
    if (method === undefined) {
      method = scope;
      scope = 'all';
    }

    // for each shortcut
    for (var i = 0; i < keys.length; i++) {
      // set modifier keys if any
      mods = [];
      key = keys[i].split('+');
      if (key.length > 1){
        mods = getMods(key);
        key = [key[key.length-1]];
      }
      // convert to keycode and...
      key = key[0]
      key = code(key);
      // ...store handler
      if (!(key in _handlers)) _handlers[key] = [];
      _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
    }
  };

  // unbind all handlers for given key in current scope
  function unbindKey(key, scope) {
    var multipleKeys, keys,
      mods = [],
      i, j, obj;

    multipleKeys = getKeys(key);

    for (j = 0; j < multipleKeys.length; j++) {
      keys = multipleKeys[j].split('+');

      if (keys.length > 1) {
        mods = getMods(keys);
      }

      key = keys[keys.length - 1];
      key = code(key);

      if (scope === undefined) {
        scope = getScope();
      }
      if (!_handlers[key]) {
        return;
      }
      for (i = 0; i < _handlers[key].length; i++) {
        obj = _handlers[key][i];
        // only clear handlers if correct scope and mods match
        if (obj.scope === scope && compareArray(obj.mods, mods)) {
          _handlers[key][i] = {};
        }
      }
    }
  };

  // Returns true if the key with code 'keyCode' is currently down
  // Converts strings into key codes.
  function isPressed(keyCode) {
      if (typeof(keyCode)=='string') {
        keyCode = code(keyCode);
      }
      return index(_downKeys, keyCode) != -1;
  }

  function getPressedKeyCodes() {
      return _downKeys.slice(0);
  }

  // different behavior from canonical repo to allow us to watch events on more
  // element types
  function filter(event){
    var el = (event.target || event.srcElement);
    // HTML document does not contain tagName and classList. Instead, we set
    // the element as body tag.
    // if (el instanceof HTMLDocument) var el = el.activeElement;
    var tagName = el.tagName;
    var classList = el.classList;

    if (! (classList instanceof DOMTokenList)) {
      console.info('Event', event);
      throw new Error('Browser does not support classList');
    }

    var hasAllowedTag = tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA';
    var hasAllowedClass = classList.contains('js-keymaster-allow');

    return hasAllowedTag || hasAllowedClass;
  }

  // initialize key.<modifier> to false
  for(k in _MODIFIERS) assignKey[k] = false;

  // set current scope (default 'all')
  function setScope(scope){
    if (scope) {
      _scope = [scope];
    } else {
      _scope = [];
    }
  };

  function getScope(){ return _scope.length ? _scope[_scope.length-1] : 'all' };
  function pushScope(scope) { _scope.push(scope); };

  function popScope(scope) {
    newScope = [];
    for (var i = 0; i < _scope.length; i++) {
      if (_scope[i] !== scope) {
        newScope.push(_scope[i]);
      } else {
        break;
      }
    }
    _scope = newScope;
  };

  // delete all handlers for a given scope
  function deleteScope(scope){
    var key, handlers, i;

    for (key in _handlers) {
      handlers = _handlers[key];
      for (i = 0; i < handlers.length; ) {
        if (handlers[i].scope === scope) handlers.splice(i, 1);
        else i++;
      }
    }
  };

  // abstract key logic for assign and unassign
  function getKeys(key) {
    var keys;
    key = key.replace(/\s/g, '');
    keys = key.split(',');
    if ((keys[keys.length - 1]) == '') {
      keys[keys.length - 2] += ',';
    }
    return keys;
  }

  // abstract mods logic for assign and unassign
  function getMods(key) {
    var mods = key.slice(0, key.length - 1);
    for (var mi = 0; mi < mods.length; mi++)
    mods[mi] = _MODIFIERS[mods[mi]];
    return mods;
  }

  // cross-browser events
  function addEvent(object, event, method) {
    if (object.addEventListener)
      object.addEventListener(event, method, false);
    else if(object.attachEvent)
      object.attachEvent('on'+event, function(){ method(window.event) });
  };

  // set the handlers globally on document
  addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
  addEvent(document, 'keyup', clearModifier);

  // reset modifiers to false whenever the window is (re)focused.
  addEvent(window, 'focus', resetModifiers);

  // store previously defined key
  var previousKey = global.key;

  // restore previously defined key and return reference to our key object
  function noConflict() {
    var k = global.key;
    global.key = previousKey;
    return k;
  }

  // set window.key and window.key.set/get/deleteScope, and the default filter
  global.key = assignKey;
  global.key.setScope = setScope;
  global.key.getScope = getScope;
  global.key.pushScope = pushScope;
  global.key.popScope = popScope;
  global.key.deleteScope = deleteScope;
  global.key.filter = filter;
  global.key.isPressed = isPressed;
  global.key.getPressedKeyCodes = getPressedKeyCodes;
  global.key.noConflict = noConflict;
  global.key.unbind = unbindKey;

  if(typeof module !== 'undefined') module.exports = assignKey;

})(this);
<|endoftext|>