<commit_before>var fields_module = require('./fields'),
    Field = fields_module.Field,
    types = require('./types'),
    Type = types.Type,
    utils = require('./utils');

var Form = exports.Form = function (obj, doc) {
    this.fields = (obj instanceof Type) ? obj.fields: obj;
    if (doc) {
        this.values = doc;
    }
};

Form.prototype.validate = function (req) {
    this.values = exports.parseRequest(this.fields, req);
    var validation_errors = types.validateFields(
        this.fields, this.values, this.values, [], false
    );
    var required_errors = types.checkRequired(this.fields, this.values, []);
    this.errors = required_errors.concat(validation_errors);
    return this;
};

Form.prototype.toHTML = function (iterator) {
    iterator = iterator || exports.render.div;
    return exports.renderFields(
        iterator, this.fields, this.values, this.errors, []
    );
};

Form.prototype.isValid = function () {
    return (!this.errors || this.errors.length === 0);
};

exports.parseRequest = function (fields, req) {
    var values = {};
    for (var k in req.form) {
        var f = utils.getPropertyPath(fields, k.split('.'));
        if (f instanceof Field) {
            var val = req.form[k];
            // has a value or omit_empty option is false
            if ((val !== undefined && val !== '') || !f.omit_empty) {
                val = f.parse(val);
                utils.setPropertyPath(values, k.split('.'), val);
            }
        }
        else {
            utils.setPropertyPath(values, k.split('.'), req.form[k]);
        }
    }
    return values;
};

exports.getErrors = function (errors, path) {
    if (!errors || !errors.length) {
        return [];
    }
    var errs = [];
    var field_name = path.join('.');
    for (var i = 0; i < errors.length; i++) {
        if (errors[i].field.join('.') === field_name) {
            errs.push(errors[i]);
        }
    }
    return errs;
};

exports.renderFields = function (iterator, fields, values, errors, path) {
    var html = '';
    for (var k in fields) {
        if (fields.hasOwnProperty(k)) {
            var field = fields[k];
            var field_path = path.concat([k]);
            var errs = exports.getErrors(errors, field_path);
            if (field instanceof Field) {
                var name = field_path.join('.');
                var val;
                if (values) {
                    val = utils.getPropertyPath(values, field_path);
                }
                else if (field.default_value) {
                    val = field.default_value;
                }
                else {
                    val = undefined;
                }
                html += iterator(name, field, val, errs);
            }
            else if (utils.isArray(field)) {
                // TODO: handle arrays
                throw new Error('not implemented');
            }
            else {
                var subvalues = utils.getPropertyPath(values, field_path);
                // sub-object, recurse through and check for more fields
                html += exports.renderFields(
                    iterator, field, subvalues, errors, field_path
                );
            }
        }
    }
    return html;
};

/**
 * iterator functions for use with renderFields
 */

var wrapWith = function (tag) {
    return function (name, field, value, errors) {
        var html = '<' + tag +
            ' class="' + field.classes(errors).join(' ') + '">';

        if (field.widget.type === 'hidden') {
            return field.widget.toHTML(name, value);
        }
        else if (field.widget.type === 'multipleCheckbox' ||
           field.widget.type === 'multipleRadio') {
            html += '<fieldset>' +
                '<legend>' + field.labelText(name) + '</legend>' +
                field.errorHTML() +
                field.widget.toHTML(name, value) +
            '</fieldset>';
        }
        else {
            html += field.errorHTML(errors) +
                field.labelHTML(name) +
                field.widget.toHTML(name, value);
        }
        return html + '</' + tag + '>';
    };
};

exports.render = {
    div: wrapWith('div'),
    p: wrapWith('p'),
    li: wrapWith('li'),
    table: function (name, field, value, errors) {
        // TODO: handle hidden, multipleCheckbox and multipleRadio fields
        return '
' +
            '' + field.labelHTML(name) + '	' +
            '' +
                field.errorHTML(errors) +
                field.widget.toHTML(name, value) +
            '	' +
        '
';
    }
};
<commit_msg>handle hidden fields when using render.table<commit_after>var fields_module = require('./fields'),
    Field = fields_module.Field,
    types = require('./types'),
    Type = types.Type,
    utils = require('./utils');

var Form = exports.Form = function (obj, doc) {
    this.fields = (obj instanceof Type) ? obj.fields: obj;
    if (doc) {
        this.values = doc;
    }
};

Form.prototype.validate = function (req) {
    this.values = exports.parseRequest(this.fields, req);
    var validation_errors = types.validateFields(
        this.fields, this.values, this.values, [], false
    );
    var required_errors = types.checkRequired(this.fields, this.values, []);
    this.errors = required_errors.concat(validation_errors);
    return this;
};

Form.prototype.toHTML = function (iterator) {
    iterator = iterator || exports.render.div;
    return exports.renderFields(
        iterator, this.fields, this.values, this.errors, []
    );
};

Form.prototype.isValid = function () {
    return (!this.errors || this.errors.length === 0);
};

exports.parseRequest = function (fields, req) {
    var values = {};
    for (var k in req.form) {
        var f = utils.getPropertyPath(fields, k.split('.'));
        if (f instanceof Field) {
            var val = req.form[k];
            // has a value or omit_empty option is false
            if ((val !== undefined && val !== '') || !f.omit_empty) {
                val = f.parse(val);
                utils.setPropertyPath(values, k.split('.'), val);
            }
        }
        else {
            utils.setPropertyPath(values, k.split('.'), req.form[k]);
        }
    }
    return values;
};

exports.getErrors = function (errors, path) {
    if (!errors || !errors.length) {
        return [];
    }
    var errs = [];
    var field_name = path.join('.');
    for (var i = 0; i < errors.length; i++) {
        if (errors[i].field.join('.') === field_name) {
            errs.push(errors[i]);
        }
    }
    return errs;
};

exports.renderFields = function (iterator, fields, values, errors, path) {
    var html = '';
    for (var k in fields) {
        if (fields.hasOwnProperty(k)) {
            var field = fields[k];
            var field_path = path.concat([k]);
            var errs = exports.getErrors(errors, field_path);
            if (field instanceof Field) {
                var name = field_path.join('.');
                var val;
                if (values) {
                    val = utils.getPropertyPath(values, field_path);
                }
                else if (field.default_value) {
                    val = field.default_value;
                }
                else {
                    val = undefined;
                }
                html += iterator(name, field, val, errs);
            }
            else if (utils.isArray(field)) {
                // TODO: handle arrays
                throw new Error('not implemented');
            }
            else {
                var subvalues = utils.getPropertyPath(values, field_path);
                // sub-object, recurse through and check for more fields
                html += exports.renderFields(
                    iterator, field, subvalues, errors, field_path
                );
            }
        }
    }
    return html;
};

/**
 * iterator functions for use with renderFields
 */

var wrapWith = function (tag) {
    return function (name, field, value, errors) {
        var html = '<' + tag +
            ' class="' + field.classes(errors).join(' ') + '">';

        if (field.widget.type === 'hidden') {
            return field.widget.toHTML(name, value);
        }
        else if (field.widget.type === 'multipleCheckbox' ||
           field.widget.type === 'multipleRadio') {
            html += '<fieldset>' +
                '<legend>' + field.labelText(name) + '</legend>' +
                field.errorHTML() +
                field.widget.toHTML(name, value) +
            '</fieldset>';
        }
        else {
            html += field.errorHTML(errors) +
                field.labelHTML(name) +
                field.widget.toHTML(name, value);
        }
        return html + '</' + tag + '>';
    };
};

exports.render = {
    div: wrapWith('div'),
    p: wrapWith('p'),
    li: wrapWith('li'),
    table: function (name, field, value, errors) {
        // TODO: handle multipleCheckbox and multipleRadio fields
        if (field.widget.type === 'hidden') {
            return field.widget.toHTML(name, value);
        }
        return '
' +
            '' + field.labelHTML(name) + '	' +
            '' +
                field.errorHTML(errors) +
                field.widget.toHTML(name, value) +
            '	' +
        '
';
    }
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Annict 放送予定仕切
// @description Annict の放送予定ページで既に放送済みと予定のエピソードに仕切り線を入れます。また、放送予定か放送済みかで色を変えます。
// @namespace   imatake.github.io
// @include     https://annict.com/programs
// @author      imatake
// @version     1.00.20160428
// @license     MIT License
// @icon        data:image/gif;base64,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
// @grant       none
// ==/UserScript==
(function (doc) {
  // ページロード後のイベントで処理
  // Infinite Scroll とかどうしようか…
  doc.body.addEventListener('load', function (e) {
    var now_dt = new Date();
    var before_dt = new Date(now_dt.getTime() - 30 * 60 * 1000);
    var after_dt = new Date(now_dt.getTime() + 30 * 60 * 1000);
    var prev_hr = null;
    var objs = null;
    //
    $('div.program').each(function () {
      //console.info(this);
      // 放送エピソード
      objs = $(this).find('div.media');
      var media = (objs != null && objs.length > 0) ? objs[0] : null;
      //console.info(media);
      // 放送開始時刻
      objs = $(this).find('span.started-at');
      var started_at = (objs != null && objs.length > 0) ? objs[0] : null;
      //console.info(started_at);
      var started_at_str = started_at != null ? started_at.textContent : '1/1 0:00';
      var started_at_obj = new Date('2016/' + started_at_str.replace(' ~', ''));
      //console.info('started_at: ' + started_at_str + ' -> ' + started_at_obj);
      // 水平線
      objs = $(this).children('hr');
      var hr = (objs != null && objs.length > 0) ? objs[0] : null;
      //console.info(hr);
      // 放送前のエピソードの水平線を残す
      if (started_at_obj > now_dt && hr != null) prev_hr = hr;
      // 放送予定か放送済みかで色を変える
      if (started_at_obj > now_dt) { // 放送予定
        started_at.style = 'color: mediumorchid;';
        if (started_at_obj < after_dt) { // (たぶん)これから
          media.style = 'background-color: lavenderblush;';
        } else {
        }
      } else { // 放送済み
        started_at.style = 'color: royalblue;';
        if (started_at_obj > before_dt) { // (たぶん)放送中
          media.style = 'background-color: lightcyan;';
        } else {
        }
      }
    });
    // 最後の放送予定の水平線のスタイルを変更する
    if (prev_hr != null) prev_hr.style = 'background-color: red; height: 2px;';
  }, true);
}) (document);
<commit_msg>放送前の開始時刻の文字色変更をコメントアウト。6時間以上前の放送予定を非表示に。<commit_after>// ==UserScript==
// @name        Annict 放送予定仕切
// @description Annict の放送予定ページで既に放送済みと予定のエピソードに仕切り線を入れます。また、放送予定か放送済みかで放送開始時刻の文字色を変えます。放送予定の表示範囲を抑制します。
// @namespace   imatake.github.io
// @include     https://annict.com/programs
// @author      imatake
// @version     1.00.20160520
// @license     MIT License
// @icon        data:image/gif;base64,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
// @grant       none
// ==/UserScript==
(function (doc) {
  // ページロード後のイベントで処理
  // Infinite Scroll とかどうしようか…
  doc.body.addEventListener('load', function (e) {
    var now_dt = new Date();
    // 放送中と判断する時刻(通算ミリ秒, now_dt.getTime() - 30 * 60 * 1000 で現在時刻より30分前)
    var before_dt = new Date(now_dt.getTime() - 30 * 60 * 1000);
    // 直近の放送と判断する時刻(通算ミリ秒, now_dt.getTime() + 30 * 60 * 1000 で現在時刻より30分後)
    var after_dt = new Date(now_dt.getTime() + 30 * 60 * 1000);
    // 非表示とする放送予定の時刻(通算ミリ秒, now_dt.getTime() + 6 * 60 * 60 * 1000 で現在時刻より30分前)
    var hidden_dt = new Date(now_dt.getTime() + 6 * 60 * 60 * 1000);
    // ---------- ----------
    var objs = null;
    var prev_hr = null;
    $('div.program').each(function () {
      //console.info(this);
      // 放送エピソード
      objs = $(this).find('div.media');
      var media = (objs != null && objs.length > 0) ? objs[0] : null;
      //console.info(media);
      // 放送開始時刻
      objs = $(this).find('span.started-at');
      var started_at = (objs != null && objs.length > 0) ? objs[0] : null;
      //console.info(started_at);
      var started_at_str = started_at != null ? started_at.textContent : '1/1 0:00';
      var started_at_obj = new Date('2016/' + started_at_str.replace(' ~', ''));
      //console.info('started_at: ' + started_at_str + ' -> ' + started_at_obj);
      // 水平線
      objs = $(this).children('hr');
      var hr = (objs != null && objs.length > 0) ? objs[0] : null;
      if (hr != null) hr.style = null; // 一旦スタイルをクリア
      //console.info(hr);
      // ---------- ----------
      // 指定時刻より前のものは隠す
      if (started_at_obj > hidden_dt) media.parentNode.style = 'display: none;';
      // 放送予定か放送済みか
      if (started_at_obj > now_dt) { // == 放送予定 ==
        if (hr != null) prev_hr = hr; // 放送前のエピソードの水平線を残す
        // started_at.style = 'color: mediumorchid;'; // 放送予定の開始時刻の文字色設定
        if (started_at_obj < after_dt) {
          media.style = 'background-color: lavenderblush;'; // 直近の放送予定の背景色変更
        } else { // == ↑(たぶん)これから / それより前↓ ==
        }
      } else { // == 放送済み ==
        started_at.style = 'color: royalblue;'; // 放送済みの開始時刻の文字色設定
        if (started_at_obj > before_dt) {
          media.style = 'background-color: lightcyan;'; // 放送中の背景色変更
        } else { // == ↑(たぶん)放送中 / それより後↓ ==
        }
      }
    });
    // 最後の放送予定の水平線のスタイルを変更する
    if (prev_hr != null) prev_hr.style = 'background-color: red; height: 2px;';
  }, true);
}) (document);
<|endoftext|>


<commit_before>(function() { 'use strict'; define(function({ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
	exports,
}) {

const _chrome = typeof chrome !== 'undefined' && chrome;
const _browser = typeof browser !== 'undefined' && browser;
const _api = _browser || _chrome;

const cache = new WeakMap;
let messageHandler;

const ua = navigator.userAgent;
const rootUrl = _api.extension.getURL('.').slice(0, -1);
const blink = rootUrl.startsWith('chrome');
const opera = blink && (/ OPR\/\d+\./).test(ua); // TODO: is this safe to do?
const vivaldi = blink && (/ Vivaldi\/\d+\./).test(ua); // TODO: is this safe to do?
const google = blink && !opera && !vivaldi; // TODO: thst for Google Chrome specific api
const chromium = blink && !opera && !vivaldi && !google;

const gecko = rootUrl.startsWith('moz');
const fennec = gecko && !(_api.browserAction && _api.browserAction.setPopup); // can't use userAgent (may be faked) // TODO: test
const firefox = gecko && !fennec;

const edgeHTML = rootUrl.startsWith('ms-browser');
const edge = edgeHTML;

const currentApp = (() => { switch (true) {
	case (firefox):         return 'firefox';
	case (fennec):          return 'fennec';
	case (chromium):        return 'chromium';
	case (opera):           return 'opera';
	case (vivaldi):         return 'vivaldi';
	case (google):          return 'chrome';
	case (edge):            return 'edge';
} })();

const appVersion = (() => { switch (true) {
	case (edge):            return           (/Edge\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (vivaldi):         return        (/Vivaldi\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (opera):           return            (/OPR\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (blink):           return (/Chrom(?:e|ium)\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (fennec): switch (false) {
		case !(_api.pageAction && _api.pageAction.show): return '50.0';
		default: return '48.0';
	} break;
	case (firefox): switch (false) {
		case !(_api.runtime.connectNative || _api.history && _api.history.getVisits): return '50.0'; // these require permissions
		case !(_api.tabs.removeCSS): return '49.0';
		case !(_api.commands.getAll): return '48.0';
		case !(_api.tabs.insertCSS): return '47.0';
		case !(_api.tabs.move): return '46.0';
		default: return '45.0';
	}
	return '0';
} })();

/**
 * This is a flat copy of the window.chrome / window.browser API with the additional properties:
 *
 *     <any browser/chrome API starting with a capital letter>:
 *                          If a Promise capable version of the API exists, then that API.
 *                          Otherwise a deep clone of the original chrome/browser API with the difference
 *                          that all methods of these objects are wrapped such that they automatically
 *                          add a callback as the last parameter and return a promise instead.
 *                          Calling these wrapped functions with a callback parameter will not work,
 *                          because it would result in an invalid signature:
 *                          ``Chrome.Runtime.getManifest()`` will not work as expected, but ``Chrome.runtime.getManifest()`` still does.
 *                          The methods of objects starting with /^on[A-Z]/ (event listeners) are not wrapped,
 *                          so ``Chrome.Storage.onUpdate.addListener(function)`` still works.
 *
 *     Storage:             As described above, only that .Storage.sync === .Storage.local if .storage.sync doesn't exist.
 *     <any chrome API>:    The original chrome[API], or browser[API] if `chrome` doesn't exist.
 *
 *     messages/Messages:   A MessageHandler instance for more convenient message sending and receiving, @see MessageHandler.
 *
 *     applications:        An object of booleans indicating the browser this WebExtension is running in
 *                          Accessing any other property than those listed above will throw:
 *                              gecko:          Any Mozilla browser.
 *                              firefox:        Firefox desktop.
 *                              fennec:         Firefox for Android. This is not extracted from the userAgent.
 *                              blink:          Any blink/chromium based browser.
 *                              chromium:       Chromium and not Google Chrome, Opera or Vivaldi.
 *                              opera:          Opera desktop (Chromium).
 *                              vivaldi:        Vivaldi (Chromium).
 *                              google:         Google Chrome (Chromium).
 *                              chrome:         Google Chrome (Chromium) (alias).
 *                              edgeHTML:       MS Edge
 *                              edge:           MS Edge
 *                              current:        String naming the current browser, one of [ 'firefox', 'fennec', 'chromium', 'opera', 'vivaldi', 'chrome', 'edge', ].
 *                              version:        String version of the current browser, as read from the UserAgent string. For gecko browsers it is feature-detected.
 *
 *     rootUrl/rootURL:     The extensions file root URL, ends with '/'.
 *     chrome:              Non Promise-capable chrome/browser API, bug-fixed (see below)
 *     browser:             Native Promise-capable chrome/browser API, or null, bug-fixed (see below)
 *
 * Furthermore this Chrome object (compared to window.chrome) fixes the Firefox bug that window.parent.chrome has more properties than window.chrome (in an iframe).
 */
const Chrome = new Proxy(Object.freeze({
	chrome: edgeHTML ? _browser : _chrome,
	browser: gecko ? _browser : null,
	rootUrl, rootURL: rootUrl,
	get messages() { return new MessageHandler; },
	get Messages() { return new MessageHandler; },
	applications: new Proxy(Object.freeze({
		gecko, firefox, fennec,
		blink, chromium, google, chrome: google, opera, vivaldi,
		edgeHTML, edge,
		current: currentApp, version: appVersion,
	}), { get(self, key) {
		if (self.hasOwnProperty(key)) { return self[key]; }
		throw new Error(`Unknown application "${ key }"`);
	}, set() { }, }),
	Storage: cloneLocal(gecko ? _browser.storage : wrapAPI(_api.storage))
}), { get(self, key) {
	let value;
	value = self[key]; if (value) { return value; }
	value = edgeHTML ? _browser[key] : _chrome[key]; if (value) { return value; }
	key = key.replace(/^./, s => s.toLowerCase());
	value = gecko ? _browser[key] : wrapAPI(_api[key]); if (value) { return value; }
}, set() { }, });

let mh_handlers = { };
let mh_listener = null;
let mh_request, mh_post; // these functions are defined below

/**
 * The MessageHandler singleton wraps chrome.runtime/tabs.sendMessage and chrome.runtime.onMessage with the following benefits:
 *  - Promises: the send function returns promises instead of taking callbacks, all handlers can return Promises to return asynchronously
 *  - Proper error handling: all Error objects thrown by the handlers (also in rejected promises) are encoded properly and will reject the Promise on the sending side
 *  - Simpler API, especially for multiple (named) handlers -> no more switches in a single huge message handler
 */
class MessageHandler {
	/// singleton: calling this will return the existing instance
	constructor() {
		if (messageHandler) { return messageHandler; }
		return (messageHandler = this);
	}
	static get instance() { return new MessageHandler; }
	set isExclusiveMessageHandler(_) { console.trace('MessageHandler.isExclusiveMessageHandler is deprecated'); }

	/**
	 * Adds a named message handler.
	 * @param  {string}    name     Optional. Non-empty name of this handler which can me used
	 *                              by .request() and .post() to call this handler. defaults to `handler`.name.
	 * @param  {function}  handler  The handler function. It will be called with JSON-clones of all additional arguments
	 *                              provided to .request() or .post() and may return a Promise to asynchronously return a value.
	 * @return {MessageHandler}     `this` for chaining.
	 * @throws {Error}              If there is already a handler registered for `name`.
	 */
	addHandler(name, handler) {
		if (arguments.length === 1) { handler = name; name = handler.name; }
		if (!name || typeof name !== 'string') { throw new TypeError(`Handler names must be non-empty strings`); }
		if (typeof handler !== 'function') { throw new TypeError(`Message handlers must be functions`); }
		if (mh_handlers[name]) { throw new Error(`Duplicate message handler for "${ name }"`); }
		mh_handlers[name] = handler;
		mh_attach();
		return messageHandler;
	}
	/**
	 * Adds multiple named message handlers.
	 * @param  {string}        prefix    Optional. Prefix to prepend to all handler names specified in `handlers`. Defaults to ''.
	 * @param  {object|array}  handlers  Ether an array of named functions or an object with methods. Array entries / object properties that are no functions will be ignores.
	 * @return {MessageHandler}          `this` for chaining.
	 * @throws {Error}                   If there is already a handler registered for any `prefix` + handler.name; no handlers have been added.
	 */
	addHandlers(prefix, handlers) {
		if (arguments.length === 1) { handlers = prefix; prefix = ''; }
		if (typeof prefix !== 'string') { throw new TypeError(`Handler name prefixes must be strings (or omitted)`); }
		const add = (Array.isArray(handlers) ? handlers.map(f => [ f.name, f, ]) : Object.keys(handlers).map(k => [ k, handlers[k], ])).filter(([ , f, ]) => typeof f === 'function');
		add.forEach(([ name, handler, ]) => {
			if (name === prefix) { throw new TypeError(`Handler names must be non-empty strings`); }
			if (mh_handlers[name]) { throw new Error(`Duplicate message handler for "${ name }"`); }
		});
		add.forEach(([ name, handler, ]) => mh_handlers[prefix + name] = handler);
		mh_attach();
		return messageHandler;
	}
	/**
	 * Removes a named handler.
	 * @param  {string}  name  The name of the handler to be removed.
	 * @return {bool}          true iff a handler existed and has been removed.
	 */
	removeHandler(name) {
		const ret = delete mh_handlers[name];
		ret && mh_detatch();
		return ret;
	}
	/**
	 * Calls a handler in a different context and returns a Promise to its return value.
	 * @param  {object}  tab   Optional. Object of { tabId, frameId, } to send a message to all frames in a tab
	 *                         or optionally a single frame in the tab if frameId is set.
	 * @param  {string}  name  Name of the remote handler to call.
	 * @param  {...any}  args  Additional arguments whose JSON-clones are passed to the remote handler.
	 * @return {Promise}       Promise that rejects if the request wasn't handled by any context
	 *                         or if the handler threw and otherwise resolves to the handlers return value.
	 */
	request(/* arguments */) {
		return mh_request.apply(null, arguments).then(arg => {
			if (!arg) { throw new Error(`No message handler found for "${ name }"`); }
			if (arg.threw) { throw fromJson(arg.error); }
			return arg.value;
		});
	}
	/**
	 * Calls a handler in a different context without waiting for its return value and without guarantee that a handler has in fact been called.
	 * @param  {object}  tab   Optional. Object of { tabId, frameId, } to send a message to all frames in a tab
	 *                         or optionally a single frame in the tab if frameId is set.
	 * @param  {string}  name  Name of the remote handler to call.
	 * @param  {...any}  args  Additional arguments whose JSON-clones are passed to the remote handler.
	 */
	post(/* arguments */) {
		return mh_post.apply(null, arguments);
	}
}

// Deeply clones an object but replaces all functions with Promise-wrapped functions.
function wrapAPI(api) {
	if (!api) { return api; }
	let clone = cache.get(api);
	if (clone) { return clone; }
	clone = promisifyAll(api);
	cache.set(api, clone);
	return clone;
}
function promisifyAll(api) {
	const clone = { };
	Object.keys(api).forEach(key => {
		let value = api[key];
		if (typeof value === 'function') {
			value = promisify(value, api);
		} else if (typeof value === 'object' && !(/^on[A-Z]/).test(key)) {
			value = wrapAPI(value);
		}
		clone[key] = value;
	});
	return Object.freeze(clone);
}

function cloneLocal(storage) {
	if (storage.sync) { return storage; }
	storage = Object.assign({ }, storage);
	// console.info('chrome.storage.sync is unavailable, fall back to chrome.storage.local');
	storage.sync = storage.local;
	return Object.freeze(storage);
}

function promisify(method, thisArg) {
	return function() {
		return new Promise((resolve, reject) => {
			method.call(thisArg, ...arguments, function() {
				const error = _api.runtime.lastError || _api.extension.lastError;
				return error ? reject(error) : resolve(...arguments);
			});
		});
	};
}

mh_request = makeSendFunction(
	gecko ? _browser.runtime.sendMessage : promisify(_api.runtime.sendMessage, _api.runtime),
	_api.tabs
	? gecko ? _browser.tabs.sendMessage : promisify(_api.tabs.sendMessage, _api.tabs)
	: () => { throw new Error(`Can't send messages to tabs (from within a tab)`); },
	false
);
mh_post = makeSendFunction(
	_api.runtime.sendMessage,
	_api.tabs ? _api.tabs.sendMessage : () => { throw new Error(`Can't send messages to tabs (from within a tab)`); },
	true
);
function makeSendFunction(send, sendTab, post) {
	return function() {
		const tab = arguments[0];
		if (typeof tab === 'object') {
			const { tabId, frameId, } = tab;
			const [ , name, ...args ] = arguments;
			if (!name || typeof name !== 'string') { throw new TypeError(`Handler names must be non-empty strings`); }
			return sendTab(tabId, { name, args, post, }, frameId ? { frameId, } : { });
		} else {
			const [ name, ...args ] = arguments;
			if (!name || typeof name !== 'string') { throw new TypeError(`Handler names must be non-empty strings`); }
			return send({ name, args, post, });
		}
	};
}

function mh_attach() {
	if (mh_listener) { return; }
	mh_listener = ({ name, args, post, }, sender, reply) => {
		if (!mh_handlers[name]) { return; }
		if (post) { mh_handlers[name].apply(sender, args); return; }
		try {
			const value = mh_handlers[name].apply(sender, args);
			if (value instanceof Promise) {
				Promise.prototype.then.call(value,
					value => reply({ value, }),
					error => reply({ error: toJson(error), threw: true, })
				);
				return true;
			} else {
				reply({ value, });
			}
		} catch (error) { reply({ error: toJson(error), threw: true, }); }
	};

	_api.runtime.onMessage.addListener(mh_listener);
}

function mh_detatch() {
	if (!mh_listener || Object.keys(mh_handlers).length) { return; }
	_api.runtime.onMessage.removeListener(mh_listener);
	mh_listener = null;
}

function toJson(value) {
	return JSON.stringify(value, (key, value) => {
		if (!value || typeof value !== 'object') { return value; }
		if (value instanceof Error) { return '$_ERROR_$'+ JSON.stringify({ name: value.name, message: value.message, stack: value.stack, }); }
		return value;
	});
}
function fromJson(string) {
	if (typeof string !== 'string') { return string; }
	return JSON.parse(string, (key, value) => {
		if (!value || typeof value !== 'string' || !value.startsWith('$_ERROR_$')) { return value; }
		const object = JSON.parse(value.slice(9));
		const Constructor = object.name ? window[object.name] || Error : Error;
		const error = gecko ? Object.create(Constructor.prototype) : new Constructor; // Firefox (49) won't log any properties of actual Error instances to the web pages console
		Object.assign(error, object);
		return error;
	});
}

return (Chrome);

}); })();
<commit_msg>use es6lib/Port to implement chrome.Messages<commit_after>(function(global) { 'use strict'; const factory = function webExtUtils_chrome(exports) { // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

const _chrome = typeof chrome !== 'undefined' && chrome;
const _browser = typeof browser !== 'undefined' && browser;
const _api = _browser || _chrome;

const cache = new WeakMap;
let messageHandler;

const ua = navigator.userAgent;
const rootUrl = _api.extension.getURL('.').slice(0, -1);
const blink = rootUrl.startsWith('chrome');
const opera = blink && (/ OPR\/\d+\./).test(ua); // TODO: is this safe to do?
const vivaldi = blink && (/ Vivaldi\/\d+\./).test(ua); // TODO: is this safe to do?
const google = blink && !opera && !vivaldi; // TODO: thst for Google Chrome specific api
const chromium = blink && !opera && !vivaldi && !google;

const gecko = rootUrl.startsWith('moz');
const fennec = gecko && !(_api.browserAction && _api.browserAction.setPopup); // can't use userAgent (may be faked) // TODO: this may be added in the future
const firefox = gecko && !fennec;

const edgeHTML = rootUrl.startsWith('ms-browser');
const edge = edgeHTML;

const currentApp = (() => { switch (true) {
	case (firefox):         return 'firefox';
	case (fennec):          return 'fennec';
	case (chromium):        return 'chromium';
	case (opera):           return 'opera';
	case (vivaldi):         return 'vivaldi';
	case (google):          return 'chrome';
	case (edge):            return 'edge';
} })();

const appVersion = (() => { switch (true) {
	case (edge):            return           (/Edge\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (vivaldi):         return        (/Vivaldi\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (opera):           return            (/OPR\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (blink):           return (/Chrom(?:e|ium)\/((?:\d+.)*\d+)/).exec(ua)[1];
	case (fennec): switch (false) {
		// TODO: keep up to date
		case !(_api.pageAction && _api.pageAction.show): return '50.0';
		default: return '48.0';
	} break;
	case (firefox): switch (false) {
		// TODO: keep up to date
		case !(_api.runtime.connectNative || _api.history && _api.history.getVisits): return '50.0'; // these require permissions
		case !(_api.tabs.removeCSS): return '49.0';
		case !(_api.commands.getAll): return '48.0';
		case !(_api.tabs.insertCSS): return '47.0';
		case !(_api.tabs.move): return '46.0';
		default: return '45.0';
	}
	return '0';
} })();

/**
 * This is a flat copy of the window.chrome / window.browser API with the additional properties:
 *
 *     <any browser/chrome API starting with a capital letter>:
 *                          If a Promise capable version of the API exists, then that API.
 *                          Otherwise a deep clone of the original chrome/browser API with the difference
 *                          that all methods of these objects are wrapped such that they automatically
 *                          add a callback as the last parameter and return a promise instead.
 *                          Calling these wrapped functions with a callback parameter will not work,
 *                          because it would result in an invalid signature:
 *                          ``Chrome.Runtime.getManifest()`` will not work as expected, but ``Chrome.runtime.getManifest()`` still does.
 *                          The methods of objects starting with /^on[A-Z]/ (event listeners) are not wrapped,
 *                          so ``Chrome.Storage.onUpdate.addListener(function)`` still works.
 *
 *     Storage:             As described above, only that .Storage.sync === .Storage.local if .storage.sync doesn't exist.
 *     <any chrome API>:    The original chrome[API], or browser[API] if `chrome` doesn't exist.
 *
 *     messages/Messages:   An es6lib/Port that wrapps the runtime/tabs.on/sendMessage API for more convenient message sending and receiving.
 *                          'es6lib/port.js' to be loaded at the time of accessing. @see https://github.com/NiklasGollenstede/es6lib/blob/master/port.js
 *
 *     applications:        An object of booleans indicating the browser this WebExtension is running in
 *                          Accessing any other property than those listed above will throw:
 *                              gecko:          Any Mozilla browser.
 *                              firefox:        Firefox desktop.
 *                              fennec:         Firefox for Android. This is not extracted from the userAgent.
 *                              blink:          Any blink/chromium based browser.
 *                              chromium:       Chromium and not Google Chrome, Opera or Vivaldi.
 *                              opera:          Opera desktop (Chromium).
 *                              vivaldi:        Vivaldi (Chromium).
 *                              google:         Google Chrome (Chromium).
 *                              chrome:         Google Chrome (Chromium) (alias).
 *                              edgeHTML:       MS Edge
 *                              edge:           MS Edge
 *                              current:        String naming the current browser, one of [ 'firefox', 'fennec', 'chromium', 'opera', 'vivaldi', 'chrome', 'edge', ].
 *                              version:        String version of the current browser, as read from the UserAgent string. For gecko browsers it is feature-detected.
 *
 *     rootUrl/rootURL:     The extensions file root URL, ends with '/'.
 *     chrome:              Non Promise-capable chrome/browser API.
 *     browser:             Native Promise-capable chrome/browser API, or null.
 */
const Chrome = new Proxy(Object.freeze({
	chrome: edgeHTML ? _browser : _chrome,
	browser: gecko ? _browser : null,
	rootUrl, rootURL: rootUrl,
	get messages() { return getGlobalPort(); },
	get Messages() { return getGlobalPort(); },
	applications: new Proxy(Object.freeze({
		gecko, firefox, fennec,
		blink, chromium, google, chrome: google, opera, vivaldi,
		edgeHTML, edge,
		current: currentApp, version: appVersion,
	}), { get(self, key) {
		if (self.hasOwnProperty(key)) { return self[key]; }
		throw new Error(`Unknown application "${ key }"`);
	}, set() { }, }),
	Storage: cloneLocal(gecko ? _browser.storage : wrapAPI(_api.storage))
}), { get(self, key) {
	let value;
	value = self[key]; if (value) { return value; }
	value = edgeHTML ? _browser[key] : _chrome[key]; if (value) { return value; }
	key = key.replace(/^./, s => s.toLowerCase());
	value = gecko ? _browser[key] : wrapAPI(_api[key]); if (value) { return value; }
}, set() { }, });

function getGlobalPort() {
	if (messageHandler) { return messageHandler; }

	const Port = global.es6lib_port || require('node_modules/es6lib/port');

	const port = new Port(
		{ runtime: Chrome.Runtime, tabs: Chrome.Tabs, },
		Port.web_ext_Runtime
	);
	[ 'addHandler', 'addHandlers', 'removeHandler', 'hasHandler', 'request', 'post', 'destroy', ]
	.forEach(key => port[key] = port[key].bind(port));

	return (messageHandler = port);
}

// Deeply clones an object but replaces all functions with Promise-wrapped functions.
function wrapAPI(api) {
	if (!api) { return api; }
	let clone = cache.get(api);
	if (clone) { return clone; }
	clone = promisifyAll(api);
	cache.set(api, clone);
	return clone;
}
function promisifyAll(api) {
	const clone = { };
	Object.keys(api).forEach(key => {
		let value = api[key];
		if (typeof value === 'function') {
			value = promisify(value, api);
		} else if (typeof value === 'object' && !(/^on[A-Z]/).test(key)) {
			value = wrapAPI(value);
		}
		clone[key] = value;
	});
	return Object.freeze(clone);
}

function cloneLocal(storage) {
	if (storage.sync) { return storage; }
	storage = Object.assign({ }, storage);
	// console.info('chrome.storage.sync is unavailable, fall back to chrome.storage.local');
	storage.sync = storage.local;
	return Object.freeze(storage);
}

function promisify(method, thisArg) {
	return function() {
		return new Promise((resolve, reject) => {
			method.call(thisArg, ...arguments, function() {
				const error = _api.runtime.lastError || _api.extension.lastError;
				return error ? reject(error) : resolve(...arguments);
			});
		});
	};
}

return (Chrome);

}; if (typeof define === 'function' && define.amd) { define([ 'exports', ], factory); } else { const exp = { }, result = factory(exp) || exp; if (typeof exports === 'object' && typeof module === 'object') { module.exports = result; } else { global[factory.name] = result; } } })((function() { return this; })());
<|endoftext|>


<commit_before>var constants = require('../utils/constants.js')

function Issuer() {
  this.create = function (data, trs) {
    trs.recipientId = null
    trs.amount = '0'
    trs.asset.uiaIssuer = {
      name: data.name,
      desc: data.desc
    }

    return trs
  }

  this.calculateFee = function (trs, sender) {
    // FIXME catch exception
    // FIXME bind library on global
    // var bytes = this.getBytes(trs)
    //return (100 + (Math.floor(bytes.length / 200) + 1)) * library.base.block.calculateFee()
    return 100 * constants.fixedPoint
  }

  this.verify = function (trs, sender, cb) {
    if (trs.recipientId) return setImmediate(cb, 'Invalid recipient')
    if (trs.amount != '0') return setImmediate(cb, 'Invalid transaction amount')

    var issuer = trs.asset.uiaIssuer
    if (!/^[A-Za-z][A-Za-z0-9_\-]{0,19}$/.test(issuer.name)) return setImmediate(cb, 'Invalid issuer name')
    if (!issuer.desc) return setImmediate(cb, 'Invalid issuer desc')
    if (issuer.desc.length > 4096) return setImmediate(cb, 'Invalid issuer desc size')

    library.model.isIssuerExists(issuer.name, sender.address, function (err, exists) {
      if (err) return cb(err)
      if (exists) return cb('Double register')
      setImmediate(cb, null, trs)
    })
  }

  this.process = function (trs, sender, cb) {
    setImmediate(cb, null, trs)
  }

  this.getBytes = function (trs) {
    return Buffer.concat([
      new Buffer(trs.asset.uiaIssuer.name, 'utf8'),
      new Buffer(trs.asset.uiaIssuer.desc, 'utf8')
    ])
  }

  this.apply = function (trs, block, sender, cb) {
    setImmediate(cb)
  }

  this.undo = function (trs, block, sender, cb) {
    setImmediate(cb)
  }

  this.applyUnconfirmed = function (trs, sender, cb) {
    var nameKey = trs.asset.uiaIssuer.name + ':' + trs.type
    var idKey = sender.address + ':' + trs.type
    if (library.oneoff.has(nameKey) || library.oneoff.has(idKey)) {
      return setImmediate(cb, 'Double submit')
    }
    library.oneoff.set(nameKey, true)
    library.oneoff.set(idKey, true)
    setImmediate(cb)
  }

  this.undoUnconfirmed = function (trs, sender, cb) {
    var nameKey = trs.asset.uiaIssuer.name + ':' + trs.type
    var idKey = sender.address + ':' + trs.type
    library.oneoff.delete(nameKey)
    library.oneoff.delete(idKey)
    setImmediate(cb)
  }

  this.objectNormalize = function (trs) {
    var report = library.scheme.validate(trs.asset.uiaIssuer, {
      object: true,
      properties: {
        name: {
          type: 'string',
          minLength: 1,
          maxLength: 16
        },
        desc: {
          type: 'string',
          minLength: 1,
          maxLength: 4096
        }
      },
      required: ['name', 'desc']
    })

    if (!report) {
      throw Error('Can\'t parse transaction: ' + library.scheme.getLastError())
    }

    return trs
  }

  this.dbRead = function (raw) {
    if (!raw.issuers_name) {
      return null
    } else {
      var issuer = {
        transactionId: raw.t_id,
        name: raw.issuers_name,
        desc: raw.issuers_desc
      }

      return { uiaIssuer: issuer }
    }
  }

  this.dbSave = function (trs, cb) {
    // FIXME catch exceptions on upper layer
    var asset = trs.asset.uiaIssuer
    var values = {
      transactionId: trs.id,
      issuerId: trs.senderId,
      name: asset.name,
      desc: asset.desc
    }
    library.model.add('issuers', values, cb)
  }

  this.ready = function (trs, sender) {
    if (sender.multisignatures.length) {
      if (!trs.signatures) {
        return false
      }
      return trs.signatures.length >= sender.multimin - 1
    } else {
      return true
    }
  }
}

module.exports = new Issuer<commit_msg>fix issuer normalize issue<commit_after>var constants = require('../utils/constants.js')

function Issuer() {
  this.create = function (data, trs) {
    trs.recipientId = null
    trs.amount = '0'
    trs.asset.uiaIssuer = {
      name: data.name,
      desc: data.desc
    }

    return trs
  }

  this.calculateFee = function (trs, sender) {
    // FIXME catch exception
    // FIXME bind library on global
    // var bytes = this.getBytes(trs)
    //return (100 + (Math.floor(bytes.length / 200) + 1)) * library.base.block.calculateFee()
    return 100 * constants.fixedPoint
  }

  this.verify = function (trs, sender, cb) {
    if (trs.recipientId) return setImmediate(cb, 'Invalid recipient')
    if (trs.amount != '0') return setImmediate(cb, 'Invalid transaction amount')

    var issuer = trs.asset.uiaIssuer
    if (!/^[A-Za-z][A-Za-z0-9_\-]{0,19}$/.test(issuer.name)) return setImmediate(cb, 'Invalid issuer name')
    if (!issuer.desc) return setImmediate(cb, 'Invalid issuer desc')
    if (issuer.desc.length > 4096) return setImmediate(cb, 'Invalid issuer desc size')

    library.model.isIssuerExists(issuer.name, sender.address, function (err, exists) {
      if (err) return cb(err)
      if (exists) return cb('Double register')
      setImmediate(cb, null, trs)
    })
  }

  this.process = function (trs, sender, cb) {
    setImmediate(cb, null, trs)
  }

  this.getBytes = function (trs) {
    return Buffer.concat([
      new Buffer(trs.asset.uiaIssuer.name, 'utf8'),
      new Buffer(trs.asset.uiaIssuer.desc, 'utf8')
    ])
  }

  this.apply = function (trs, block, sender, cb) {
    setImmediate(cb)
  }

  this.undo = function (trs, block, sender, cb) {
    setImmediate(cb)
  }

  this.applyUnconfirmed = function (trs, sender, cb) {
    var nameKey = trs.asset.uiaIssuer.name + ':' + trs.type
    var idKey = sender.address + ':' + trs.type
    if (library.oneoff.has(nameKey) || library.oneoff.has(idKey)) {
      return setImmediate(cb, 'Double submit')
    }
    library.oneoff.set(nameKey, true)
    library.oneoff.set(idKey, true)
    setImmediate(cb)
  }

  this.undoUnconfirmed = function (trs, sender, cb) {
    var nameKey = trs.asset.uiaIssuer.name + ':' + trs.type
    var idKey = sender.address + ':' + trs.type
    library.oneoff.delete(nameKey)
    library.oneoff.delete(idKey)
    setImmediate(cb)
  }

  this.objectNormalize = function (trs) {
    var report = library.scheme.validate(trs.asset.uiaIssuer, {
      type: 'object',
      properties: {
        name: {
          type: 'string',
          minLength: 1,
          maxLength: 16
        },
        desc: {
          type: 'string',
          minLength: 1,
          maxLength: 4096
        }
      },
      required: ['name', 'desc']
    })

    if (!report) {
      throw Error('Can\'t parse transaction: ' + library.scheme.getLastError())
    }

    return trs
  }

  this.dbRead = function (raw) {
    if (!raw.issuers_name) {
      return null
    } else {
      var issuer = {
        transactionId: raw.t_id,
        name: raw.issuers_name,
        desc: raw.issuers_desc
      }

      return { uiaIssuer: issuer }
    }
  }

  this.dbSave = function (trs, cb) {
    // FIXME catch exceptions on upper layer
    var asset = trs.asset.uiaIssuer
    var values = {
      transactionId: trs.id,
      issuerId: trs.senderId,
      name: asset.name,
      desc: asset.desc
    }
    library.model.add('issuers', values, cb)
  }

  this.ready = function (trs, sender) {
    if (sender.multisignatures.length) {
      if (!trs.signatures) {
        return false
      }
      return trs.signatures.length >= sender.multimin - 1
    } else {
      return true
    }
  }
}

module.exports = new Issuer<|endoftext|>


<commit_before>(function () {
  var output = document.getElementById('output');
  // For future translation
  var messages = [
    "Cannot use browser download manager. Using alternative download manager.",
    'Direct MEGA installed, but no file to download.',
    'After the download starts you can close this window.',
    'Unknown error. Using alternative download manager.',
    'Try the original',
    'Report issue',
    'Click here to download if it not starts automatically',
    'Unknown error',
    "Downloading $1 ($2) - don't close this page",
    'Your download will start soon...',
    'No file to download.',
  ];

  var identifier = (location.hash.length > 2 ? location.hash : location.search || '').substr(1);
  var href = 'https://mega.nz/#' + identifier;

  if (!navigator.serviceWorker) {
    if (identifier.length < 4) {
      showMessage(messages[10], true);
      return;
    }
    showMessage(messages[0], true);
    loadFallback();
    return;
  }

  navigator.serviceWorker.register('sw.min.js', {scope: '.'})
  .then(wait(2000))
  .then(navigator.serviceWorker.ready)
  .then(function (instance){
    if (identifier.length < 4) {
      showMessage(messages[1], true);
      return;
    }

    if (parent !== self && // is in a iframe and is the iframe created below
       parent.location.origin === location.origin) {
      top.postMessage('', location.origin);
      return;
    }

    showMessage(messages[9], true);

    var downloadFrame = document.createElement('iframe');
    downloadFrame.src = '?' + identifier;
    downloadFrame.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;' +
      'margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0';

    var canCloseTimeout = setTimeout(function() {
      showMessage(messages[2], true);
    }, 8000);
    window.addEventListener('message', function(event) {
      if (event.origin !== location.origin) return;
      clearTimeout(canCloseTimeout);
      showMessage(messages[3], true);
      loadFallback();
    });

    document.body.appendChild(downloadFrame);
  }, function (error) {
    console.error(error);
    showMessage(messages[3], true);
    loadFallback();
  });

  function wait(time) {
    return new Promise(function(resolve) {
      setTimeout(resolve, time);
    });
  }

  function loadFallback() {
    var script = document.createElement('script');
    var file;
    var attributes;
    script.src = 'mega.js';
    script.onload = function() {
      file = mega.file(href);
      file.loadAttributes(afterLoadAttributes);
    };

    function afterLoadAttributes(err, data) {
      attributes = data;
      if (err) {
        showMessage(messages[7]);
        throw err;
      }
      showMessage(messages[8]
        .replace('$1', attributes.name)
        .replace('$2', humanizeSize(attributes.size)), true);

      handleProgress(file.download(afterDownload), attributes.size);
    }

    function handleProgress(stream, total) {
      var percentageBar = document.getElementsByClassName('downloading-progress-bar')[0];
      var percentageText = document.getElementsByClassName('percentage')[0];
      document.getElementsByClassName('downloader')[0].className += ' active';

      percentageText.textContent = '0%';

      stream.on('progress', function (data) {
        percentageText.textContent = Math.floor(data.bytesLoaded * 100 / total) + '%';
        percentageBar.style.width = (data.bytesLoaded * 100 / total).toFixed(2) + '%';
      });

      stream.on('end', function () {
        percentageText.textContent = percentageBar.style.width = '100%';
      });
    }

    function afterDownload(err, data) {
      if (err) {
        showMessage(messages[7]);
        throw err;
      }
      var anchor = document.createElement('a');
      anchor.textContent = messages[6];

      // data is Uint8Array
      anchor.href = URL.createObjectURL(new Blob([data.buffer || data], { type: 'application/octet-stream' }));
      anchor.download = attributes.name;
      showMessage('', true);
      output.appendChild(anchor);
      anchor.click();
    }

    document.head.appendChild(script)
  }

  function humanizeSize(a,b,c,d,e) { // http://stackoverflow.com/a/20463021
    return (b = Math, c = b.log, d = 1024, e = c(a)/c(d)|0, a/b.pow(d,e)).toFixed(2)+' '+(e?'KMGTPEZY'[--e]+'B':'bytes');
  }

  function showMessage(message, noError) {
    output.textContent = message;
    output.appendChild(document.createElement('br'));

    if (noError) return;

    var anchor = document.createElement('a');
    anchor.textContent = messages[4];
    anchor.href = href;
    output.appendChild(anchor);
    output.appendChild(document.createElement('br'));

    anchor = document.createElement('a');
    anchor.textContent = messages[5];
    anchor.href = 'https://github.com/qgustavor/direct-mega/issues/new';
    output.appendChild(anchor);
  }
}());<commit_msg>Use state instead of timeout<commit_after>(function () {
  var output = document.getElementById('output');
  // For future translation
  var messages = [
    "Cannot use browser download manager. Using alternative download manager.",
    'Direct MEGA installed, but no file to download.',
    'After the download starts you can close this window.',
    'Unknown error. Using alternative download manager.',
    'Try the original',
    'Report issue',
    'Click here to download if it not starts automatically',
    'Unknown error',
    "Downloading $1 ($2) - don't close this page",
    'Your download will start soon...',
    'No file to download.',
  ];

  var identifier = (location.hash.length > 2 ? location.hash : location.search || '').substr(1);
  var href = 'https://mega.nz/#' + identifier;

  if (!navigator.serviceWorker) {
    if (identifier.length < 4) {
      showMessage(messages[10], true);
      return;
    }
    showMessage(messages[0], true);
    loadFallback();
    return;
  }

  navigator.serviceWorker.register('sw.min.js', {scope: '.'})
  .then(navigator.serviceWorker.ready)
  .then(function (instance){
    if (identifier.length < 4) {
      showMessage(messages[1], true);
      return;
    }

    if (instance.active && instance.active.state === 'activating') {
      location.reload();
      return;
    }

    if (parent !== self && // is in a iframe and is the iframe created below
       parent.location.origin === location.origin) {
      top.postMessage('', location.origin);
      return;
    }

    showMessage(messages[9], true);

    var downloadFrame = document.createElement('iframe');
    downloadFrame.src = '?' + identifier;
    downloadFrame.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;' +
      'margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0';

    var canCloseTimeout = setTimeout(function() {
      showMessage(messages[2], true);
    }, 8000);
    window.addEventListener('message', function(event) {
      if (event.origin !== location.origin) return;
      clearTimeout(canCloseTimeout);
      showMessage(messages[3], true);
      loadFallback();
    });

    document.body.appendChild(downloadFrame);
  }, function (error) {
    console.error(error);
    showMessage(messages[3], true);
    loadFallback();
  });

  function loadFallback() {
    var script = document.createElement('script');
    var file;
    var attributes;
    script.src = 'mega.js';
    script.onload = function() {
      file = mega.file(href);
      file.loadAttributes(afterLoadAttributes);
    };

    function afterLoadAttributes(err, data) {
      attributes = data;
      if (err) {
        showMessage(messages[7]);
        throw err;
      }
      showMessage(messages[8]
        .replace('$1', attributes.name)
        .replace('$2', humanizeSize(attributes.size)), true);

      handleProgress(file.download(afterDownload), attributes.size);
    }

    function handleProgress(stream, total) {
      var percentageBar = document.getElementsByClassName('downloading-progress-bar')[0];
      var percentageText = document.getElementsByClassName('percentage')[0];
      document.getElementsByClassName('downloader')[0].className += ' active';

      percentageText.textContent = '0%';

      stream.on('progress', function (data) {
        percentageText.textContent = Math.floor(data.bytesLoaded * 100 / total) + '%';
        percentageBar.style.width = (data.bytesLoaded * 100 / total).toFixed(2) + '%';
      });

      stream.on('end', function () {
        percentageText.textContent = percentageBar.style.width = '100%';
      });
    }

    function afterDownload(err, data) {
      if (err) {
        showMessage(messages[7]);
        throw err;
      }
      var anchor = document.createElement('a');
      anchor.textContent = messages[6];

      // data is Uint8Array
      anchor.href = URL.createObjectURL(new Blob([data.buffer || data], { type: 'application/octet-stream' }));
      anchor.download = attributes.name;
      showMessage('', true);
      output.appendChild(anchor);
      anchor.click();
    }

    document.head.appendChild(script)
  }

  function humanizeSize(a,b,c,d,e) { // http://stackoverflow.com/a/20463021
    return (b = Math, c = b.log, d = 1024, e = c(a)/c(d)|0, a/b.pow(d,e)).toFixed(2)+' '+(e?'KMGTPEZY'[--e]+'B':'bytes');
  }

  function showMessage(message, noError) {
    output.textContent = message;
    output.appendChild(document.createElement('br'));

    if (noError) return;

    var anchor = document.createElement('a');
    anchor.textContent = messages[4];
    anchor.href = href;
    output.appendChild(anchor);
    output.appendChild(document.createElement('br'));

    anchor = document.createElement('a');
    anchor.textContent = messages[5];
    anchor.href = 'https://github.com/qgustavor/direct-mega/issues/new';
    output.appendChild(anchor);
  }
}());<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

CLASS({
  package: 'foam.apps.builder',
  name: 'UploadManager',

  requires: [
    'foam.apps.builder.PackageManager',
  ],
  imports: [
    'identityManager',
    'metricsDAO',
    'sourceManager',
    'xhrManager',
  ],

  constants: {
    UPLOAD_TO_CWS: 'https://www.googleapis.com/upload/chromewebstore/v1.1/items',
    HEADERS: [
      [
        /https?:[/][/]www.googleapis.com[/][^?#]*chromewebstore/,
        { 'x-goog-api-version': '2' },
      ],
    ],
  },

  properties: [
    {
      type: 'foam.apps.builder.ExportFlow',
      name: 'data',
    },
    {
      type: 'foam.apps.builder.PackageManager',
      name: 'packageManager',
      lazyFactory: function() {
        return this.PackageManager.create({}, this.Y);
      },
    },
  ],

  methods: [
    function exportApp(data) {
      this.data = data;
      aseq(
          apar(
              this.identityManager.withEmail.bind(this.identityManager),
              aseq(
                  this.sourceManager.aloadSources.bind(this.sourceManager, data.config),
                  this.packageManager.prepareSources.bind(this.packageManager))
              ),
          apar(
              this.identityManager.withOAuth.bind(this.identityManager),
              this.prepareUpload.bind(this)),
          this.sendUpload.bind(this))
      (this.finalizeUpload.bind(this));
    },
    function prepareUpload(ret, email, archive) {
      var indicators = argsToArray(arguments).slice(2);

      if ( ! email ) {
        this.data.message = 'Oop! Looks like something went wrong. Did you grant the application the permission it needs to upload your app?';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (this.data.config.model_.id || this.data.config.name_) +
              ' - Access denied',
        }));
        return;
      }

      if ( ! archive ) {
        this.data.message = 'Oop! Looks like something went wrong packaging your app. Make sure all required fields are filled out.';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (this.data.config.model_.id || this.data.config.name_) +
              ' - Packaging error',
        }));
        return;
      }

      var data = new FormData();
      data.append('uploadType', 'media');
      data.append('fileupload', archive.toBlob());

      ret(data);
    },
    function sendUpload(ret, oauth, data) {
      var config = this.data.config;

      if ( ! oauth ) {
        this.data.message = 'Oop! Looks like something went wrong. Did you grant the application the permission it needs to upload your app?';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (config.model_.id || config.name_) +
              ' - Access denied',
        }));
        return;
      }

      this.data.state = 'UPLOADING';
      var id = config.chromeId;
      this.xhrManager.asend(
          ret,
          this.UPLOAD_TO_CWS + (id ? '/' + id : ''),
          data,
          id ? 'PUT' : 'POST');
    },
    function finalizeUpload(data, xhr, status) {
      if ( ( ! status ) || data.uploadState !== 'SUCCESS' ) {
        this.data.message = 'Oop! Something went wrong during the file upload. Please try again later.';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (this.data.config.model_.id || this.data.config.name_) +
              ' - Status ' + (status ? data.uploadState : xhr.status),
        }));
        return;
      }

      this.data.config.chromeId = data.id;
      // TODO(markdittmer): This is currently not going through. Perhaps we
      // need to wait for the updateDetailView to receive an event/update?
      this.data.updateView && this.data.updateView.save && this.data.updateView.save();
      this.data.state = 'COMPLETED';
    },
  ],
});
<commit_msg>App Builder UploadManager: Get rid of email (use of withEmail on identityManager) and use default email associated with OAuth token (code commented and commented-out)<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

CLASS({
  package: 'foam.apps.builder',
  name: 'UploadManager',

  requires: [
    'foam.apps.builder.PackageManager',
  ],
  imports: [
    'identityManager',
    'metricsDAO',
    'sourceManager',
    'xhrManager',
  ],

  constants: {
    UPLOAD_TO_CWS: 'https://www.googleapis.com/upload/chromewebstore/v1.1/items',
    HEADERS: [
      [
        /https?:[/][/]www.googleapis.com[/][^?#]*chromewebstore/,
        { 'x-goog-api-version': '2' },
      ],
    ],
  },

  properties: [
    {
      type: 'foam.apps.builder.ExportFlow',
      name: 'data',
    },
    {
      type: 'foam.apps.builder.PackageManager',
      name: 'packageManager',
      lazyFactory: function() {
        return this.PackageManager.create({}, this.Y);
      },
    },
  ],

  methods: [
    function exportApp(data) {
      this.data = data;
      aseq(
          // NOTE:
          // We currently currently use the default email from OAuth, so
          // withEmail is not necessary. Uncomment code below to delegate email
          // lookup to identity manager.
          // apar(
          //     this.identityManager.withEmail.bind(this.identityManager),
          //     aseq(
                  this.sourceManager.aloadSources.bind(this.sourceManager, data.config),
                  this.packageManager.prepareSources.bind(this.packageManager)
          //     )
          // )
          ,
          apar(
              this.identityManager.withOAuth.bind(this.identityManager),
              this.prepareUpload.bind(this)),
          this.sendUpload.bind(this))
      (this.finalizeUpload.bind(this));
    },
    function prepareUpload(ret,
                           // NOTE:
                           // We currently currently use the default email from
                           // OAuth, so withEmail is not necessary. Uncomment
                           // email parameter this changes.
                           // email,
                           archive) {
      var indicators = argsToArray(arguments).slice(2);

      // NOTE:
      // We currently currently use the default email from OAuth, so withEmail
      // is not necessary. Uncomment code below if this changes.
      // if ( ! email ) {
      //   this.data.message = 'Oop! Looks like something went wrong. Did you grant the application the permission it needs to upload your app?';
      //   this.data.state = 'FAILED';
      //   this.metricsDAO.put(this.Error.create({
      //     name: 'Action:upload:fail - ' +
      //         (this.data.config.model_.id || this.data.config.name_) +
      //         ' - Access denied',
      //   }));
      //   return;
      // }

      if ( ! archive ) {
        this.data.message = 'Oop! Looks like something went wrong packaging your app. Make sure all required fields are filled out.';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (this.data.config.model_.id || this.data.config.name_) +
              ' - Packaging error',
        }));
        return;
      }

      var data = new FormData();
      data.append('uploadType', 'media');

      // We currently currently use the default email from OAuth, so withEmail
      // is not necessary. Uncomment code below to use injected email instead.
      // if ( ! this.data.config.chromeId ) data.append('publisherEmail', email);

      data.append('fileupload', archive.toBlob());

      ret(data);
    },
    function sendUpload(ret, oauth, data) {
      var config = this.data.config;

      if ( ! oauth ) {
        this.data.message = 'Oop! Looks like something went wrong. Did you grant the application the permission it needs to upload your app?';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (config.model_.id || config.name_) +
              ' - Access denied',
        }));
        return;
      }

      this.data.state = 'UPLOADING';
      var id = config.chromeId;
      this.xhrManager.asend(
          ret,
          this.UPLOAD_TO_CWS + (id ? '/' + id : ''),
          data,
          id ? 'PUT' : 'POST');
    },
    function finalizeUpload(data, xhr, status) {
      if ( ( ! status ) || data.uploadState !== 'SUCCESS' ) {
        this.data.message = 'Oop! Something went wrong during the file upload. Please try again later.';
        this.data.state = 'FAILED';
        this.metricsDAO.put(this.Error.create({
          name: 'Action:upload:fail - ' +
              (this.data.config.model_.id || this.data.config.name_) +
              ' - Status ' + (status ? data.uploadState : xhr.status),
        }));
        return;
      }

      this.data.config.chromeId = data.id;
      // TODO(markdittmer): This is currently not going through. Perhaps we
      // need to wait for the updateDetailView to receive an event/update?
      this.data.updateView && this.data.updateView.save && this.data.updateView.save();
      this.data.state = 'COMPLETED';
    },
  ],
});
<|endoftext|>


<commit_before>
var fs = require("fs");
var path = require("path");

exports.open = open;
exports.ZipFile = ZipFile;
exports.Entry = Entry;
exports.dateToDosDateTime = dateToDosDateTime;

function open(path, callback) {
  if (callback == null) callback = defaultCallback;
  fs.open(path, "w", function(err, fd) {
    if (err) return callback(err);
    var outputStream = fs.createWriteStream(path, {fd: fd});
    callback(null, new ZipFile(outputStream, {autoClose: true}));
  });
}

function ZipFile(outputStream, options) {
  this.outputStream = outputStream;
  options = options || {autoClose: false};
  this.autoClose = !!options.autoClose;
  this.entries = [];
  this.centralDirectoryBuffers = [];
  this.outputStreamCursor = 0;
}
ZipFile.prototype.addFile = function(realPath, options) {
  var self = this;
  var metadataPath = getMetadataPath(options, realPath);

  var entry = new Entry(metadataPath, options);
  self.entries.push(entry);
  fs.open(realPath, "r", function(err, fd) {
    if (err) return self.emit("error", err);
    fs.fstat(fd, function(err, stats) {
      if (err) return self.emit("error", err);
      if (!stats.isFile()) return self.emit("error", new Error("not a file: " + realPath));
      entry.setLastModDate(stats.mtime);
      entry.setPumper(function(sink) {
        var readStream = fs.createReadStream(null, {fd: fd});
        readStream.on("error", function(err) {
          self.emit("error", err);
        });
        readStream.on("end", function() {
          entry.dataWritten = true;
          pumpEntries(self);
        });
        readStream.pipe(self.outputStream, {end: false});
        readStream.on("data", function(data) {
          self.outputStreamCursor += data.length;
        });
      });
      pumpEntries(self);
    });
  });
};
ZipFile.prototype.addBuffer = function(buffer, options) {
  var self = this;
  var metadataPath = getMetadataPath(options);
  var entry = new Entry(metadataPath, options);
  if (options.mtime == null) throw new Error("missing mtime");
  entry.setLastModDate(options.mtime);
  self.entries.push(entry);
  entry.setPumper(function(sink) {
    sink.write(buffer);
    sink.end();
    pumpEntries(self);
  });
  pumpEntries(self);
};

function pumpEntries(self) {
}

function getMetadataPath(options, realPath) {
  var metadataPath = options.metadataPath;
  if (metadataPath == null) {
    if (realPath == null) throw new Error("missing metadataPath");
    if (options.archiveRootPath == null) throw new Error("one of metadataPath or archiveRootPath is required");
    metadataPath = path.relative(options.archiveRootPath, realPath);
  }
  if (metadataPath.indexOf("\\") !== -1) throw new Error("invalid characters in path: " + metadataPath);
  if (/^[a-zA-Z]:/.test(metadataPath) || /^\//.test(metadataPath)) throw new Error("absolute path: " + metadataPath);
  if (metadataPath.split("/").indexOf("..") !== -1) throw new Error("invalid relative path: " + metadataPath);
  return metadataPath;
}

function Entry(metadataPath, options) {
  this.fileName = metadataPath;
  if (options.extraFields != null) this.setExtraFields(options.extraFields);
}
Entry.prototype.setExtraFields = function(extraFields) {
  var extraFieldBuffers = [];
  extraFields.forEach(function(extraField) {
    var id = extraField.id;
    var data = extraField.data;
    var headerBuffer = new Buffer(4);
    headerBuffer.writeUInt16LE(id, 0);
    headerBuffer.writeUInt16LE(data.length, 2);
    extraFieldBuffers.push(headerBuffer);
    extraFieldBuffers.push(data);
  });
  this.extraFields = Buffer.concat(extraFieldBuffers);
};
Entry.prototype.setLastModDate = function(date) {
  var dosDateTime = dateToDosDateTime(date);
  this.lastModFileTime = dosDateTime.time;
  this.lastModFileDate = dosDateTime.date;
};
Entry.prototype.setPumper = function(pumper) {
  this.pumper = pumper;
};

function dateToDosDateTime(jsDate) {
  var date = 0;
  date |= jsDate.getDay() & 0x1f; // 1-31
  date |= ((jsDate.getMonth() + 1) & 0xf) << 5; // 0-11, 1-12
  date |= ((jsDate.getYear() - 1980) & 0x7f) << 9; // 0-128, 1980-2108

  var time = 0;
  time |= Math.floor(jsDate.getSecond() / 2); // 0-59, 0-29 (lose odd numbers)
  time |= (jsDate.getMinute() & 0x3f) << 5; // 0-59
  time |= (jsDate.getHour() & 0x1f) << 11; // 0-23

  return {date: date, time: time};
}

function defaultCallback(err) {
  if (err) throw err;
}
<commit_msg>trash 3<commit_after>
var fs = require("fs");
var path = require("path");

exports.open = open;
exports.ZipFile = ZipFile;
exports.dateToDosDateTime = dateToDosDateTime;

function open(path, callback) {
  if (callback == null) callback = defaultCallback;
  fs.open(path, "w", function(err, fd) {
    if (err) return callback(err);
    var outputStream = fs.createWriteStream(path, {fd: fd});
    callback(null, new ZipFile(outputStream, {autoClose: true}));
  });
}

function ZipFile(outputStream, options) {
  this.outputStream = outputStream;
  options = options || {autoClose: false};
  this.autoClose = !!options.autoClose;
  this.entries = [];
  this.centralDirectoryBuffers = [];
  this.outputStreamCursor = 0;
  this.ended = false;
}

ZipFile.prototype.addFile = function(realPath, options) {
  var self = this;
  var metadataPath = getMetadataPath(options, realPath);

  var entry = new Entry(metadataPath, options);
  self.entries.push(entry);
  fs.open(realPath, "r", function(err, fd) {
    if (err) return self.emit("error", err);
    fs.fstat(fd, function(err, stats) {
      if (err) return self.emit("error", err);
      if (!stats.isFile()) return self.emit("error", new Error("not a file: " + realPath));
      entry.setLastModDate(stats.mtime);
      entry.setFileDataPumpFunction(function() {
        var readStream = fs.createReadStream(null, {fd: fd});
        readStream.on("error", function(err) {
          self.emit("error", err);
        });
        readStream.on("end", function() {
          entry.state = Entry.FILE_DATA_DONE;
          pumpEntries(self);
        });
        readStream.pipe(self.outputStream, {end: false});
        readStream.on("data", function(data) {
          self.outputStreamCursor += data.length;
        });
      });
      pumpEntries(self);
    });
  });
};

ZipFile.prototype.addBuffer = function(buffer, options) {
  var self = this;
  var metadataPath = getMetadataPath(options);
  var entry = new Entry(metadataPath, options);
  if (options.mtime == null) throw new Error("missing mtime");
  entry.setLastModDate(options.mtime);
  self.entries.push(entry);
  entry.setFileDataPumpFunction(function() {
    writeToOutputStream(self, buffer);
    entry.state = Entry.FILE_DATA_DONE;
    pumpEntries(self);
  });
  pumpEntries(self);
};

ZipFile.prototype.end = function() {
  this.ended = true;
  pumpEntries(this);
};

function writeToOutputStream(self, buffer) {
  self.outputStream.write(buffer);
  self.outputStreamCursor += buffer.length;
}

function pumpEntries(self) {
  var entry = getFirstNotDoneEntry();
  function getFirstNotDoneEntry() {
    for (var i = 0; i < self.entries.length; i++) {
      var entry = self.entries[i];
      if (entry.state < Entry.FILE_DATA_DONE) return entry;
    }
    return null;
  }
  if (entry != null) {
    // this entry is not done yet
    if (entry.state < Entry.READY_TO_PUMP_FILE_DATA) return; // input file not open yet
    // start with local file header
    entry.relativeOffsetOfLocalHeader = self.outputStreamCursor;
    var localFileHeader = entry.getLocalFileHeader();
    writeToOutputStream(self, localFileHeader);
    entry.doFileDataPump();
  } else {
    // all cought up on writing entries
    if (self.ended) {
      // head for the exit
      self.entries.forEach(function(entry) {
        var centralDirectoryRecord = entry.getCentralDirectoryRecord();
        writeToOutputStream(self, centralDirectoryRecord);
      });
    }
  }
}

function getMetadataPath(options, realPath) {
  var metadataPath = options.metadataPath;
  if (metadataPath == null) {
    if (realPath == null) throw new Error("missing metadataPath");
    if (options.archiveRootPath == null) throw new Error("one of metadataPath or archiveRootPath is required");
    metadataPath = path.relative(options.archiveRootPath, realPath);
  }
  if (metadataPath.indexOf("\\") !== -1) throw new Error("invalid characters in path: " + metadataPath);
  if (/^[a-zA-Z]:/.test(metadataPath) || /^\//.test(metadataPath)) throw new Error("absolute path: " + metadataPath);
  if (metadataPath.split("/").indexOf("..") !== -1) throw new Error("invalid relative path: " + metadataPath);
  return metadataPath;
}

// this class is not part of the public API
function Entry(metadataPath, options) {
  this.utf8FileName = new Buffer(metadataPath);
  if (this.utf8FileName.length > 0xffff) throw new Error("utf8 file name too long. " + utf8FileName.length + " > " + 0xffff);
  this.state = Entry.WAITING_FOR_METADATA;
  if (options.extraFields != null) this.setExtraFields(options.extraFields);
}
Entry.WAITING_FOR_METADATA = 0;
Entry.READY_TO_PUMP_FILE_DATA = 1;
Entry.FILE_DATA_DONE = 2;
Entry.prototype.setExtraFields = function(extraFields) {
  var extraFieldBuffers = [];
  extraFields.forEach(function(extraField) {
    var id = extraField.id;
    var data = extraField.data;
    var headerBuffer = new Buffer(4);
    headerBuffer.writeUInt16LE(id, 0);
    headerBuffer.writeUInt16LE(data.length, 2);
    extraFieldBuffers.push(headerBuffer);
    extraFieldBuffers.push(data);
  });
  this.extraFields = Buffer.concat(extraFieldBuffers);
  if (this.extraFields.length > 0xffff) throw new Error("extra fields too long. " + extraFields.length + " > " + 0xffff);
};
Entry.prototype.setLastModDate = function(date) {
  var dosDateTime = dateToDosDateTime(date);
  this.lastModFileTime = dosDateTime.time;
  this.lastModFileDate = dosDateTime.date;
};
Entry.prototype.setFileDataPumpFunction = function(doFileDataPump) {
  this.doFileDataPump = doFileDataPump;
  this.state = Entry.READY_TO_PUMP_FILE_DATA;
};
// this version enables utf8 filename compression
var VERSION_NEEDED_TO_EXTRACT = 0x0014;
var CENTRAL_DIRECOTRY_GENERAL_PURPOSE_BIT_FLAG =
  (1 << 11) | // utf8 filename
  0;
var LOCAL_FILE_HEADER_GENERAL_PURPOSE_BIT_FLAG =
  (1 << 3) | // crc32 and file sizes are unknown before we encode the file
  (1 << 11) | // utf8 filename
  0;
Entry.prototype.getLocalFileHeader = function() {
  var fixedSizeStuff = new Buffer(30);
  // local file header signature     4 bytes  (0x04034b50)
  fixedSizeStuff.writeUInt32LE(0x04034b50, 0);
  // version needed to extract       2 bytes
  // (mimic linux info-zip)
  fixedSizeStuff.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT, 4);
  // general purpose bit flag        2 bytes
  fixedSizeStuff.writeUInt16LE(LOCAL_FILE_HEADER_GENERAL_PURPOSE_BIT_FLAG, 6);
  // compression method              2 bytes
  fixedSizeStuff.writeUInt16LE(0, 8);
  // last mod file time              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 10);
  // last mod file date              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 12);
  // crc-32                          4 bytes
  fixedSizeStuff.writeUInt32LE(0, 14);
  // compressed size                 4 bytes
  fixedSizeStuff.writeUInt32LE(0, 18);
  // uncompressed size               4 bytes
  fixedSizeStuff.writeUInt32LE(0, 22);
  // file name length                2 bytes
  fixedSizeStuff.writeUInt16LE(this.utf8FileName.length, 26);
  // extra field length              2 bytes
  fixedSizeStuff.writeUInt16LE(this.extraFields.length, 28);
  // file name (variable size)
  // extra field (variable size)
  // should we concat here, or write each individually? They're probably pretty small.
  return Buffers.concat([fixedSizeStuff, this.utf8FileName, this.extraFields]);
};
Entry.prototype.getCentralDirectoryRecord = function() {
  // central file header signature   4 bytes  (0x02014b50)
  fixedSizeStuff.writeUInt32LE(0x02014b50, 0);
  // version made by                 2 bytes
  // (impersonate linux info-zip)
  fixedSizeStuff.writeUInt16LE(0x031e, 4);
  // version needed to extract       2 bytes
  // (mimic linux info-zip)
  fixedSizeStuff.writeUInt16LE(VERSION_NEEDED_TO_EXTRACT, 6);
  // general purpose bit flag        2 bytes
  fixedSizeStuff.writeUInt16LE(CENTRAL_DIRECOTRY_GENERAL_PURPOSE_BIT_FLAG, 8);
  // compression method              2 bytes
  fixedSizeStuff.writeUInt16LE(0, 10);
  // last mod file time              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileTime, 12);
  // last mod file date              2 bytes
  fixedSizeStuff.writeUInt16LE(this.lastModFileDate, 14);
  // crc-32                          4 bytes
  fixedSizeStuff.writeUInt16LE(this.crc32, 16);
  // compressed size                 4 bytes
  fixedSizeStuff.writeUInt16LE(this.compressedSize, 20);
  // uncompressed size               4 bytes
  fixedSizeStuff.writeUInt16LE(this.uncompressedSize, 24);
  // file name length                2 bytes
  // extra field length              2 bytes
  // file comment length             2 bytes
  // disk number start               2 bytes
  // internal file attributes        2 bytes
  // external file attributes        4 bytes
  // relative offset of local header 4 bytes
  // file name (variable size)
  // extra field (variable size)
  // file comment (variable size)
};

function dateToDosDateTime(jsDate) {
  var date = 0;
  date |= jsDate.getDay() & 0x1f; // 1-31
  date |= ((jsDate.getMonth() + 1) & 0xf) << 5; // 0-11, 1-12
  date |= ((jsDate.getYear() - 1980) & 0x7f) << 9; // 0-128, 1980-2108

  var time = 0;
  time |= Math.floor(jsDate.getSecond() / 2); // 0-59, 0-29 (lose odd numbers)
  time |= (jsDate.getMinute() & 0x3f) << 5; // 0-59
  time |= (jsDate.getHour() & 0x1f) << 11; // 0-23

  return {date: date, time: time};
}

function defaultCallback(err) {
  if (err) throw err;
}
<|endoftext|>