<commit_before>'use strict';

var T = require('../lib/twitter_client').user;
var async = require('async');
var _ = require('lodash');
var db = require('../lib/db');
var randomString = require('../lib/random');

var sendWarningTweet = function (user) {
  var warnedUser = db('user_warnings').find({ id: user.id });
  if (!warnedUser) {
    var tweetText = '@' + user.id + ', sorry I can\'t send you a code unless you follow me. DM me again once that is fixed.';

    warnedUser = db('user_warnings').push({id: user.id})[0];
    db.write().then(function () {
      T.post('statuses/update',
        { status: tweetText },
        function (err, data, response) {
          if (err) {
            return console.warn(err);
          }
          // console.log(data);
          console.log('Warning tweet successfully sent to %s', user.id);
        }
      );
    });
  } else {
    console.log('Skipping warning tweet to %s since we already warned them once', user.id);
  }
};

var sendTVCode = function (user) {
  var controlUrl = 'https://tvcontrolco.de/tv/' + user.tv + '/control?code=' + user.code;
  var tweetText = 'You can control TV #' + user.tv + ' via ' + controlUrl;
  T.post('direct_messages/new',
    { screen_name: user.id,
      text: tweetText
    },
    function (err, data, response) {
      if (err) {
        return console.warn(err);
      }

      // console.log(data);
      if (data.errors && data.errors[0].code === 150) {
        console.log('Telling %s that they are not following us', user.id);
        sendWarningTweet(user);
      } else {
        console.log('DM successfully sent to %s', user.id);
      }
    }
  );
};

var processTweet = function (status) {
  var tweetUser = status.user.screen_name;
  var idExtract = /\d/.exec(status.text);
  if (!idExtract) {
    console.log('Ignoring tweet: %s', status.text);
    return;
  }
  var tvId = idExtract[0];
  var user = db('users').find({ id: tweetUser, tv: tvId });
  if (user) {
    // Most likely the user forgot...
    return sendTVCode(user);
  } else {
    user = db('users').push({id: tweetUser, tv: tvId, code: randomString(12)})[0];
    db.write().then(function () {
      return sendTVCode(user);
    });
  }
};

var sinceId = false;
async.forever(
  function (next) {
    T.get('search/tweets',
      { q: 'to:' + process.env.TWITTER_HANDLE,
        count: 100,
        result_type: 'recent',
        since_id: sinceId
      },
      function (err, data, response) {
        if (err) {
          console.warn(err);
          return setTimeout(next, 30 * 1000);
        }
        sinceId = data.search_metadata.max_id_str;
        _.each(data.statuses, function (status) {
          processTweet(status);
        });
        setTimeout(next, 5 * 1000);
      }
    );
  },
  function (err) {
    // We never get here.
    console.warn(err);
  }
);
<commit_msg>Teaching the system not to check the same tweet twice<commit_after>'use strict';

var T = require('../lib/twitter_client').user;
var async = require('async');
var _ = require('lodash');
var db = require('../lib/db');
var randomString = require('../lib/random');

var sendWarningTweet = function (user) {
  var warnedUser = db('user_warnings').find({ id: user.id });
  if (!warnedUser) {
    var tweetText = '@' + user.id + ', sorry I can\'t send you a code unless you follow me. DM me again once that is fixed.';

    warnedUser = db('user_warnings').push({id: user.id})[0];
    db.write().then(function () {
      T.post('statuses/update',
        { status: tweetText },
        function (err, data, response) {
          if (err) {
            return console.warn(err);
          }
          // console.log(data);
          console.log('Warning tweet successfully sent to %s', user.id);
        }
      );
    });
  } else {
    console.log('Skipping warning tweet to %s since we already warned them once', user.id);
  }
};

var sendTVCode = function (user) {
  var controlUrl = 'https://tvcontrolco.de/tv/' + user.tv + '/control?code=' + user.code;
  var tweetText = 'You can control TV #' + user.tv + ' via ' + controlUrl;
  T.post('direct_messages/new',
    { screen_name: user.id,
      text: tweetText
    },
    function (err, data, response) {
      if (err) {
        return console.warn(err);
      }

      // console.log(data);
      if (data.errors && data.errors[0].code === 150) {
        console.log('Telling %s that they are not following us', user.id);
        sendWarningTweet(user);
      } else {
        console.log('DM successfully sent to %s', user.id);
      }
    }
  );
};

var processTweet = function (status) {
  var tweetUser = status.user.screen_name;
  var idExtract = /\d/.exec(status.text);
  if (!idExtract) {
    console.log('Ignoring tweet: %s', status.text);
    return;
  }
  var tvId = idExtract[0];
  var user = db('users').find({ id: tweetUser, tv: tvId });
  if (user) {
    // Most likely the user forgot...
    return sendTVCode(user);
  } else {
    user = db('users').push({id: tweetUser, tv: tvId, code: randomString(12)})[0];
    db.write().then(function () {
      return sendTVCode(user);
    });
  }
};

var dbSinceStorage = db('meta').find({ key: 'since_tweet' });
var sinceId = dbSinceStorage.value;
async.forever(
  function (next) {
    T.get('search/tweets',
      { q: 'to:' + process.env.TWITTER_HANDLE,
        count: 100,
        result_type: 'recent',
        since_id: sinceId
      },
      function (err, data, response) {
        if (err) {
          console.warn(err);
          return setTimeout(next, 30 * 1000);
        }

        sinceId = data.search_metadata.max_id_str;
        dbSinceStorage.value = sinceId;
        db.write();

        _.each(data.statuses, function (status) {
          processTweet(status);
        });
        setTimeout(next, 10 * 1000);
      }
    );
  },
  function (err) {
    // We never get here.
    console.warn(err);
  }
);
<|endoftext|>


<commit_before>var gulp = require('gulp');
var gutil = require('gulp-util');
var bower = require('bower');
var concat = require('gulp-concat');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var sh = require('shelljs');
var ngAnnotate = require('gulp-ng-annotate');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var templateCache = require('gulp-angular-templatecache');
var header = require('gulp-header');

var pkg = require('./package.json');
var babel = require("gulp-babel");
var plumber = require("gulp-plumber");

var paths = {
  sass: ['src/scss/**/*.scss'],
  es6: ['src/es6/**/*.js'],
  js: ['src/js/**/*.js'],
  templates: ['src/templates/**/*.html']
};

gulp.task('bump', require('gulp-cordova-bump'));

gulp.task('default', ['babel', 'sass', 'templates']);

gulp.task('sass', function(done) {
  gulp.src('src/scss/*.scss')
    .pipe(sass())
    .pipe(gulp.dest('./www/dist/css/'))
    .pipe(minifyCss({
      keepSpecialComments: 0
    }))
    .pipe(rename({ extname: '.min.css' }))
    .pipe(gulp.dest('./www/dist/css/'))
    .on('end', done);
});

gulp.task("babel", function () {
  return gulp.src(paths.es6)
    .pipe(plumber())
    .pipe(babel({presets: ['es2015']}))
    .pipe(gulp.dest("www/dist/js"));
});

gulp.task('build', function () {
  return gulp.src('src/es6/**/*.js')
        .pipe(sourcemaps.init())
          .pipe(ngAnnotate({
            single_quotes: true
          }))
          //.pipe(concat('js/app.js'))
          //.pipe(uglify())
          .pipe(sourcemaps.write())
          .pipe(header('window.VERSION = "<%= pkg.version %>";', { pkg : pkg } ))
        .pipe(gulp.dest('www/dist'));
});

gulp.task('templates', function(){
  return gulp.src('src/templates/**/*.html')
    .pipe(templateCache('templates.js',{module: 'starter', root:'templates/'}))
    .pipe(gulp.dest('www/dist'));
});

gulp.task('wiredep', function () {
  var wiredep = require('wiredep').stream;
  gulp.src('www/*.html')
    .pipe(wiredep({
      exclude: [
            'lib/angular/angular.js',
            'angular-animate.js',
            'angular-sanitize.js',
            'angular-ui-router.js',
            'angular-router.js',
            'ionic.material.js',
            'ionic-angular.js',
            'ng-cordova.js'
        ]
    }))
    .pipe(gulp.dest('www'));
});

gulp.task('watch', function() {
  gulp.watch(paths.es6, ['babel']);
  gulp.watch(paths.sass, ['sass']);
  gulp.watch(paths.js, ['build']);
  gulp.watch(paths.templates, ['templates']);
});

gulp.task('install', ['git-check'], function() {
  return bower.commands.install()
    .on('log', function(data) {
      gutil.log('bower', gutil.colors.cyan(data.id), data.message);
    });
});

gulp.task('git-check', function(done) {
  if (!sh.which('git')) {
    console.log(
      '  ' + gutil.colors.red('Git is not installed.'),
      '\n  Git, the version control system, is required to download Ionic.',
      '\n  Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
      '\n  Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
    );
    process.exit(1);
  }
  done();
});
<commit_msg>add exclusion<commit_after>var gulp = require('gulp');
var gutil = require('gulp-util');
var bower = require('bower');
var concat = require('gulp-concat');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var sh = require('shelljs');
var ngAnnotate = require('gulp-ng-annotate');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var templateCache = require('gulp-angular-templatecache');
var header = require('gulp-header');

var pkg = require('./package.json');
var babel = require("gulp-babel");
var plumber = require("gulp-plumber");

var paths = {
  sass: ['src/scss/**/*.scss'],
  es6: ['src/es6/**/*.js'],
  js: ['src/js/**/*.js'],
  templates: ['src/templates/**/*.html']
};

gulp.task('bump', require('gulp-cordova-bump'));

gulp.task('default', ['babel', 'sass', 'templates']);

gulp.task('sass', function(done) {
  gulp.src('src/scss/*.scss')
    .pipe(sass())
    .pipe(gulp.dest('./www/dist/css/'))
    .pipe(minifyCss({
      keepSpecialComments: 0
    }))
    .pipe(rename({ extname: '.min.css' }))
    .pipe(gulp.dest('./www/dist/css/'))
    .on('end', done);
});

gulp.task("babel", function () {
  return gulp.src(paths.es6)
    .pipe(plumber())
    .pipe(babel({presets: ['es2015']}))
    .pipe(gulp.dest("www/dist/js"));
});

gulp.task('build', function () {
  return gulp.src('src/es6/**/*.js')
        .pipe(sourcemaps.init())
          .pipe(ngAnnotate({
            single_quotes: true
          }))
          //.pipe(concat('js/app.js'))
          //.pipe(uglify())
          .pipe(sourcemaps.write())
          .pipe(header('window.VERSION = "<%= pkg.version %>";', { pkg : pkg } ))
        .pipe(gulp.dest('www/dist'));
});

gulp.task('templates', function(){
  return gulp.src('src/templates/**/*.html')
    .pipe(templateCache('templates.js',{module: 'starter', root:'templates/'}))
    .pipe(gulp.dest('www/dist'));
});

gulp.task('wiredep', function () {
  var wiredep = require('wiredep').stream;
  gulp.src('www/*.html')
    .pipe(wiredep({
      exclude: [
            'lib/angular/angular.js',
            'angular-animate.js',
            'angular-sanitize.js',
            'angular-ui-router.js',
            'angular-router.js',
            'ionic.material.js',
            'ionic-angular.js',
            'lib/jquery/jquery.js',
            'ng-cordova.js'
        ]
    }))
    .pipe(gulp.dest('www'));
});

gulp.task('watch', function() {
  gulp.watch(paths.es6, ['babel']);
  gulp.watch(paths.sass, ['sass']);
  gulp.watch(paths.js, ['build']);
  gulp.watch(paths.templates, ['templates']);
});

gulp.task('install', ['git-check'], function() {
  return bower.commands.install()
    .on('log', function(data) {
      gutil.log('bower', gutil.colors.cyan(data.id), data.message);
    });
});

gulp.task('git-check', function(done) {
  if (!sh.which('git')) {
    console.log(
      '  ' + gutil.colors.red('Git is not installed.'),
      '\n  Git, the version control system, is required to download Ionic.',
      '\n  Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
      '\n  Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
    );
    process.exit(1);
  }
  done();
});
<|endoftext|>


<commit_before>var processInfosCount = 0; // keep track of how many process info divs are shown
var processInfosIndex = 0;

//var closeProcessButton = document.getElementById("closeProcessButton");
//function closeProcessInfo() {
//    processInfosCount = processInfosCount - 1;
//}
//
//closeProcessButton.onclick = function() {closeProcessInfo()};o

window.onload = function() {
    var testShowButton = document.getElementById("tempAdd");
    var testCloseButton = document.getElementById("tempClose");

    // For testing
    testShowButton.onclick = function() {addProcessInfo()};
    testCloseButton.onclick = function() {removeProcessInfo(1)};
}

function addProcessInfo(processURI, inputsArray, outputsArray) {
    if (processInfosCount == 4) {
        // remove a
    }
    // check that process div thing is not over count;
    var newProcessInfoDiv = document.createElement("div"); // create a new div
    newProcessInfoDiv.setAttribute("id", processInfosIndex);
    newProcessInfoDiv.setAttribute("class", "processInfo");
    var layoutDiv = document.getElementById("processInfoDivLayout");
    var layoutDivCopy = layoutDiv.cloneNode(true);
    layoutDivCopy.style.display = "block";
    newProcessInfoDiv.innerHTML = layoutDivCopy.innerHTML;
    document.body.appendChild(newProcessInfoDiv);
    processInfosCount = processInfosCount + 1;
    processInfosIndex = processInfosIndex + 1;
    console.log(processInfosCount);

}

function removeProcessInfo(i) {
    var divToRemove = document.getElementById(i);
    divToRemove.remove();

}<commit_msg>process info correctly populates input and output variable names<commit_after>var processInfosCount = 0; // keep track of how many process info divs are shown
var processInfosIndex = 0;

//var closeProcessButton = document.getElementById("closeProcessButton");
//function closeProcessInfo() {
//    processInfosCount = processInfosCount - 1;
//}
//
//closeProcessButton.onclick = function() {closeProcessInfo()};o

window.onload = function() {
    var testShowButton = document.getElementById("tempAdd");
    var testCloseButton = document.getElementById("tempClose");

    // For testing
    testShowButton.onclick = function() {addProcessInfo()};
    testCloseButton.onclick = function() {removeProcessInfo(1)};
}

function addProcessInfo(processURI, inputsArray, outputsArray) {
    if (processInfosCount == 4) {
        // remove a
    }
    // check that process div thing is not over count;

    // set header name
    var processName = stripNameFromURI(processURI);
    console.log(processName);
    var nameHeader = document.getElementById("name_of_process_header");
    nameHeader.innerHTML = processName;

    console.log(inputsArray);
    console.log(outputsArray);
    // Populate the table with variable data
    var l = inputsArray.length;
    if (outputsArray.length > l) {
        l = outputsArray.length;
    }
    // adding inputs and outputs to the table
    var tableBody = document.getElementById("process_info_table_body");
    var newTableBody = document.createElement("tbody");
    newTableBody.setAttribute("id", "process_info_table_body");
    for (i = 0; i < l; i++) {
        var row = newTableBody.insertRow(-1);
        // add input variable name and add output variable name (if applicable)
        var cell1 = row.insertCell(0);
        var cell2 = row.insertCell(1);
        if (i < inputsArray.length) {
            cell1.innerHTML = stripNameFromURI(inputsArray[i]);
        }
        if (i < outputsArray.length) {
            cell2.innerHTML = stripNameFromURI(outputsArray[i]);
        }
    }
    console.log(tableBody.parentNode);
    tableBody.parentNode.replaceChild(newTableBody, tableBody);

    var newProcessInfoDiv = document.createElement("div"); // create a new div
    newProcessInfoDiv.setAttribute("id", processInfosIndex);
    newProcessInfoDiv.setAttribute("class", "processInfo");
    var layoutDiv = document.getElementById("processInfoDivLayout");
    var layoutDivCopy = layoutDiv.cloneNode(true);
    layoutDivCopy.style.display = "block";
    newProcessInfoDiv.innerHTML = layoutDivCopy.innerHTML;
    processInfosCount = processInfosCount + 1;
    processInfosIndex = processInfosIndex + 1;
    document.body.appendChild(newProcessInfoDiv);
    console.log(processInfosCount);

}

function removeProcessInfo(i) {
    var divToRemove = document.getElementById(i);
    divToRemove.remove();

}<|endoftext|>


<commit_before>'use strict';

/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

module.exports = function (gulp = require('gulp')) {
	require('@v4fire/core/gulpfile')(gulp);
	require('./build/static.gulp')(gulp);

	const
		runWebpack = 'node node_modules/parallel-webpack/bin/run.js',
		args = process.argv.slice(3).join(' ');

	gulp.task('cleanClient', (cb) => {
		const del = require('del');
		del('./dist/packages').then(() => cb(), cb);
	});

	gulp.task('client', (cb) => {
		const run = require('gulp-run');
		run(`${runWebpack} -- ${args}`, {verbosity: 3})
			.exec()
			.on('finish', cb);
	});

	gulp.task('watchClient', () => {
		const run = require('gulp-run');
		run(`${runWebpack} --watch -- ${args}`, {verbosity: 3}).exec();
	});

	gulp.task('watch', ['watchClient']);
	gulp.task('default', ['head', 'cleanClient', 'client']);
	gulp.task('prod', ['setProd', 'default']);
};

module.exports();
<commit_msg>:wrench:<commit_after>'use strict';

/*!
 * V4Fire Client Core
 * https://github.com/V4Fire/Client
 *
 * Released under the MIT license
 * https://github.com/V4Fire/Client/blob/master/LICENSE
 */

module.exports = function (gulp = require('gulp')) {
	require('@v4fire/core/gulpfile')(gulp);
	require('./build/static.gulp')(gulp);

	const
		runWebpack = 'node node_modules/parallel-webpack/bin/run.js',
		args = process.argv.slice(3).join(' ');

	gulp.task('cleanClient', (cb) => {
		const del = require('del');
		del('./dist/packages').then(() => cb(), cb);
	});

	gulp.task('client', (cb) => {
		const run = require('gulp-run');
		run(`${runWebpack} -- ${args}`, {verbosity: 3})
			.exec()
			.on('finish', cb);
	});

	gulp.task('watchClient', () => {
		const run = require('gulp-run');
		run(`${runWebpack} --watch -- ${args}`, {verbosity: 3}).exec();
	});

	gulp.task('watch', ['watchClient']);
	gulp.task('default', ['head', 'cleanClient', 'client']);
};

module.exports();
<|endoftext|>


<commit_before>var gulp = require('gulp');
var concat = require('gulp-concat');
var header = require('gulp-header');
var jscs = require('gulp-jscs');
var jshint = require('gulp-jshint');
var rename = require('gulp-rename');
var size = require('gulp-size');
var uglify = require('gulp-uglify');

var files = './src/**/*.js';

gulp.task('concat', ['lint'], function () {
    return gulp.src(files)
        .pipe(concat('domtokenlist.js'))
        .pipe(header('/*! DOMTokenlist shim | Copyright <%= new Date().getFullYear() %> Jonathan Wilsson and Bogdan Chadkin. */\n'))
        .pipe(size({
            showFiles: true
        }))
        .pipe(gulp.dest('./dist'));
});

gulp.task('lint', function () {
    return gulp.src(files)
        .pipe(jscs())
        .on('error', function (err) {
            console.log(err.message);
        })
        .pipe(jshint())
        .pipe(jshint.reporter('default'));
});

gulp.task('uglify', ['concat'], function () {
    return gulp.src('dist/domtokenlist.js')
        .pipe(uglify({
            preserveComments: 'some'
        }))
        .pipe(rename({
            suffix: '.min'
        }))
        .pipe(size({
            showFiles: true
        }))
        .pipe(gulp.dest('./dist'));
});

gulp.task('watch', ['default'], function () {
    gulp.watch(files, ['default']);
});

gulp.task('default', ['uglify']);
<commit_msg>Rearrange gulpfile Fix order<commit_after>var gulp = require('gulp');
var concat = require('gulp-concat');
var header = require('gulp-header');
var jscs = require('gulp-jscs');
var jshint = require('gulp-jshint');
var rename = require('gulp-rename');
var size = require('gulp-size');
var uglify = require('gulp-uglify');

var files = ['src/DOMTokenList-newest.js', 'src/DOMTokenList.js', 'src/classList.js', 'src/relList.js'];

gulp.task('build', function () {
    return gulp.src(files)
        .pipe(jscs())
        .pipe(jshint())
        .pipe(jshint.reporter('default'))
        .pipe(concat('domtokenlist.js'))
        .pipe(header('/*! DOMTokenlist shim | Copyright <%= new Date().getFullYear() %> Jonathan Wilsson and Bogdan Chadkin. */\n'))
        .pipe(size({
            showFiles: true
        }))
        .pipe(gulp.dest('dist'))
        .pipe(uglify({
            preserveComments: 'some'
        }))
        .pipe(rename({
            suffix: '.min'
        }))
        .pipe(size({
            showFiles: true
        }))
        .pipe(gulp.dest('dist'));
});

gulp.task('dev', ['build'], function () {
    gulp.watch(files, ['build'])
});

gulp.task('default', ['build']);
<|endoftext|>


<commit_before>// Copyright 2021-2022, University of Colorado Boulder

/**
 * Command Line Interface (CLI) for TypeScript transpilation via babel.  Transpiles *.ts and copies all *.js files to
 * chipper/dist/js. Does not do type checking. Filters based on active-repos and subsets of directories within repos
 * (such as js/, images/, and sounds/)
 *
 * Usage:
 * cd chipper
 * node js/scripts/transpile.js --watch
 *
 * OPTIONS:
 * --watch                Continue watching all directories and transpile on detected changes.
 * --clean                Dispose of the cache that tracks file status on startup, can be combined with other commands.
 *                        You would need to run --clean if the files in chipper/dist/js or chipper/dist/js-cache-status.json
 *                        are modified externally.  For example if you edit a file in chipper/dist/js or if you edit
 *                        chipper/dist/js-cache-status.json, they would be out of sync.  If you `rm -rf chipper/dist`
 *                        that does not require --clean, because that erases the cache file and the js files together.
 * --skipInitial          Skip the initial transpilation.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// constants
const start = Date.now();
const args = process.argv.slice( 2 );

// imports
const Transpiler = require( '../common/Transpiler' );

const transpiler = new Transpiler( {
  clean: args.includes( '--clean' ),
  verbose: args.includes( '--verbose' )
} );

transpiler.pruneStaleDistFiles();

// Initial pass
if ( !args.includes( '--skipInitial' ) ) {
  transpiler.transpileAll();
  console.log( 'Finished initial transpilation in ' + ( Date.now() - start ) + 'ms' );
}

// Watch process
if ( args.includes( '--watch' ) ) {
  transpiler.watch();
  console.log( 'Watching...' );
}<commit_msg>Watch before initial transpile, see https://github.com/phetsims/chipper/issues/1190<commit_after>// Copyright 2021-2022, University of Colorado Boulder

/**
 * Command Line Interface (CLI) for TypeScript transpilation via babel.  Transpiles *.ts and copies all *.js files to
 * chipper/dist/js. Does not do type checking. Filters based on active-repos and subsets of directories within repos
 * (such as js/, images/, and sounds/)
 *
 * Usage:
 * cd chipper
 * node js/scripts/transpile.js --watch
 *
 * OPTIONS:
 * --watch                Continue watching all directories and transpile on detected changes.
 * --clean                Dispose of the cache that tracks file status on startup, can be combined with other commands.
 *                        You would need to run --clean if the files in chipper/dist/js or chipper/dist/js-cache-status.json
 *                        are modified externally.  For example if you edit a file in chipper/dist/js or if you edit
 *                        chipper/dist/js-cache-status.json, they would be out of sync.  If you `rm -rf chipper/dist`
 *                        that does not require --clean, because that erases the cache file and the js files together.
 * --skipInitial          Skip the initial transpilation.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

// constants
const start = Date.now();
const args = process.argv.slice( 2 );

// imports
const Transpiler = require( '../common/Transpiler' );

const transpiler = new Transpiler( {
  clean: args.includes( '--clean' ),
  verbose: args.includes( '--verbose' )
} );

transpiler.pruneStaleDistFiles();

// Watch process
if ( args.includes( '--watch' ) ) {
  transpiler.watch();
}

// Initial pass
if ( !args.includes( '--skipInitial' ) ) {
  transpiler.transpileAll();
  console.log( 'Finished initial transpilation in ' + ( Date.now() - start ) + 'ms' );

  if ( args.includes( '--watch' ) ) {
    console.log( 'Watching...' );
  }
}<|endoftext|>


<commit_before>var gulp = require('gulp'),
  browserify = require('gulp-browserify'),
  sass = require('gulp-sass'),
  refresh = require('gulp-livereload'),
  lr = require('tiny-lr')(),
  concat = require('gulp-concat'),
  through = require('through'),
  express = require('express'),
  lrport = 35729,
  devport = 1337;

var app = express();
  app.use(require('connect-livereload')({
    port: lrport
  }));
  app.use(express.static('./experiments'));

gulp.task('sass', function(){
  gulp.src('experiments/*')
    .pipe(through(function(file) {

      gulp.src(file.path+'/*.scss')
        .pipe(sass())
        .pipe(gulp.dest(file.path + '/build'));

    }));
});

gulp.task('browserify', function(){
  gulp.src('experiments/*')
    .pipe(through(function(file) {

      gulp.src(file.path+'/*.js')
        .pipe(browserify({
          debug: true
        }))
        .pipe(concat('bundle.js'))
        .pipe(gulp.dest(file.path + '/build'));

    }));
});

gulp.task('watch', function() {
  gulp.watch('experiments/**/*.scss', function() {
    gulp.run('sass');
  });

  gulp.watch(['experiments/**/*.js', '!experiments/**/build/*'], function() {
    gulp.run('browserify');
  });

  app.listen(devport);
  lr.listen(lrport, function(err) {
    if(err) return console.log(err);

    gulp.watch('**/build/*', function(e) {
      lr.changed({body: {files: e.path}});
    });
  })
});<commit_msg>Fixes live-reload usage in gulpfile<commit_after>var gulp = require('gulp'),
  browserify = require('gulp-browserify'),
  sass = require('gulp-sass'),
  refresh = require('gulp-livereload'),
  lr = require('tiny-lr')(),
  concat = require('gulp-concat'),
  through = require('through'),
  express = require('express'),
  lrport = 35729,
  devport = 1337;

var app = express();
  app.use(require('connect-livereload')({
    port: lrport
  }));
  app.use(express.static('./experiments'));

gulp.task('sass', function(){
  gulp.src('experiments/*')
    .pipe(through(function(file) {

      gulp.src(file.path+'/*.scss')
        .pipe(sass())
        .pipe(gulp.dest(file.path + '/build'))
        .pipe(refresh(lr));;

    }));
});

gulp.task('browserify', function(){
  gulp.src('experiments/*')
    .pipe(through(function(file) {

      gulp.src(file.path+'/*.js')
        .pipe(browserify({
          debug: true
        }))
        .pipe(concat('bundle.js'))
        .pipe(gulp.dest(file.path + '/build'))
        .pipe(refresh(lr));;

    }));
});

gulp.task('watch', function() {
  app.listen(devport);
  lr.listen(lrport, function(err) {
    if(err) return console.log(err);

    gulp.watch('experiments/**/*.scss', function() {
      gulp.run('sass');
    });

    gulp.watch(['experiments/**/*.js', '!experiments/**/build/*'], function() {
      gulp.run('browserify');
    });

    gulp.watch('experiments/**/*.html', function (e) {
      lr.changed({body: {files: e.path}});
    })

  })
});<|endoftext|>


<commit_before>// Copyright 2022, University of Colorado Boulder
/**
 *
 * The tsc-status script runs through relevant common code repos and counts the lines of code written in javascript
 * and typescript. Provides data on conversion status, as well as occurrences of @ts-ignore.
 *
 * Run from sims root directory
 * USAGE:
 * cd ${root containing all repos}
 * node ./perennial/js/scripts/ts-status.js
 *
 * @author Marla Schulz (PhET Interactive Simulations)
 */

// eslint-disable-next-line require-statement-match
const _ = require( 'lodash' );
const fs = require( 'fs' );

// The repositories the script will iterate through to produce data
const repos = [
  'axon',
  'brand',
  'chipper',
  'dot',
  'joist',
  'kite',
  'mobius',
  'perennial',
  'phet-core',
  'phet-io',
  'phetcommon',
  'phetmarks',
  'scenery',
  'scenery-phet',
  'shred',
  'sun',
  'tambo',
  'tandem',
  'twixt',
  'utterance-queue',
  'vegas'
];

// Table headers. Begin here to add another data point.
const jsHeader = 'JS';
const tsHeader = 'TS';
const tsIgnoreHeader = '"@ts-ignore"';
const completeHeader = '% Complete';
const tableData = {};

const percent = ( numerator, denominator ) => {
  return Math.floor( ( numerator / denominator ) * 100 );
};

// Counts by every line of text in a file vs `wc -l` which counts by every newline.
// Therefore, `wc -l` is inaccurate by at least 1 line per file.
const countLines = path => {
  const text = fs.readFileSync( path, 'utf8' );
  const textLines = text.trim().split( /\r?\n/ );
  const lineCount = textLines.length;
  return lineCount;
};

// Uses `.include` to check if word is present in line and then ups word count by 1.
// Does not count multiple uses of same word in one line. For those types of scenarios,
// this function is inaccurate.
const countWord = ( path, word ) => {
  const occurrence = [];
  const text = fs.readFileSync( path, 'utf8' );
  const textLines = text.trim().split( /\r?\n/ );
  textLines.forEach( line => {
    if ( line.includes( word ) ) {
      occurrence.push( word );
    }
  } );
  const wordCount = occurrence.length;
  return wordCount;
};

// recursively navigates each repository to find relevant javascript and typescript files
const captureData = ( repo, tableData ) => {
  let tsCount = 0;
  let jsCount = 0;
  let tsIgnoreCount = 0;

  const startPath = `./${repo}`;
  const entries = fs.readdirSync( startPath );

  entries.forEach( file => {
    const path = `${startPath}/${file}`;

    if ( fs.statSync( path ).isDirectory() ) {
      captureData( path, tableData );
    }
    else if ( file.match( /\.js$/ ) ) {
      const fileCount = countLines( path );
      jsCount += fileCount;
    }
    else if ( file.match( /\.ts$/ ) ) {
      const fileCount = countLines( path );
      tsCount += fileCount;
      const fileTSIgnoreCount = countWord( path, '@ts-ignore' );
      tsIgnoreCount += fileTSIgnoreCount;
    }
  } );

  // Adds count to respective key in nested repo object.
  tableData[ jsHeader ] += jsCount;
  tableData[ tsHeader ] += tsCount;
  tableData[ tsIgnoreHeader ] += tsIgnoreCount;
};

// iterate through list of common code repos to fill out data
repos.forEach( repo => {

  // Sets baseline for nested repo object. New data point baselines should be added here.
  tableData[ repo ] = {
    [ jsHeader ]: 0,
    [ tsHeader ]: 0,
    [ completeHeader ]: 0,
    [ tsIgnoreHeader ]: 0
  };
  const repoData = tableData[ repo ];

  captureData( repo + '/js', repoData );

  repoData[ completeHeader ] = percent( repoData.TS, repoData.TS + repoData.JS );
} );

// calculates total sum across all provided repos
const rows = Object.values( tableData );
const totalJS = _.sumBy( rows, jsHeader );
const totalTS = _.sumBy( rows, tsHeader );
const totalTSIgnore = _.sumBy( rows, tsIgnoreHeader );

const summary = `\n --------- SUMMARY ----------
 Total ${tsIgnoreHeader}: ${totalTSIgnore}
 Total ${jsHeader}: ${totalJS}
 Total ${tsHeader}: ${totalTS}
 ${completeHeader}: ${percent( totalTS, totalTS + totalJS )}%
 `;

console.log( summary );
console.table( tableData );<commit_msg>Clean up js code, see: https://github.com/phetsims/perennial/issues/274<commit_after>// Copyright 2022, University of Colorado Boulder
/**
 *
 * The tsc-status script runs through relevant common code repos and counts the lines of code written in javascript
 * and typescript. Provides data on conversion status, as well as occurrences of @ts-ignore.
 *
 * Run from sims root directory
 * USAGE:
 * cd ${root containing all repos}
 * node ./perennial/js/scripts/ts-status.js
 *
 * @author Marla Schulz (PhET Interactive Simulations)
 */

// eslint-disable-next-line require-statement-match
const _ = require( 'lodash' );
const fs = require( 'fs' );

// The repositories the script will iterate through to produce data
const repos = [
  'axon',
  'brand',
  'chipper',
  'dot',
  'joist',
  'kite',
  'mobius',
  'perennial',
  'phet-core',
  'phet-io',
  'phetcommon',
  'phetmarks',
  'scenery',
  'scenery-phet',
  'shred',
  'sun',
  'tambo',
  'tandem',
  'twixt',
  'utterance-queue',
  'vegas'
];

// Table headers. Begin here to add another data point.
const jsHeader = 'JS';
const tsHeader = 'TS';
const tsIgnoreHeader = '"@ts-ignore"';
const completeHeader = '% Complete';
const tableData = {};

const percent = ( numerator, denominator ) => {
  return Math.floor( ( numerator / denominator ) * 100 );
};

// Counts by every line of text in a file vs `wc -l` which counts by every newline.
// Therefore, `wc -l` is inaccurate by at least 1 line per file.
const countLines = path => {
  const text = fs.readFileSync( path, 'utf8' );
  const textLines = text.trim().split( /\r?\n/ );
  return textLines.length;
};

// Uses `.include` to check if word is present in line and then ups word count by 1.
// Does not count multiple uses of same word in one line. For those types of scenarios,
// this function is inaccurate.
const countWord = ( path, word ) => {
  const occurrence = [];
  const text = fs.readFileSync( path, 'utf8' );
  const textLines = text.trim().split( /\r?\n/ );
  textLines.forEach( line => {
    if ( line.includes( word ) ) {
      occurrence.push( word );
    }
  } );
  return occurrence.length;
};

// recursively navigates each repository to find relevant javascript and typescript files
const captureData = ( path, tableData ) => {
  let tsCount = 0;
  let jsCount = 0;
  let tsIgnoreCount = 0;

  const entries = fs.readdirSync( path );

  entries.forEach( file => {
    const newPath = `${path}/${file}`;

    if ( fs.statSync( newPath ).isDirectory() ) {
      captureData( newPath, tableData );
    }
    else if ( file.match( /\.js$/ ) ) {
      jsCount += countLines( newPath );
    }
    else if ( file.match( /\.ts$/ ) ) {
      tsCount += countLines( newPath );
      tsIgnoreCount += countWord( newPath, '@ts-ignore' );
    }
  } );

  // Adds count to respective key in nested repo object.
  tableData[ jsHeader ] += jsCount;
  tableData[ tsHeader ] += tsCount;
  tableData[ tsIgnoreHeader ] += tsIgnoreCount;
};

// iterate through list of common code repos to fill out data
repos.forEach( repo => {

  // Sets baseline for nested repo object. New data point baselines should be added here.
  tableData[ repo ] = {
    [ jsHeader ]: 0,
    [ tsHeader ]: 0,
    [ completeHeader ]: 0,
    [ tsIgnoreHeader ]: 0
  };
  const repoData = tableData[ repo ];

  captureData( `./${repo}/js`, repoData );

  repoData[ completeHeader ] = percent( repoData.TS, repoData.TS + repoData.JS );
} );

// calculates total sum across all provided repos
const rows = Object.values( tableData );
const totalJS = _.sumBy( rows, jsHeader );
const totalTS = _.sumBy( rows, tsHeader );
const totalTSIgnore = _.sumBy( rows, tsIgnoreHeader );

const summary = `\n --------- SUMMARY ----------
 Total ${tsIgnoreHeader}: ${totalTSIgnore}
 Total ${jsHeader}: ${totalJS}
 Total ${tsHeader}: ${totalTS}
 ${completeHeader}: ${percent( totalTS, totalTS + totalJS )}%
 `;

console.log( summary );
console.table( tableData );<|endoftext|>


<commit_before><commit_msg>0.0.1<commit_after>'use strict'

var gulp = require('gulp');
var sass = require('gulp-sass');
var imagemin = require('gulp-imagemin');
var imageResize = require('gulp-image-resize');

var image_prop = {
	      width : 430,
	      height : 300,
	      crop : true,
	      upscale : false
	    };

gulp.task('images', function(){
    gulp.src(['src/example8/*.png', 'src/example8/*.jpg'])
        .pipe(imagemin({
        	optimizationLevel: 10,
        }))
        .pipe(imageResize(image_prop))
        .pipe(gulp.dest('All examples/example8'));
});
<|endoftext|>


<commit_before>console.log('Confirm that this sends no outside network requests in the Network Panel.')

var receiveProgress = document.getElementById('receiveProgress')
var sendProgress = document.getElementById('sendProgress')
var bitrateDiv = document.getElementById('bitrate')
var statusMessage = document.getElementById('status')
var downloadsAnchor = document.getElementById('downloads')
var theirVideo = document.getElementById('theirVideo')
var myVideo = document.getElementById('myVideo')

function WebRTCChat(cfg, con, sendTyping) {
    var self = this;

/* WebRTC setup for broser-to-browser connection */
    self.cfg = {'iceServers':[{
      'url': 'stun:stun.l.google.com:19302'
    }]}; //{"url":"stun:23.21.150.121"}
    self.con = {'optional':  [{'DtlsSrtpKeyAgreement': true}] };
    self.activeChannel;
    self.activeConnection;
    self.roomName;

    self.readystate = false;
    self.sendTyping = true;

/* WebRTC + CHAT CONNECTION CODE */

    /* THE HOST (initiates the chat) */

    self.hostChat = function(offer_callback, ready_callback) {
        var hostConnection = new RTCPeerConnection(self.cfg, self.con);                                 // init connection
        self.initConnection(hostConnection, offer_callback);

        var hostChannel = hostConnection.createDataChannel('chat', {
            reliable: true,
            ordered: true,
        });
        self.initChannel(hostChannel, ready_callback);

        console.log("Creating RTC Chat Host Offer...");
        hostConnection.createOffer(
            self.handleDescription,
            self.handleDescriptionFailure,
            {offerToReceiveAudio: true, offerToReceiveVideo: true}
        );
        // copy paste this offer to all clients who want to join
        // they paste their answer back, which goes into handleAnswerFromClient
    }

    self.handleAnswerFromClient = function(answer) {
        var answerDesc = new RTCSessionDescription(answer.rtc);
        console.log("Received Chat RTC Join Answer: ", answerDesc);
        self.activeConnection.setRemoteDescription(answerDesc);
        self.activeChannel.onopen()
        writeToChatLog("Started hosting a chat.", "text-success alert-success");

        // hostChannel.onopen will trigger once the connection is complete (enabling the chat window)
    }

    /* THE JOINEE (joins an existing chat) */

    self.joinChat = function(offer, answer_callback, ready_callback) {
        var clientConnection = new RTCPeerConnection(cfg, con);
        self.initConnection(clientConnection, answer_callback);

        self.activeConnection.ondatachannel = function (e) {                                     // once client receives a good data channel from the host
            // Chrome sends event, FF sends raw channel
            var clientChannel = e.channel || e;
            self.initChannel(clientChannel, ready_callback);
            self.activeChannel.onopen()
            writeToChatLog("Joined a chat.", "text-success alert-success");
            // clientChannel.onopen will then trigger once the connection is complete (enabling the chat window)
        };

        self.roomName = offer.roomName;

        var offerDesc = new RTCSessionDescription(offer.rtc);
        console.log("Received Chat RTC Host Offer: ", offerDesc);
        self.activeConnection.setRemoteDescription(offerDesc);

        console.log("Answering Chat Host Offer...");
        self.activeConnection.createAnswer(
            self.handleDescription,
            self.handleDescriptionFailure,
            {offerToReceiveAudio: true, offerToReceiveVideo: true},
        );

        // ondatachannel triggers once the client has accepted our answer ^
    }

    self.initConnection = function(conn, callback) {
        self.activeConnection = conn;
        // these aren't really necessary
        conn.onconnection                   = function (state) {console.info('Chat connection complete: ', event);}
        conn.onsignalingstatechange         = function (state) {console.info('Signaling state change: ', state); if (self.activeConnection.iceConnectionState == "disconnected") self.writeToChatLog("Chat partner disconnected.", "text-warning alert-error");}
        conn.oniceconnectionstatechange     = function (state) {console.info('Signaling ICE connection state change: ', state); if (self.activeConnection.iceConnectionState == "disconnected") self.writeToChatLog("Chat partner disconnected.", "text-warning alert-error");}
        conn.onicegatheringstatechange      = function (state) {console.info('Signaling ICE setup state change: ', state);}
        conn.onaddstream = function (event) {
            var video = theirVideo;
            if (window.URL)
                video.src = window.URL.createObjectURL(event.stream);
            else
                video.src = event.stream;
            console.log("YAAAAAAAAgotvideo");
        }
        //this is the important one
        conn.onicecandidate = function (event) {
            // when browser has determined how to form a connection, generate offer or answer with ICE connection details and PGP public key
            if (event.candidate == null) {
                console.log("Valid ICE connection candidate determined.");
                var offer_or_answer = JSON.stringify({
                    rtc: self.activeConnection.localDescription,
                    roomName: self.roomName
                });
                // pass the offer or answer to the callback for display to the user or to send over some other communication channel
                if (callback) callback(offer_or_answer);
            }
        };
        conn.onfailure = function(details) {callback(details)};
        console.log("Initialized Connection: ", conn);
    }

    self.initChannel = function(chan, callback) {
        self.activeChannel = chan;
        self.activeChannel.binaryType = 'arraybuffer';
        // once the channel is open, trigger the callback to enable the chat window or carry out other logic
        self.activeChannel.onopen = function (e) {
            console.log('Data Channel Connected.');
            self.readystate = true;
            if (callback) callback(e);
        }
        chan.onmessage = self.receiveMessage;
        console.log("Initialized Data Channel: ", chan);
    }

    self.handleDescription = function(desc) {
        self.activeConnection.setLocalDescription(desc, function () {});
    }

    self.handleDescriptionFailure = function() {
        console.warn("Failed to create or answer chat offer.");
        self.activeConnection.onfailure("Invalid or expired chat offer. Please try again.")
    }

    // messaging functions

    self.sendTypingMessage = function() {
        self.activeChannel.send(JSON.stringify({message:null,typing:true}));
    }

    self.sendMessage = function(message) {
        self.activeChannel.send(JSON.stringify({message: message}));
        self.writeToChatLog(message, "text-success sent insecure", false);
    }

    self.receiveMessage = function(event) {
        try {
            var data = JSON.parse(event.data);
        } catch(e) {
            console.log('RECEIVING FILE')
            return self.receiveFile(event);
        }

        if (data.file) {
            return self.prepareToReceiveFile(data);
        }

        if (data.typing && !data.message) {
            console.log("Partner is typing...");
            self.displayPartnerTyping();
        }
        else {
            console.log("Received a message: ", data.message);
            self.writeToChatLog(data.message, "text-info recv", false);
        }
    }

    // Audio and Video

    self.initVideo = function() {
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        var video = myVideo;

        navigator.getUserMedia({
            audio: false,
            video: true
        }, function (stream) {
          if (window.URL)
            video.src = window.URL.createObjectURL(stream);
          else
            video.src = stream;
          window.myStream = stream;
          self.activeConnection.addStream(window.myStream);
            self.activeChannel.send(JSON.stringify({
                message: "Started sharing video."
            }));
            self.writeToChatLog(
                "Started sharing video.",
                "text-success sent insecure", false);
        }, function (error){
          console.log('navigator.getUserMedia error: ', error);
        });
    }

    // Files

    self.prepareToReceiveFile = function(data) {
        self.startTime = null;
        self.receiveBuffer = null;
        self.receivedSize = null;
        self.bitrateMax = null;
        self.numReceivedFiles = self.numReceivedFiles || 0;
        self.receivedFiles = self.receivedFiles || {};
        self.writeToChatLog(
            "Receiving " + data.name + " (" + data.size + " bytes)",
            "text-success sent insecure",
            false
        );
        self.numReceivedFiles += 1;
        // XSS out the wazoo
        var downloadLink = downloadsAnchor.innerHTML += (
            '
'
            + 'Download: ' + data.name + ''
            + " (" + data.size + " bytes)"
            + '
'
        )
        self.receivedFiles[data.name] = data;
        self.receivedFiles[data.name].id = self.numReceivedFiles;
        self.receivedFiles[data.name].anchor = document.getElementById('recvd-' + self.numReceivedFiles);
        self.incomingFile = self.receivedFiles[data.name]
    }

    self.receiveFile = function(event) {
        self.startTime = self.startTime || (new Date).getTime();
        self.receiveBuffer = self.receiveBuffer || [];
        self.receivedSize = self.receivedSize || 0;
        self.bitrateMax = self.bitrateMax || 0;

        self.receiveBuffer.push(event.data);
        self.receivedSize += event.data.byteLength;

        receiveProgress.value = self.receivedSize;

        // we are assuming that our signaling protocol told
        // about the expected file size (and name, hash, etc).
        var received = new window.Blob(self.receiveBuffer);
        self.receiveBuffer = [];

        var url = URL.createObjectURL(received);
        self.incomingFile.anchor.href = url;

        var bitrate = Math.round(self.receivedSize * 8 /
            ((new Date()).getTime() - self.startTime));

        if (bitrate > self.bitrateMax) {
            self.bitrateMax = bitrate;
        }

        bitrateDiv.innerHTML = 'Bitrate: ' +
            bitrate + ' kbits/sec (max: ' + self.bitrateMax + ' kbits/sec)';

        if (self.receivedSize >= self.incomingFile.size) {
            self.writeToChatLog(
                "Receiving " + self.incomingFile.name + " finished! Download it below.",
                "text-success sent insecure",
                false
            );
            self.activeChannel.send(JSON.stringify({
                message: 'Received ' + self.incomingFile.name + " succesfully.",
            }));
        } else {
            self.incomingFile.anchor.disabled = true;
        }
    }

    self.sendFile = function(files) {
        var file = [].concat.apply([], files).slice(-1)[0];
        console.log('File is ' + [file.name, file.size, file.type,
            file.lastModifiedDate
        ].join(' '));
        self.activeChannel.send(JSON.stringify({
            file: true,
            name: file.name,
            size: file.size,
        }));
        self.writeToChatLog("Sending " + file.name, "text-success sent insecure", false);

        // Handle 0 size files.
        statusMessage.textContent = '';
        if (file.size === 0) {
          bitrateDiv.innerHTML = '';
          statusMessage.textContent = 'File is empty, please select a non-empty file';
          return;
        }
        sendProgress.max = file.size;
        receiveProgress.max = file.size;
        var chunkSize = 16384;
        var sliceFile = function(offset) {
          var reader = new window.FileReader();
          reader.onload = (function() {
            return function(e) {
              self.activeChannel.send(e.target.result);
              if (file.size > offset + e.target.result.byteLength) {
                window.setTimeout(sliceFile, 0, offset + chunkSize);
              }
              sendProgress.value = offset + e.target.result.byteLength;
            };
          })(file);
          var slice = file.slice(offset, offset + chunkSize);
          reader.readAsArrayBuffer(slice);
        };
        sliceFile(0);
    }

/* Utilities */

    // set these to your own functions using WebRTCChat.writeToChatLog = function(...) {...}

    self.writeToChatLog = function(message, message_type, secure) {console.log("-> ", message, message_type, secure);}

    self.displayPartnerTyping = function() {console.log("-> Typing...");}
}
<commit_msg>change console msg<commit_after>console.log('Confirm that this sends no outside network requests in the Network Panel.')

var receiveProgress = document.getElementById('receiveProgress')
var sendProgress = document.getElementById('sendProgress')
var bitrateDiv = document.getElementById('bitrate')
var statusMessage = document.getElementById('status')
var downloadsAnchor = document.getElementById('downloads')
var theirVideo = document.getElementById('theirVideo')
var myVideo = document.getElementById('myVideo')

function WebRTCChat(cfg, con, sendTyping) {
    var self = this;

/* WebRTC setup for broser-to-browser connection */
    self.cfg = {'iceServers':[{
      'url': 'stun:stun.l.google.com:19302'
    }]}; //{"url":"stun:23.21.150.121"}
    self.con = {'optional':  [{'DtlsSrtpKeyAgreement': true}] };
    self.activeChannel;
    self.activeConnection;
    self.roomName;

    self.readystate = false;
    self.sendTyping = true;

/* WebRTC + CHAT CONNECTION CODE */

    /* THE HOST (initiates the chat) */

    self.hostChat = function(offer_callback, ready_callback) {
        var hostConnection = new RTCPeerConnection(self.cfg, self.con);                                 // init connection
        self.initConnection(hostConnection, offer_callback);

        var hostChannel = hostConnection.createDataChannel('chat', {
            reliable: true,
            ordered: true,
        });
        self.initChannel(hostChannel, ready_callback);

        console.log("Creating RTC Chat Host Offer...");
        hostConnection.createOffer(
            self.handleDescription,
            self.handleDescriptionFailure,
            {offerToReceiveAudio: true, offerToReceiveVideo: true}
        );
        // copy paste this offer to all clients who want to join
        // they paste their answer back, which goes into handleAnswerFromClient
    }

    self.handleAnswerFromClient = function(answer) {
        var answerDesc = new RTCSessionDescription(answer.rtc);
        console.log("Received Chat RTC Join Answer: ", answerDesc);
        self.activeConnection.setRemoteDescription(answerDesc);
        self.activeChannel.onopen()
        writeToChatLog("Started hosting a chat.", "text-success alert-success");

        // hostChannel.onopen will trigger once the connection is complete (enabling the chat window)
    }

    /* THE JOINEE (joins an existing chat) */

    self.joinChat = function(offer, answer_callback, ready_callback) {
        var clientConnection = new RTCPeerConnection(cfg, con);
        self.initConnection(clientConnection, answer_callback);

        self.activeConnection.ondatachannel = function (e) {                                     // once client receives a good data channel from the host
            // Chrome sends event, FF sends raw channel
            var clientChannel = e.channel || e;
            self.initChannel(clientChannel, ready_callback);
            self.activeChannel.onopen()
            writeToChatLog("Joined a chat.", "text-success alert-success");
            // clientChannel.onopen will then trigger once the connection is complete (enabling the chat window)
        };

        self.roomName = offer.roomName;

        var offerDesc = new RTCSessionDescription(offer.rtc);
        console.log("Received Chat RTC Host Offer: ", offerDesc);
        self.activeConnection.setRemoteDescription(offerDesc);

        console.log("Answering Chat Host Offer...");
        self.activeConnection.createAnswer(
            self.handleDescription,
            self.handleDescriptionFailure,
            {offerToReceiveAudio: true, offerToReceiveVideo: true}
        );

        // ondatachannel triggers once the client has accepted our answer ^
    }

    self.initConnection = function(conn, callback) {
        self.activeConnection = conn;
        // these aren't really necessary
        conn.onconnection                   = function (state) {console.info('Chat connection complete: ', event);}
        conn.onsignalingstatechange         = function (state) {console.info('Signaling state change: ', state); if (self.activeConnection.iceConnectionState == "disconnected") self.writeToChatLog("Chat partner disconnected.", "text-warning alert-error");}
        conn.oniceconnectionstatechange     = function (state) {console.info('Signaling ICE connection state change: ', state); if (self.activeConnection.iceConnectionState == "disconnected") self.writeToChatLog("Chat partner disconnected.", "text-warning alert-error");}
        conn.onicegatheringstatechange      = function (state) {console.info('Signaling ICE setup state change: ', state);}
        conn.onaddstream = function (event) {
            var video = theirVideo;
            if (window.URL)
                video.src = window.URL.createObjectURL(event.stream);
            else
                video.src = event.stream;
            console.log("You got their video!");
        }
        //this is the important one
        conn.onicecandidate = function (event) {
            // when browser has determined how to form a connection, generate offer or answer with ICE connection details and PGP public key
            if (event.candidate == null) {
                console.log("Valid ICE connection candidate determined.");
                var offer_or_answer = JSON.stringify({
                    rtc: self.activeConnection.localDescription,
                    roomName: self.roomName
                });
                // pass the offer or answer to the callback for display to the user or to send over some other communication channel
                if (callback) callback(offer_or_answer);
            }
        };
        conn.onfailure = function(details) {callback(details)};
        console.log("Initialized Connection: ", conn);
    }

    self.initChannel = function(chan, callback) {
        self.activeChannel = chan;
        self.activeChannel.binaryType = 'arraybuffer';
        // once the channel is open, trigger the callback to enable the chat window or carry out other logic
        self.activeChannel.onopen = function (e) {
            console.log('Data Channel Connected.');
            self.readystate = true;
            if (callback) callback(e);
        }
        chan.onmessage = self.receiveMessage;
        console.log("Initialized Data Channel: ", chan);
    }

    self.handleDescription = function(desc) {
        self.activeConnection.setLocalDescription(desc, function () {});
    }

    self.handleDescriptionFailure = function() {
        console.warn("Failed to create or answer chat offer.");
        self.activeConnection.onfailure("Invalid or expired chat offer. Please try again.")
    }

    // messaging functions

    self.sendTypingMessage = function() {
        self.activeChannel.send(JSON.stringify({message:null,typing:true}));
    }

    self.sendMessage = function(message) {
        self.activeChannel.send(JSON.stringify({message: message}));
        self.writeToChatLog(message, "text-success sent insecure", false);
    }

    self.receiveMessage = function(event) {
        try {
            var data = JSON.parse(event.data);
        } catch(e) {
            console.log('RECEIVING FILE')
            return self.receiveFile(event);
        }

        if (data.file) {
            return self.prepareToReceiveFile(data);
        }

        if (data.typing && !data.message) {
            console.log("Partner is typing...");
            self.displayPartnerTyping();
        }
        else {
            console.log("Received a message: ", data.message);
            self.writeToChatLog(data.message, "text-info recv", false);
        }
    }

    // Audio and Video

    self.initVideo = function() {
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        var video = myVideo;

        navigator.getUserMedia({
            audio: false,
            video: true
        }, function (stream) {
          if (window.URL)
            video.src = window.URL.createObjectURL(stream);
          else
            video.src = stream;
          window.myStream = stream;
          self.activeConnection.addStream(window.myStream);
            self.activeChannel.send(JSON.stringify({
                message: "Started sharing video."
            }));
            self.writeToChatLog(
                "Started sharing video.",
                "text-success sent insecure", false);
        }, function (error){
          console.log('navigator.getUserMedia error: ', error);
        });
    }

    // Files

    self.prepareToReceiveFile = function(data) {
        self.startTime = null;
        self.receiveBuffer = null;
        self.receivedSize = null;
        self.bitrateMax = null;
        self.numReceivedFiles = self.numReceivedFiles || 0;
        self.receivedFiles = self.receivedFiles || {};
        self.writeToChatLog(
            "Receiving " + data.name + " (" + data.size + " bytes)",
            "text-success sent insecure",
            false
        );
        self.numReceivedFiles += 1;
        // XSS out the wazoo
        var downloadLink = downloadsAnchor.innerHTML += (
            '
'
            + 'Download: ' + data.name + ''
            + " (" + data.size + " bytes)"
            + '
'
        )
        self.receivedFiles[data.name] = data;
        self.receivedFiles[data.name].id = self.numReceivedFiles;
        self.receivedFiles[data.name].anchor = document.getElementById('recvd-' + self.numReceivedFiles);
        self.incomingFile = self.receivedFiles[data.name]
    }

    self.receiveFile = function(event) {
        self.startTime = self.startTime || (new Date).getTime();
        self.receiveBuffer = self.receiveBuffer || [];
        self.receivedSize = self.receivedSize || 0;
        self.bitrateMax = self.bitrateMax || 0;

        self.receiveBuffer.push(event.data);
        self.receivedSize += event.data.byteLength;

        receiveProgress.value = self.receivedSize;

        // we are assuming that our signaling protocol told
        // about the expected file size (and name, hash, etc).
        var received = new window.Blob(self.receiveBuffer);
        self.receiveBuffer = [];

        var url = URL.createObjectURL(received);
        self.incomingFile.anchor.href = url;

        var bitrate = Math.round(self.receivedSize * 8 /
            ((new Date()).getTime() - self.startTime));

        if (bitrate > self.bitrateMax) {
            self.bitrateMax = bitrate;
        }

        bitrateDiv.innerHTML = 'Bitrate: ' +
            bitrate + ' kbits/sec (max: ' + self.bitrateMax + ' kbits/sec)';

        if (self.receivedSize >= self.incomingFile.size) {
            self.writeToChatLog(
                "Receiving " + self.incomingFile.name + " finished! Download it below.",
                "text-success sent insecure",
                false
            );
            self.activeChannel.send(JSON.stringify({
                message: 'Received ' + self.incomingFile.name + " succesfully.",
            }));
        } else {
            self.incomingFile.anchor.disabled = true;
        }
    }

    self.sendFile = function(files) {
        var file = [].concat.apply([], files).slice(-1)[0];
        console.log('File is ' + [file.name, file.size, file.type,
            file.lastModifiedDate
        ].join(' '));
        self.activeChannel.send(JSON.stringify({
            file: true,
            name: file.name,
            size: file.size,
        }));
        self.writeToChatLog("Sending " + file.name, "text-success sent insecure", false);

        // Handle 0 size files.
        statusMessage.textContent = '';
        if (file.size === 0) {
          bitrateDiv.innerHTML = '';
          statusMessage.textContent = 'File is empty, please select a non-empty file';
          return;
        }
        sendProgress.max = file.size;
        receiveProgress.max = file.size;
        var chunkSize = 16384;
        var sliceFile = function(offset) {
          var reader = new window.FileReader();
          reader.onload = (function() {
            return function(e) {
              self.activeChannel.send(e.target.result);
              if (file.size > offset + e.target.result.byteLength) {
                window.setTimeout(sliceFile, 0, offset + chunkSize);
              }
              sendProgress.value = offset + e.target.result.byteLength;
            };
          })(file);
          var slice = file.slice(offset, offset + chunkSize);
          reader.readAsArrayBuffer(slice);
        };
        sliceFile(0);
    }

/* Utilities */

    // set these to your own functions using WebRTCChat.writeToChatLog = function(...) {...}

    self.writeToChatLog = function(message, message_type, secure) {console.log("-> ", message, message_type, secure);}

    self.displayPartnerTyping = function() {console.log("-> Typing...");}
}
<|endoftext|>