<commit_before>module.exports = function(imports) {

  var window = imports.window
  var console = imports.console;

  /* globals  */
  var EventEmitter = require("events").EventEmitter;
  var onlykey_api = new EventEmitter();

  var nacl = require("./nacl.min.js");

  onlykey_api._status;
  onlykey_api.poll_delay;
  onlykey_api.poll_type;
  onlykey_api.custom_keyid;

  var {
    wait,
    sha256,
    hexStrToDec,
    bytes2string,
    // noop,
    getstringlen,
    // mkchallenge,
    // bytes2b64,
    getOS,
    ctap_error_codes,
    getAllUrlParams,
    aesgcm_decrypt,
    // aesgcm_encrypt
  } = require("./onlykey.extra.js")(imports);
  onlykey_api.getAllUrlParams = getAllUrlParams; //<-- todo: move to pages plugin

  async function digestBuff(buff) {
      const msgUint8 = buff;
      const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgUint8);
      const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
      const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
      return hashHex;
  }

  onlykey_api.browser = "Chrome";
  onlykey_api.os = getOS();

  var appKey;
  var okPub;

  // const OKDECRYPT = 240;
  // const OKSIGN = 237;
  const OKCONNECT = 228;
  // const OKGETPUBKEY = 236;
  // const OKGETRESPONSE = 242;
  // const OKPING = 243;

  /**
   * Initializes OnlyKey
   * Performs NACL key exchange to encrypt all future packets
   * Receives hardware generated entropy for future use
   */
  onlykey_api.init = false;
  onlykey_api.initok = async function(callback) {
    return new Promise(async function(resolve) {
      if (onlykey_api.init) {
        if (callback && typeof callback == "function")
          callback();
        resolve();
      }

      //Initialize OnlyKey
      if (window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1)
        onlykey_api.browser = "Firefox";
      //Set time on OnlyKey, get firmware version, get ecc public
      OK_CONNECT(async function(aerr, status) {
        // console.log(err);
        if (status) {
          console.log("OKCONNECT STATUS", status);
        }

        if (typeof(onlykey_api.sharedsec) === "undefined") {
          if (onlykey_api.browser == 'Firefox') headermsg("
OnlyKey not connected! Close this tab and open a new one to try again.
");
          else headermsg("
OnlyKey not connected! Refresh this page to try again.
");
          if (callback && typeof callback == "function")
            callback(true);
          resolve();
        }
        else {
          onlykey_api.init = true;
          //Initialize App
          if (callback && typeof callback == "function")
            callback();
          resolve();

        }
      });
    });

  };
  onlykey_api.check = function(cb) {
    onlykey_api.init = false;
    return onlykey_api.connect(cb);
  };
  onlykey_api.connect = onlykey_api.initok;

  async function OK_CONNECT(callback) {
    return new Promise(async function(resolve, reject) {

      function cb(err, data) {
        if (typeof callback === 'function') callback(err, data);
        resolve({ data: data, error: err });
      }

      var delay = 1;
      if (onlykey_api.OKversion == 'Original') {
        delay = delay * 4;
      }

      console.info("Connecting to OnlyKey");
      var cmd;
      var encryptedkeyHandle;
      var message;

      imports.app.emit("ok-connecting");
      cmd = OKCONNECT;
      message = [255, 255, 255, 255, OKCONNECT]; //Add header and message type
      var currentEpochTime = Math.round(new Date().getTime() / 1000.0).toString(16);
      var timePart = currentEpochTime.match(/.{2}/g).map(hexStrToDec);
      Array.prototype.push.apply(message, timePart);
      appKey = nacl.box.keyPair();
      // console.info(appKey);
      // console.info(appKey.publicKey);
      // console.info(appKey.secretKey);
      // console.info("Application ECDH Public Key: ", appKey.publicKey);
      Array.prototype.push.apply(message, appKey.publicKey);
      var env = [onlykey_api.browser.charCodeAt(0), onlykey_api.os.charCodeAt(0)];
      Array.prototype.push.apply(message, env);
      encryptedkeyHandle = Uint8Array.from(message); // Not encrypted as this is the initial key exchange
      await wait(delay * 1000);
      var enc_resp = 1;
      var ctaphid_response = await ctaphid_via_webauthn(cmd, 2, null, null, encryptedkeyHandle, 6000, function(maybe_a_err, data) {
         console.info("ctaphid_response resp", maybe_a_err, data);

      });

      imports.app.emit("ok-waiting");

      var response;

      if (ctaphid_response.data && !ctaphid_response.error)
        response = ctaphid_response.data;

      if (!response) {
        if (onlykey_api.browser == 'Firefox') headermsg("
OnlyKey not connected! Close this tab and open a new one to try again.
");
        else headermsg("
OnlyKey not connected! Refresh this page to try again.
");
        imports.app.emit("ok-disconnected");
      }
      else {
        switch (ctaphid_response.status) {
          case "CTAP2_ERR_EXTENSION_NOT_SUPPORTED":
            break;
          case "CTAP1_SUCCESS":
            var BREAKING_BETA_8C = !!(bytes2string(response.slice(8, 20)) == "v0.2-beta.8c");

            if(!BREAKING_BETA_8C){
              okPub = response.slice(0, 32);

              // Decrypt with transit_key
              var transit_key = nacl.box.before(Uint8Array.from(okPub), appKey.secretKey);
              console.info("Onlykey transit public", okPub);
              console.info("App transit public", appKey.publicKey);
              console.info("Transit shared secret", transit_key);
              transit_key = await digestBuff(Uint8Array.from(transit_key)); //AES256 key sha256 hash of shared secret
              console.info("App AES Key", transit_key);
              var encrypted  = response.slice(32, response.length);
              onlykey_api.FWversion = bytes2string(response.slice(32+8, 32+20));
              response = await aesgcm_decrypt(encrypted, transit_key);
              onlykey_api.OKversion = response[32+19] == 99 ? 'Color' : 'Go';
              onlykey_api.sharedsec = nacl.box.before(Uint8Array.from(okPub), appKey.secretKey);
              console.info("Version:",[onlykey_api.OKversion, onlykey_api.FWversion]);
              imports.app.emit("ok-connected");
              cb(null);
            }else{
              okPub = response.slice(21, 53);
              console.info("OnlyKey Public Key: ", okPub);
              onlykey_api.sharedsec = nacl.box.before(Uint8Array.from(okPub), appKey.secretKey);
              console.info("NACL shared secret: ", onlykey_api.sharedsec);
              onlykey_api.OKversion = response[19] == 99 ? 'Color' : 'Original';
              onlykey_api.FWversion = bytes2string(response.slice(8, 20));
              console.info("Version:",[onlykey_api.OKversion, onlykey_api.FWversion]);
              imports.app.emit("ok-connected");
              cb(null);
            }
            headermsg("
OnlyKey " + onlykey_api.FWversion + " Secure Connection Established
\n");
            break;
          default:
            imports.app.emit("ok-disconnected");

        }
        cb(null, ctaphid_response.status);

      }

    });
  }

  function encode_ctaphid_request_as_keyhandle(cmd, opt1, opt2, opt3, data) {
    // console.log('REQUEST CMD', cmd);
    // console.log('REQUEST OPT1', opt1);
    // console.log('REQUEST OPT2', opt2);
    // console.log('REQUEST OPT3', opt3);
    // console.log('REQUEST DATA', data);
    //var addr = 0;

    data = data || new Uint8Array();

    const offset = 10;

    if (offset + data.length > 255) {
      throw new Error("Max size exceeded");
    }

    // `is_extension_request` expects at least 16 bytes of data
    const data_pad = data.length < 16 ? 16 - data.length : 0;
    var array = new Uint8Array(offset + data.length + data_pad);

    array[0] = cmd & 0xff;

    array[1] = opt1 & 0xff;
    array[2] = opt2 & 0xff;
    array[3] = opt3 & 0xff;
    array[4] = 0x8C; // 140
    array[5] = 0x27; //  39
    array[6] = 0x90; // 144
    array[7] = 0xf6; // 246

    array[8] = 0;
    array[9] = data.length & 0xff;

    array.set(data, offset);

    // console.log('FORMATTED REQUEST:', array);
    return array;
  }

  function decode_ctaphid_response_from_signature(response) {
    // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#using-the-ctap2-authenticatorgetassertion-command-with-ctap1-u2f-authenticators<Paste>
    //
    // compared to `parse_device_response`, the data is encoded a little differently here
    //
    // attestation.response.authenticatorData
    //
    // first 32 bytes: SHA-256 hash of the rp.id
    // 1 byte: zeroth bit = user presence set in U2F response (always 1)
    // last 4 bytes: signature counter (32 bit big-endian)
    //
    // attestation.response.signature
    // signature data (bytes 5-end of U2F response

    // console.log('UNFORMATTED RESPONSE:', response);

    var signature_count;
    if (onlykey_api.os == "Node") {
      signature_count = (
        new DataView(toArrayBuffer(Buffer.from(response.authenticatorData.slice(33, 37))))
      ).getUint32(0, false); // get count as 32 bit BE integer
    }
    else {
      signature_count = (
        new DataView(
          response.authenticatorData.slice(33, 37)
        )
      ).getUint32(0, false); // get count as 32 bit BE integer
    }

    var signature = new Uint8Array(response.signature);
    var error_code = signature[0];

    var data = null;
    var error = null;

    if (signature.length > 1)
      data = signature.slice(1, signature.length);

    switch (ctap_error_codes[error_code]) {
      case "CTAP1_SUCCESS":
        if (bytes2string(data.slice(0, 9)) == 'UNLOCKEDv') {
          // Reset shared secret and start over
          onlykey_api.unlocked = true;
        }
        else if (signature.length < 73 && bytes2string(data.slice(0, 6)) == 'Error ') {
          // Something went wrong, read the ascii response and display to user
          var msgtext = data.slice(0, getstringlen(data));
          error = bytes2string(msgtext);
        }
      default:
        console.warn("ctap_error_code", ctap_error_codes[error_code]);
        if (ctap_error_codes[error_code] == 'CTAP2_ERR_EXTENSION_NOT_SUPPORTED') {
           error = ctap_error_codes[error_code];
        }
        break;
    }

    return {
      count: signature_count,
      status: ctap_error_codes[error_code],
      data: data,
      error: error,
      signature: signature,
    };
  }

  function ctaphid_via_webauthn(cmd, opt1, opt2, opt3, data, timeout, cb) {
    var request = {
        cmd:cmd,
        opt1:opt1,
        opt2:opt2,
        opt3:opt3,
        input_data:data,
        timeout:timeout
      }

    //#define DERIVE_PUBLIC_KEY 1
    //#define DERIVE_SHARED_SECRET 2
    //#define NO_ENCRYPT_RESP 0
    //#define ENCRYPT_RESP 1
    var keyhandle = encode_ctaphid_request_as_keyhandle(cmd, opt1, opt2, opt3, data);
    var challenge = window.crypto.getRandomValues(new Uint8Array(32));

    var id = window.location.hostname;

    request.request_options = {
      challenge: challenge,
      allowCredentials: [{
        transports: ["usb"],
        id: keyhandle,
        type: 'public-key',
      }],
      timeout: timeout,
      //rpId: 'apps.crp.to',
      //rpId: id,
      userVerification: 'discouraged',
      //userPresence: 'false',
      //mediation: 'silent',
      //extensions: {
        // appid: 'https://apps.crp.to',
        //appid: 'https://' + id
      //},
    };

    return new Promise(async function(resolve) {
      // return

      console.log({ctaphid_request:request});
      var results = false;
      // console.log("REQUEST:", request_options);
      window.navigator.credentials.get({
        publicKey: request.request_options
      }).catch(error => {
        console.warn("ERROR CALLING:", cmd, opt1, opt2, opt3, data);
        console.warn("THE ERROR:", error);
        console.warn("NAME:", error.name);
        console.warn("MESSAGE:", error.message);
        var response = { error: "Error " + error.name + " " + error.message };
        if (error.name == 'NS_ERROR_ABORT' || error.name == 'AbortError' || error.name == 'InvalidStateError') {
          // _$status('done_challenge');
          response.error2 = response.error;
          response.error = "Error aborted or bad hw-key-state";
          // return resolve(-1); // 1 = set error: aborted or bad hw-key-state
        }

        if (error.name == 'NotAllowedError' && onlykey_api.os == 'Windows') {
          response.error2 = response.error;
          response.error = "Error Win 10 1903 issue maybe?";
        }

        results = response;

      }).then(assertion => {
        var response;
        if (!assertion && results) {
          response = results;
        }
        else {
          // console.log("GOT ASSERTION", assertion);
          // console.log("RESPONSE", assertion.response);
          response = decode_ctaphid_response_from_signature(assertion.response);
          response.request = request;
          // console.log("RESPONSE:", response);
        }
        console.log({ctaphid_response:response});

        if (cb) cb(response.error, response);
        resolve(response);
      });

    });

  }

  onlykey_api.encode_ctaphid_request_as_keyhandle = encode_ctaphid_request_as_keyhandle;
  onlykey_api.decode_ctaphid_response_from_signature = decode_ctaphid_response_from_signature;
  onlykey_api.ctaphid_via_webauthn = ctaphid_via_webauthn;

  function toArrayBuffer(buf) {
    var ab = new ArrayBuffer(buf.length);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buf.length; ++i) {
      view[i] = buf[i];
    }
    return ab;
  }

  function id(s) { return document.getElementById(s); }

  function headermsg(s) {
    //if(imports.app)
    //  imports.app.emit("ok-message",s);
    //else
      id('header_messages').innerHTML += "
" + s;

  }

  return onlykey_api;
};
<commit_msg>Update onlykey-api.js<commit_after>module.exports = function(imports) {

  var window = imports.window
  var console = imports.console;

  /* globals  */
  var EventEmitter = require("events").EventEmitter;
  var onlykey_api = new EventEmitter();

  var nacl = require("./nacl.min.js");

  onlykey_api._status;
  onlykey_api.poll_delay;
  onlykey_api.poll_type;
  onlykey_api.custom_keyid;

  var {
    wait,
    sha256,
    hexStrToDec,
    bytes2string,
    // noop,
    getstringlen,
    // mkchallenge,
    // bytes2b64,
    getOS,
    ctap_error_codes,
    getAllUrlParams,
    aesgcm_decrypt,
    // aesgcm_encrypt
  } = require("./onlykey.extra.js")(imports);
  onlykey_api.getAllUrlParams = getAllUrlParams; //<-- todo: move to pages plugin

  async function digestBuff(buff) {
      const msgUint8 = buff;
      const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgUint8);
      const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
      const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
      return hashHex;
  }

  onlykey_api.browser = "Chrome";
  onlykey_api.os = getOS();

  var appKey;
  var okPub;

  // const OKDECRYPT = 240;
  // const OKSIGN = 237;
  const OKCONNECT = 228;
  // const OKGETPUBKEY = 236;
  // const OKGETRESPONSE = 242;
  // const OKPING = 243;

  /**
   * Initializes OnlyKey
   * Performs NACL key exchange to encrypt all future packets
   * Receives hardware generated entropy for future use
   */
  onlykey_api.init = false;
  onlykey_api.initok = async function(callback) {
    return new Promise(async function(resolve) {
      if (onlykey_api.init) {
        if (callback && typeof callback == "function")
          callback();
        resolve();
      }

      //Initialize OnlyKey
      if (window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1)
        onlykey_api.browser = "Firefox";
      //Set time on OnlyKey, get firmware version, get ecc public
      OK_CONNECT(async function(aerr, status) {
        // console.log(err);
        if (status) {
          console.log("OKCONNECT STATUS", status);
        }

        if (typeof(onlykey_api.sharedsec) === "undefined") {
          if (onlykey_api.browser == 'Firefox') headermsg("
OnlyKey not connected! Close this tab and open a new one to try again.
");
          else headermsg("
OnlyKey not connected! Refresh this page to try again.
");
          if (callback && typeof callback == "function")
            callback(true);
          resolve();
        }
        else {
          onlykey_api.init = true;
          //Initialize App
          if (callback && typeof callback == "function")
            callback();
          resolve();

        }
      });
    });

  };
  onlykey_api.check = function(cb) {
    onlykey_api.init = false;
    return onlykey_api.connect(cb);
  };
  onlykey_api.connect = onlykey_api.initok;

  async function OK_CONNECT(callback) {
    return new Promise(async function(resolve, reject) {

      function cb(err, data) {
        if (typeof callback === 'function') callback(err, data);
        resolve({ data: data, error: err });
      }

      var delay = 1;
      if (onlykey_api.OKversion == 'Original') {
        delay = delay * 4;
      }

      console.info("Connecting to OnlyKey");
      var cmd;
      var encryptedkeyHandle;
      var message;

      imports.app.emit("ok-connecting");
      cmd = OKCONNECT;
      message = [255, 255, 255, 255, OKCONNECT]; //Add header and message type
      var currentEpochTime = Math.round(new Date().getTime() / 1000.0).toString(16);
      var timePart = currentEpochTime.match(/.{2}/g).map(hexStrToDec);
      Array.prototype.push.apply(message, timePart);
      appKey = nacl.box.keyPair();
      // console.info(appKey);
      // console.info(appKey.publicKey);
      // console.info(appKey.secretKey);
      // console.info("Application ECDH Public Key: ", appKey.publicKey);
      Array.prototype.push.apply(message, appKey.publicKey);
      var env = [onlykey_api.browser.charCodeAt(0), onlykey_api.os.charCodeAt(0)];
      Array.prototype.push.apply(message, env);
      encryptedkeyHandle = Uint8Array.from(message); // Not encrypted as this is the initial key exchange
      await wait(delay * 1000);
      var enc_resp = 1;
      var ctaphid_response = await ctaphid_via_webauthn(cmd, null, null, null, encryptedkeyHandle, 6000, function(maybe_a_err, data) {
         console.info("ctaphid_response resp", maybe_a_err, data);

      });

      imports.app.emit("ok-waiting");

      var response;

      if (ctaphid_response.data && !ctaphid_response.error)
        response = ctaphid_response.data;

      if (!response) {
        if (onlykey_api.browser == 'Firefox') headermsg("
OnlyKey not connected! Close this tab and open a new one to try again.
");
        else headermsg("
OnlyKey not connected! Refresh this page to try again.
");
        imports.app.emit("ok-disconnected");
      }
      else {
        switch (ctaphid_response.status) {
          case "CTAP2_ERR_EXTENSION_NOT_SUPPORTED":
            break;
          case "CTAP1_SUCCESS":
            var BREAKING_BETA_8C = !!(bytes2string(response.slice(8, 20)) == "v0.2-beta.8c");

            if(!BREAKING_BETA_8C){
              okPub = response.slice(0, 32);

              // Decrypt with transit_key
              var transit_key = nacl.box.before(Uint8Array.from(okPub), appKey.secretKey);
              console.info("Onlykey transit public", okPub);
              console.info("App transit public", appKey.publicKey);
              console.info("Transit shared secret", transit_key);
              transit_key = await digestBuff(Uint8Array.from(transit_key)); //AES256 key sha256 hash of shared secret
              console.info("App AES Key", transit_key);
              var encrypted  = response.slice(32, response.length);
              onlykey_api.FWversion = bytes2string(response.slice(32+8, 32+20));
              response = await aesgcm_decrypt(encrypted, transit_key);
              onlykey_api.OKversion = response[32+19] == 99 ? 'Color' : 'Go';
              onlykey_api.sharedsec = nacl.box.before(Uint8Array.from(okPub), appKey.secretKey);
              console.info("Version:",[onlykey_api.OKversion, onlykey_api.FWversion]);
              imports.app.emit("ok-connected");
              cb(null);
            }else{
              okPub = response.slice(21, 53);
              console.info("OnlyKey Public Key: ", okPub);
              onlykey_api.sharedsec = nacl.box.before(Uint8Array.from(okPub), appKey.secretKey);
              console.info("NACL shared secret: ", onlykey_api.sharedsec);
              onlykey_api.OKversion = response[19] == 99 ? 'Color' : 'Original';
              onlykey_api.FWversion = bytes2string(response.slice(8, 20));
              console.info("Version:",[onlykey_api.OKversion, onlykey_api.FWversion]);
              imports.app.emit("ok-connected");
              cb(null);
            }
            headermsg("
OnlyKey " + onlykey_api.FWversion + " Secure Connection Established
\n");
            break;
          default:
            imports.app.emit("ok-disconnected");

        }
        cb(null, ctaphid_response.status);

      }

    });
  }

  function encode_ctaphid_request_as_keyhandle(cmd, opt1, opt2, opt3, data) {
    // console.log('REQUEST CMD', cmd);
    // console.log('REQUEST OPT1', opt1);
    // console.log('REQUEST OPT2', opt2);
    // console.log('REQUEST OPT3', opt3);
    // console.log('REQUEST DATA', data);
    //var addr = 0;

    data = data || new Uint8Array();

    const offset = 10;

    if (offset + data.length > 255) {
      throw new Error("Max size exceeded");
    }

    // `is_extension_request` expects at least 16 bytes of data
    const data_pad = data.length < 16 ? 16 - data.length : 0;
    var array = new Uint8Array(offset + data.length + data_pad);

    array[0] = cmd & 0xff;

    array[1] = opt1 & 0xff;
    array[2] = opt2 & 0xff;
    array[3] = opt3 & 0xff;
    array[4] = 0x8C; // 140
    array[5] = 0x27; //  39
    array[6] = 0x90; // 144
    array[7] = 0xf6; // 246

    array[8] = 0;
    array[9] = data.length & 0xff;

    array.set(data, offset);

    // console.log('FORMATTED REQUEST:', array);
    return array;
  }

  function decode_ctaphid_response_from_signature(response) {
    // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#using-the-ctap2-authenticatorgetassertion-command-with-ctap1-u2f-authenticators<Paste>
    //
    // compared to `parse_device_response`, the data is encoded a little differently here
    //
    // attestation.response.authenticatorData
    //
    // first 32 bytes: SHA-256 hash of the rp.id
    // 1 byte: zeroth bit = user presence set in U2F response (always 1)
    // last 4 bytes: signature counter (32 bit big-endian)
    //
    // attestation.response.signature
    // signature data (bytes 5-end of U2F response

    // console.log('UNFORMATTED RESPONSE:', response);

    var signature_count;
    if (onlykey_api.os == "Node") {
      signature_count = (
        new DataView(toArrayBuffer(Buffer.from(response.authenticatorData.slice(33, 37))))
      ).getUint32(0, false); // get count as 32 bit BE integer
    }
    else {
      signature_count = (
        new DataView(
          response.authenticatorData.slice(33, 37)
        )
      ).getUint32(0, false); // get count as 32 bit BE integer
    }

    var signature = new Uint8Array(response.signature);
    var error_code = signature[0];

    var data = null;
    var error = null;

    if (signature.length > 1)
      data = signature.slice(1, signature.length);

    switch (ctap_error_codes[error_code]) {
      case "CTAP1_SUCCESS":
        if (bytes2string(data.slice(0, 9)) == 'UNLOCKEDv') {
          // Reset shared secret and start over
          onlykey_api.unlocked = true;
        }
        else if (signature.length < 73 && bytes2string(data.slice(0, 6)) == 'Error ') {
          // Something went wrong, read the ascii response and display to user
          var msgtext = data.slice(0, getstringlen(data));
          error = bytes2string(msgtext);
        }
      default:
        console.warn("ctap_error_code", ctap_error_codes[error_code]);
        if (ctap_error_codes[error_code] == 'CTAP2_ERR_EXTENSION_NOT_SUPPORTED') {
           error = ctap_error_codes[error_code];
        }
        break;
    }

    return {
      count: signature_count,
      status: ctap_error_codes[error_code],
      data: data,
      error: error,
      signature: signature,
    };
  }

  function ctaphid_via_webauthn(cmd, opt1, opt2, opt3, data, timeout, cb) {
    var request = {
        cmd:cmd,
        opt1:opt1,
        opt2:opt2,
        opt3:opt3,
        input_data:data,
        timeout:timeout
      }

    //#define DERIVE_PUBLIC_KEY 1
    //#define DERIVE_SHARED_SECRET 2
    //#define NO_ENCRYPT_RESP 0
    //#define ENCRYPT_RESP 1
    var keyhandle = encode_ctaphid_request_as_keyhandle(cmd, opt1, opt2, opt3, data);
    var challenge = window.crypto.getRandomValues(new Uint8Array(32));

    var id = window.location.hostname;

    request.request_options = {
      challenge: challenge,
      allowCredentials: [{
        transports: ["usb"],
        id: keyhandle,
        type: 'public-key',
      }],
      timeout: timeout,
      //rpId: 'apps.crp.to',
      //rpId: id,
      userVerification: 'discouraged',
      //userPresence: 'false',
      //mediation: 'silent',
      //extensions: {
        // appid: 'https://apps.crp.to',
        //appid: 'https://' + id
      //},
    };

    return new Promise(async function(resolve) {
      // return

      console.log({ctaphid_request:request});
      var results = false;
      // console.log("REQUEST:", request_options);
      window.navigator.credentials.get({
        publicKey: request.request_options
      }).catch(error => {
        console.warn("ERROR CALLING:", cmd, opt1, opt2, opt3, data);
        console.warn("THE ERROR:", error);
        console.warn("NAME:", error.name);
        console.warn("MESSAGE:", error.message);
        var response = { error: "Error " + error.name + " " + error.message };
        if (error.name == 'NS_ERROR_ABORT' || error.name == 'AbortError' || error.name == 'InvalidStateError') {
          // _$status('done_challenge');
          response.error2 = response.error;
          response.error = "Error aborted or bad hw-key-state";
          // return resolve(-1); // 1 = set error: aborted or bad hw-key-state
        }

        if (error.name == 'NotAllowedError' && onlykey_api.os == 'Windows') {
          response.error2 = response.error;
          response.error = "Error Win 10 1903 issue maybe?";
        }

        results = response;

      }).then(assertion => {
        var response;
        if (!assertion && results) {
          response = results;
        }
        else {
          // console.log("GOT ASSERTION", assertion);
          // console.log("RESPONSE", assertion.response);
          response = decode_ctaphid_response_from_signature(assertion.response);
          response.request = request;
          // console.log("RESPONSE:", response);
        }
        console.log({ctaphid_response:response});

        if (cb) cb(response.error, response);
        resolve(response);
      });

    });

  }

  onlykey_api.encode_ctaphid_request_as_keyhandle = encode_ctaphid_request_as_keyhandle;
  onlykey_api.decode_ctaphid_response_from_signature = decode_ctaphid_response_from_signature;
  onlykey_api.ctaphid_via_webauthn = ctaphid_via_webauthn;

  function toArrayBuffer(buf) {
    var ab = new ArrayBuffer(buf.length);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buf.length; ++i) {
      view[i] = buf[i];
    }
    return ab;
  }

  function id(s) { return document.getElementById(s); }

  function headermsg(s) {
    //if(imports.app)
    //  imports.app.emit("ok-message",s);
    //else
      id('header_messages').innerHTML += "
" + s;

  }

  return onlykey_api;
};
<|endoftext|>


<commit_before>'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$ocLazyLoadProvider', '$urlRouterProvider',
	function($stateProvider, $ocLazyLoadProvider, $urlRouterProvider) {
		// Redirect to home view when route not found
		$urlRouterProvider.otherwise('/');

        $ocLazyLoadProvider.config({
        // Set to true if you want to see what and when is dynamically loaded
        debug: false
        });

		// Home state routing
		$stateProvider
		.state('home', {
			url: '/',
			templateUrl: 'modules/core/views/home.client.view.html'
		})
        .state('metrics', {
			url: '/metrics',
			templateUrl: 'modules/core/views/metrics.client.view.html'
		})
        .state('utilization', {
			url: '/utilization',
			templateUrl: 'modules/core/views/utilization.client.view.html',
            resolve: {
                loadPlugin: function ($ocLazyLoad) {
                    return $ocLazyLoad.load([
                        {
                            serie: true,
                            name: 'angel',
                            files: ['/lib/chartJs/angles.js', '/lib/chartJs/angles.js']
                        },
                        ]);
                }
            }
		})
        .state('projectDashboard', {
			url: '/Project Dashboard',
			templateUrl: 'modules/core/views/projectDashboard.client.view.html'
		})
        .state('mysqltest', {
			url: '/mysqltest',
			templateUrl: 'modules/core/views/mysqltest.client.view.html'
		});
	}
]);<commit_msg>lazy load<commit_after>'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$ocLazyLoadProvider', '$urlRouterProvider',
	function($stateProvider, $ocLazyLoadProvider, $urlRouterProvider) {
		// Redirect to home view when route not found
		$urlRouterProvider.otherwise('/');

        $ocLazyLoadProvider.config({
        // Set to true if you want to see what and when is dynamically loaded
        debug: false
        });

		// Home state routing
		$stateProvider
		.state('home', {
			url: '/',
			templateUrl: 'modules/core/views/home.client.view.html'
		})
        .state('metrics', {
			url: '/metrics',
			templateUrl: 'modules/core/views/metrics.client.view.html'
		})
        .state('utilization', {
			url: '/utilization',
			templateUrl: 'modules/core/views/utilization.client.view.html',
            resolve: {
                loadPlugin: function ($ocLazyLoad) {
                    return $ocLazyLoad.load([
                        {
                            serie: true,
                            name: 'angles',
                            files: ['/lib/chartJs/angles.js', '/lib/chartJs/Chart.min.js']
                        }
                        ]);
                }
            }
		})
        .state('projectDashboard', {
			url: '/Project Dashboard',
			templateUrl: 'modules/core/views/projectDashboard.client.view.html'
		})
        .state('mysqltest', {
			url: '/mysqltest',
			templateUrl: 'modules/core/views/mysqltest.client.view.html'
		});
	}
]);<|endoftext|>


<commit_before>// grunt-dep-concat
// https://github.com/tJener/grunt-dep-concat
//
// Copyright (c) 2012 Eric Li
// Licensed under the MIT license.

module.exports = function( grunt ) {
  var _ = grunt.utils._;

  var path = require( 'path' );
  var util = require( 'util' );
  var exec = require( 'child_process' ).exec;
  var spawn = require( 'child_process' ).spawn;

  var keywords = [ 'load', 'run' ];

  // A DependencyList describes two sets of dependencies, which I am calling
  // "loadtime" and "runtime" dependencies. "Loadtime" is when the concatenated
  // file is initially executed, and "runtime" is, well, during runtime. An
  // example may be clearer:
  //
  //     (function( $ ) {
  //       $(function() {
  //         console.log( _ );
  //       });
  //     }( jQuery ));
  //
  // Here jQuery is a "loadtime" dependency, and underscore/lodash is a
  // "runtime" dependency.
  var DependencyList = function DependencyList( path ) {
    this.path = path;
    _.forEach( keywords, function( prop ) {
      this[ prop ] = [];
    }.bind( this ));
  };

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerMultiTask( 'depconcat', 'Concatenate files, ordered by dependencies.', function() {
    var done = this.async();

    var files = grunt.file.expandFiles( this.file.src );
    var orderedFiles = [];
    grunt.helper( 'depconcat_order_files', files, orderedFiles, function() {
      var src = grunt.helper( 'concat', orderedFiles, { separator: this.data.separator });
      grunt.file.write( this.file.dest, src );

      if ( this.errorCount ) {
        done( false );
        return;
      }

      grunt.log.writeln('File "' + this.file.dest + '" created.');
      done( true );
    }.bind( this ), {
      basePath: this.data.basePath
    });
  });

  // ==========================================================================
  // HELPERS
  // ==========================================================================

  var trimElems = function( str ) {
    return _( str ).trim();
  };

  grunt.registerHelper( 'depconcat_order_files', function( files, orderedFiles, callback, options ) {
    files = files.slice(0);

    options = _.defaults( options || {}, {
      basePath: ''
    });

    var dependencyList, file;
    var deps = {};
    while ( null != (file = files.shift()) ) {
      if ( !deps[ file ] ) {
        dependencyList = null;

        // grunt.log.writeln( 'Attempting to read ' + file );

        try {
          dependencyList = grunt.helper( 'depconcat_file_parse_deps', file, options );
        } catch( e ) {
          orderedFiles.push( file );
        }

        if ( dependencyList ) {
          deps[ file ] = dependencyList;
          Array.prototype.unshift.apply( files, dependencyList.load.concat( dependencyList.run ) );
        }
      }
    }

    // grunt.log.writeln( 'Done reading.' );

    var depGraphString = '';
    var insertLoadDep = function( dependency ) {
      depGraphString += prop + ' ' + dependency + '\n';
    };

    var insertRunDep = function( dependency ) {
      depGraphString += dependency + ' ' + dependency + '\n';
    };

    for ( var prop in deps ) {
      dependencyList = deps[ prop ];
      dependencyList.load.forEach( insertLoadDep );
      dependencyList.run.forEach( insertRunDep );
      insertRunDep( dependencyList.path );
    }

    exec( 'echo "' + depGraphString + '" | tsort', function( error, stdout, stderr ) {
      Array.prototype.push.apply( orderedFiles, _( stdout ).words( '\n' ).reverse() );

      callback();
    });
  });

  // Given a file, grabs the source and returns a DependencyList.
  grunt.registerHelper( 'depconcat_file_parse_deps', function( filepath, options ) {
    options = _.defaults( options || {}, {
      basePath: ''
    });

    var src = grunt.file.read( filepath );
    return parseDeps( src, new DependencyList( filepath ), options );
  });

  var parseDeps = function( src, dependencyList, options ) {
    var comment_re = /^(?:\s*\/\/\s*(.*)|\s*\/\*(.*)\*\/)$/gm;

    // Grab comment lines.
    var matches, lines = [];
    while ( null != (matches = comment_re.exec( src )) ) {
      lines.push( matches[1] || matches[2] );
    }

    var deps = {};
    _.forEach( keywords, function( prop ) {
      deps[ prop ] = [];
    });

    // Pull dependencies from comments.
    _.forEach( lines, function( line ) {
      var split = _.words( line, ':' ).map( trimElems );

      if ( split.length > 1 ) {
        var fileList = deps[ split[0] ];
        if ( Array.isArray(fileList) ) {
          Array.prototype.push.apply(
            fileList,
            _.words( split[1], ',' ).map( trimElems )
          );
        }
      }

    });

    var pathMap = function( filePath ) {
      return path.join( options.basePath, filePath );
    };

    // Add deps to DependecyList.
    for ( var prop in deps ) {
      Array.prototype.push.apply( dependencyList[ prop ], deps[prop].map(pathMap) );
    }

    return dependencyList;
  };

};
<commit_msg>Remove keywords array to simplify logic<commit_after>// grunt-dep-concat
// https://github.com/tJener/grunt-dep-concat
//
// Copyright (c) 2012 Eric Li
// Licensed under the MIT license.

module.exports = function( grunt ) {
  var _ = grunt.utils._;

  var path = require( 'path' );
  var util = require( 'util' );
  var exec = require( 'child_process' ).exec;
  var spawn = require( 'child_process' ).spawn;

  // A DependencyList describes two sets of dependencies, which I am calling
  // "loadtime" and "runtime" dependencies. "Loadtime" is when the concatenated
  // file is initially executed, and "runtime" is, well, during runtime. An
  // example may be clearer:
  //
  //     (function( $ ) {
  //       $(function() {
  //         console.log( _ );
  //       });
  //     }( jQuery ));
  //
  // Here jQuery is a "loadtime" dependency, and underscore/lodash is a
  // "runtime" dependency.
  var DependencyList = function DependencyList( path ) {
    this.path = path;
    this.load = [];
    this.run  = [];
  };

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerMultiTask( 'depconcat', 'Concatenate files, ordered by dependencies.', function() {
    var done = this.async();

    var files = grunt.file.expandFiles( this.file.src );
    var orderedFiles = [];
    grunt.helper( 'depconcat_order_files', files, orderedFiles, function() {
      var src = grunt.helper( 'concat', orderedFiles, { separator: this.data.separator });
      grunt.file.write( this.file.dest, src );

      if ( this.errorCount ) {
        done( false );
        return;
      }

      grunt.log.writeln('File "' + this.file.dest + '" created.');
      done( true );
    }.bind( this ), {
      basePath: this.data.basePath
    });
  });

  // ==========================================================================
  // HELPERS
  // ==========================================================================

  var trimElems = function( str ) {
    return _( str ).trim();
  };

  grunt.registerHelper( 'depconcat_order_files', function( files, orderedFiles, callback, options ) {
    files = files.slice(0);

    options = _.defaults( options || {}, {
      basePath: ''
    });

    var dependencyList, file;
    var deps = {};
    while ( null != (file = files.shift()) ) {
      if ( !deps[ file ] ) {
        dependencyList = null;

        // grunt.log.writeln( 'Attempting to read ' + file );

        try {
          dependencyList = grunt.helper( 'depconcat_file_parse_deps', file, options );
        } catch( e ) {
          orderedFiles.push( file );
        }

        if ( dependencyList ) {
          deps[ file ] = dependencyList;
          Array.prototype.unshift.apply( files, dependencyList.load.concat( dependencyList.run ) );
        }
      }
    }

    // grunt.log.writeln( 'Done reading.' );

    var depGraphString = '';
    var insertLoadDep = function( dependency ) {
      depGraphString += prop + ' ' + dependency + '\n';
    };

    var insertRunDep = function( dependency ) {
      depGraphString += dependency + ' ' + dependency + '\n';
    };

    for ( var prop in deps ) {
      dependencyList = deps[ prop ];
      dependencyList.load.forEach( insertLoadDep );
      dependencyList.run.forEach( insertRunDep );
      insertRunDep( dependencyList.path );
    }

    exec( 'echo "' + depGraphString + '" | tsort', function( error, stdout, stderr ) {
      Array.prototype.push.apply( orderedFiles, _( stdout ).words( '\n' ).reverse() );

      callback();
    });
  });

  // Given a file, grabs the source and returns a DependencyList.
  grunt.registerHelper( 'depconcat_file_parse_deps', function( filepath, options ) {
    options = _.defaults( options || {}, {
      basePath: ''
    });

    var src = grunt.file.read( filepath );
    return parseDeps( src, new DependencyList( filepath ), options );
  });

  var parseDeps = function( src, dependencyList, options ) {
    var comment_re = /^(?:\s*\/\/\s*(.*)|\s*\/\*(.*)\*\/)$/gm;

    // Grab comment lines.
    var matches, lines = [];
    while ( null != (matches = comment_re.exec( src )) ) {
      lines.push( matches[1] || matches[2] );
    }

    var deps = {};
    _.each([ 'load', 'run' ], function( prop ) {
      deps[ prop ] = [];
    });

    // Pull dependencies from comments.
    _.forEach( lines, function( line ) {
      var split = _.words( line, ':' ).map( trimElems );

      if ( split.length > 1 ) {
        var fileList = deps[ split[0] ];
        if ( Array.isArray(fileList) ) {
          Array.prototype.push.apply(
            fileList,
            _.words( split[1], ',' ).map( trimElems )
          );
        }
      }

    });

    var pathMap = function( filePath ) {
      return path.join( options.basePath, filePath );
    };

    // Add deps to DependecyList.
    for ( var prop in deps ) {
      Array.prototype.push.apply( dependencyList[ prop ], deps[prop].map(pathMap) );
    }

    return dependencyList;
  };

};
<|endoftext|>


<commit_before>/*
  All Emoncms code is released under the GNU Affero General Public License.
  See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Trystan Lea: trystan.lea@googlemail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

function feedtimestamp_widgetlist()
{
  var widgets =
  {
    "feedtimestamp":
    {
      "offsetx":-40,"offsety":-30,"width":120,"height":60,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  };

	var fontoptions = [
					[9, "Arial Black"],
					[8, "Arial Narrow"],
					[7, "sans-serif"],
					[6, "Helvetica Neue"],
					[5, "Helvetica"],
					[4, "Comic Sans MS"],
					[3, "Courier New"],
					[2, "Arial"],
					[1, "Georgia"],
					[0, "Impact"]
				];

	var fstyleoptions = [
					[2, _Tr("Normal")],
					[1, _Tr("Italic")],
					[0, _Tr("Oblique")]
				];

	var fweightoptions = [
					[1, _Tr("Bold")],
					[0, _Tr("Normal")]
				];

	var sizeoptions = [
					[14, "18"], // set size 18 to the top position to be the default value for creating new feedtimestamp widgets otherwise size 40 would be always the default
					[13, "40"],
					[12, "36"],
					[11, "32"],
					[10, "28"],
					[9, "24"],
					[8, "22"],
					[7, "20"],
					[6, "18"],
					[5, "16"],
					[4, "14"],
					[3, "12"],
					[2, "10"],
					[1, "8"],
					[0, "6"]
				];

	var alignmentOptions = [
		["center", _Tr("Center")],
		["left", _Tr("Left")],
		["right", _Tr("Right")]
	];

	var dateformatOptions = [
		[0, _Tr("Not Displayed")],
		[1, "DD/MM/YYYY"],
		[2, "DD-MM-YYYY"],
		[3, "DD.MM.YYYY"],
		[4, "YYYY/MM/DD"],
		[5, "YYYY-MM-DD"],
		[6, "YYYY.MM.DD"],
		[7, "MM/DD/YYYY"],
		[8, "MM-DD-YYYY"],
		[9, "MM.DD.YYYY"]
	];

	var timeformatOptions = [
		[0, _Tr("Not Displayed")],
		[1, "HH:MM:SS"],
		[2, "HH:MM"],
		[3, "H:MM:SS"],
		[4, "H:MM"],
		[5, "H:MM:SS A"],
		[6, "H:MM A"]
	];

	addOption(widgets["feedtimestamp"], "feedid",       "feedid",        _Tr("Feed"),          _Tr("Feed value"),                     []);
	addOption(widgets["feedtimestamp"], "colour",       "colour_picker", _Tr("Colour"),        _Tr("Colour used for display"),        []);
	addOption(widgets["feedtimestamp"], "font",         "dropbox",       _Tr("Font"),          _Tr("Font used for display"),          fontoptions);
	addOption(widgets["feedtimestamp"], "fstyle",       "dropbox",       _Tr("Font style"),    _Tr("Font style used for display"),    fstyleoptions);
	addOption(widgets["feedtimestamp"], "fweight",      "dropbox",       _Tr("Font weight"),   _Tr("Font weight used for display"),   fweightoptions);
	addOption(widgets["feedtimestamp"], "size",         "dropbox",       _Tr("Size"),          _Tr("Text size in px to use"),         sizeoptions);
	addOption(widgets["feedtimestamp"], "align",        "dropbox",       _Tr("Alignment"),     _Tr("Alignment"),                      alignmentOptions);
	addOption(widgets["feedtimestamp"], "dateformat",   "dropbox",       _Tr("Date format"),   _Tr("Date format"),                    dateformatOptions);
	addOption(widgets["feedtimestamp"], "timeformat",   "dropbox",       _Tr("Time format"),   _Tr("Time format"),                    timeformatOptions);
	return widgets;
}

function draw_feedtimestamp(feedvalue,
		x_pos,				// these x and y coords seem unused?
		y_pos,
		font,
		fstyle,
		fweight,
		width,
		height,
		val,
		units,
		colour,
		size,
		align,
		unitend)
		{
			colour = colour || "4444CC";
			unitend = unitend || "0";
			size = size || "8";
			font = font || "5";
			fstyle = fstyle || "2";
			fweight = fweight || "1";
			align = align || "center";

			var fontsize;

			if (size === "0"){fontsize = 6;}
			if (size === "1"){fontsize = 8;}
			if (size === "2"){fontsize = 10;}
			if (size === "3"){fontsize = 12;}
			if (size === "4"){fontsize = 14;}
			if (size === "5"){fontsize = 16;}
			if (size === "6"){fontsize = 18;}
			if (size === "7"){fontsize = 20;}
			if (size === "8"){fontsize = 22;}
			if (size === "9"){fontsize = 24;}
			if (size === "10"){fontsize = 28;}
			if (size === "11"){fontsize = 32;}
			if (size === "12"){fontsize = 36;}
			if (size === "13"){fontsize = 40;}
			if (size === "14"){fontsize = 18;}  //default value so that not size 40 is always the default

			var fontname;

			if (font === "0"){fontname = "Impact";}
			if (font === "1"){fontname = "Georgia";}
			if (font === "2"){fontname = "Arial";}
			if (font === "3"){fontname = "Courier New";}
			if (font === "4"){fontname = "Comic Sans MS";}
			if (font === "5"){fontname = "Helvetica";}
			if (font === "6"){fontname = "Helvetica Neue";}
			if (font === "7"){fontname = "sans-serif";}
			if (font === "8"){fontname = "Arial Narrow";}
			if (font === "9"){fontname = "Arial Black";}

			var fontstyle;

			if (fstyle === "0"){fontstyle = "oblique";}
			if (fstyle === "1"){fontstyle = "italic";}
			if (fstyle === "2"){fontstyle = "normal";}

			var fontweight;

			if (fweight === "0"){fontweight = "normal";}
			if (fweight === "1"){fontweight = "bold";}

			if (colour.indexOf("#") === -1){			// Fix missing "#" on colour if needed
				colour = "#" + colour;
			}

			feedvalue.css({
				"color":colour,
				"font":fontstyle+" "+ fontweight+" "+ fontsize+"px "+fontname,"text-align":align,
				"line-height":height+"px"
			});

			feedvalue.html(val);

}

function feedtimestamp_draw()
{
	$(".feedtimestamp").each(function(index)
		{
			var feedvalue = $(this);
			var font = $(this).attr("font");
			var feedid = $(this).attr("feedid");
			if (assocfeed[feedid]!=undefined) feedid = assocfeed[feedid]; // convert tag:name to feedid
			if (associd[feedid] === undefined) {
                // console.log("Review config for feed id of " + $(this).attr("class"));
                return;
            }

			var timestamp = new Date(associd[feedid]["time"] * 1000)
			var day = "0" + timestamp.getDate();
			var month ="0" + (timestamp.getMonth() + 1);
			var year = timestamp.getFullYear();
			var hours = timestamp.getHours();
			var minutes = "0" + timestamp.getMinutes();
			var seconds = "0" + timestamp.getSeconds();

			var val;
			var hours2;
			var dateformat = $(this).attr("dateformat");
			var timeformat = $(this).attr("timeformat");

			if(hours==0){hours2=12; suffix="AM";}
			if(hours<12 && hours!=0){hours2=hours; suffix="AM";}
			if(hours==12){hours2=hours; suffix="PM";}
			if(hours>12){hours2=hours-12; suffix="PM";}

			if (dateformat === "0"){val = "";}
			if (dateformat === "1"){val=day.substr(-2) + "/" +month.substr(-2)+ "/" +year + " ";}
			if (dateformat === "2"){val=day.substr(-2) + "-" +month.substr(-2)+ "-" +year + " ";}
			if (dateformat === "3"){val=day.substr(-2) + "." +month.substr(-2)+ "." +year + " ";}
			if (dateformat === "4"){val=year + "/" +month.substr(-2) + "/" +day.substr(-2)+ " ";}
			if (dateformat === "5"){val=year + "-" +month.substr(-2) + "-" +day.substr(-2)+ " ";}
			if (dateformat === "6"){val=year + " " +month.substr(-2) + "." +day.substr(-2)+ " ";}
			if (dateformat === "7"){val=month.substr(-2) + "/" +day.substr(-2)+ "/" +year + " ";}
			if (dateformat === "8"){val=month.substr(-2) + "-" +day.substr(-2)+ "-" +year + " ";}
			if (dateformat === "9"){val=month.substr(-2) + "." +day.substr(-2)+ "." +year + " ";}

			if (timeformat === "1"){val=val + ("0"+hours).substr(-2) + ":" +minutes.substr(-2)+ ":" +seconds.substr(-2);}
			if (timeformat === "2"){val=val + ("0"+hours).substr(-2) + ":" +minutes.substr(-2);}
			if (timeformat === "3"){val=val + hours + ":" +minutes.substr(-2)+ ":" +seconds.substr(-2);}
			if (timeformat === "4"){val=val + hours + ":" +minutes.substr(-2);}
			if (timeformat === "5"){val=val + hours2 + ":" +minutes.substr(-2)+ ":" +seconds.substr(-2)+ " "+suffix;}
			if (timeformat === "6"){val=val + hours2 + ":" +minutes.substr(-2) + " "+suffix;}
			var size = $(this).attr("size");
			{
				var id = "can-"+$(this).attr("id");

				draw_feedtimestamp(feedvalue,
					0,
					0,
					$(this).attr("font"),
					$(this).attr("fstyle"),
					$(this).attr("fweight"),
					$(this).width(),
					$(this).height(),
					val,
					$(this).attr("units"),
					$(this).attr("colour"),
					$(this).attr("size"),
					$(this).attr("align"),
					$(this).attr("unitend")
					);
			}
		});
}

function feedtimestamp_init()
{
	$(".feedvalue").html("");
}
function feedtimestamp_slowupdate()
	{
		feedtimestamp_draw();
	}

function feedtimestamp_fastupdate()
	{
		feedtimestamp_draw();
	}
<commit_msg>Update feedtimestamp_render.js<commit_after>/*
  All Emoncms code is released under the GNU Affero General Public License.
  See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org

    Author: Aymeric Thibaut
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */

function feedtimestamp_widgetlist()
{
  var widgets =
  {
    "feedtimestamp":
    {
      "offsetx":-40,"offsety":-30,"width":120,"height":60,
      "menu":"Widgets",
      "options":    [],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  };

	var fontoptions = [
					[9, "Arial Black"],
					[8, "Arial Narrow"],
					[7, "sans-serif"],
					[6, "Helvetica Neue"],
					[5, "Helvetica"],
					[4, "Comic Sans MS"],
					[3, "Courier New"],
					[2, "Arial"],
					[1, "Georgia"],
					[0, "Impact"]
				];

	var fstyleoptions = [
					[2, _Tr("Normal")],
					[1, _Tr("Italic")],
					[0, _Tr("Oblique")]
				];

	var fweightoptions = [
					[1, _Tr("Bold")],
					[0, _Tr("Normal")]
				];

	var sizeoptions = [
					[14, "18"], // set size 18 to the top position to be the default value for creating new feedtimestamp widgets otherwise size 40 would be always the default
					[13, "40"],
					[12, "36"],
					[11, "32"],
					[10, "28"],
					[9, "24"],
					[8, "22"],
					[7, "20"],
					[6, "18"],
					[5, "16"],
					[4, "14"],
					[3, "12"],
					[2, "10"],
					[1, "8"],
					[0, "6"]
				];

	var alignmentOptions = [
		["center", _Tr("Center")],
		["left", _Tr("Left")],
		["right", _Tr("Right")]
	];

	var dateformatOptions = [
		[0, _Tr("Not Displayed")],
		[1, "DD/MM/YYYY"],
		[2, "DD-MM-YYYY"],
		[3, "DD.MM.YYYY"],
		[4, "YYYY/MM/DD"],
		[5, "YYYY-MM-DD"],
		[6, "YYYY.MM.DD"],
		[7, "MM/DD/YYYY"],
		[8, "MM-DD-YYYY"],
		[9, "MM.DD.YYYY"]
	];

	var timeformatOptions = [
		[0, _Tr("Not Displayed")],
		[1, "HH:MM:SS"],
		[2, "HH:MM"],
		[3, "H:MM:SS"],
		[4, "H:MM"],
		[5, "H:MM:SS A"],
		[6, "H:MM A"]
	];

	addOption(widgets["feedtimestamp"], "feedid",       "feedid",        _Tr("Feed"),          _Tr("Feed value"),                     []);
	addOption(widgets["feedtimestamp"], "colour",       "colour_picker", _Tr("Colour"),        _Tr("Colour used for display"),        []);
	addOption(widgets["feedtimestamp"], "font",         "dropbox",       _Tr("Font"),          _Tr("Font used for display"),          fontoptions);
	addOption(widgets["feedtimestamp"], "fstyle",       "dropbox",       _Tr("Font style"),    _Tr("Font style used for display"),    fstyleoptions);
	addOption(widgets["feedtimestamp"], "fweight",      "dropbox",       _Tr("Font weight"),   _Tr("Font weight used for display"),   fweightoptions);
	addOption(widgets["feedtimestamp"], "size",         "dropbox",       _Tr("Size"),          _Tr("Text size in px to use"),         sizeoptions);
	addOption(widgets["feedtimestamp"], "align",        "dropbox",       _Tr("Alignment"),     _Tr("Alignment"),                      alignmentOptions);
	addOption(widgets["feedtimestamp"], "dateformat",   "dropbox",       _Tr("Date format"),   _Tr("Date format"),                    dateformatOptions);
	addOption(widgets["feedtimestamp"], "timeformat",   "dropbox",       _Tr("Time format"),   _Tr("Time format"),                    timeformatOptions);
	return widgets;
}

function draw_feedtimestamp(feedvalue,
		x_pos,				// these x and y coords seem unused?
		y_pos,
		font,
		fstyle,
		fweight,
		width,
		height,
		val,
		units,
		colour,
		size,
		align,
		unitend)
		{
			colour = colour || "4444CC";
			unitend = unitend || "0";
			size = size || "8";
			font = font || "5";
			fstyle = fstyle || "2";
			fweight = fweight || "1";
			align = align || "center";

			var fontsize;

			if (size === "0"){fontsize = 6;}
			if (size === "1"){fontsize = 8;}
			if (size === "2"){fontsize = 10;}
			if (size === "3"){fontsize = 12;}
			if (size === "4"){fontsize = 14;}
			if (size === "5"){fontsize = 16;}
			if (size === "6"){fontsize = 18;}
			if (size === "7"){fontsize = 20;}
			if (size === "8"){fontsize = 22;}
			if (size === "9"){fontsize = 24;}
			if (size === "10"){fontsize = 28;}
			if (size === "11"){fontsize = 32;}
			if (size === "12"){fontsize = 36;}
			if (size === "13"){fontsize = 40;}
			if (size === "14"){fontsize = 18;}  //default value so that not size 40 is always the default

			var fontname;

			if (font === "0"){fontname = "Impact";}
			if (font === "1"){fontname = "Georgia";}
			if (font === "2"){fontname = "Arial";}
			if (font === "3"){fontname = "Courier New";}
			if (font === "4"){fontname = "Comic Sans MS";}
			if (font === "5"){fontname = "Helvetica";}
			if (font === "6"){fontname = "Helvetica Neue";}
			if (font === "7"){fontname = "sans-serif";}
			if (font === "8"){fontname = "Arial Narrow";}
			if (font === "9"){fontname = "Arial Black";}

			var fontstyle;

			if (fstyle === "0"){fontstyle = "oblique";}
			if (fstyle === "1"){fontstyle = "italic";}
			if (fstyle === "2"){fontstyle = "normal";}

			var fontweight;

			if (fweight === "0"){fontweight = "normal";}
			if (fweight === "1"){fontweight = "bold";}

			if (colour.indexOf("#") === -1){			// Fix missing "#" on colour if needed
				colour = "#" + colour;
			}

			feedvalue.css({
				"color":colour,
				"font":fontstyle+" "+ fontweight+" "+ fontsize+"px "+fontname,"text-align":align,
				"line-height":height+"px"
			});

			feedvalue.html(val);

}

function feedtimestamp_draw()
{
	$(".feedtimestamp").each(function(index)
		{
			var feedvalue = $(this);
			var font = $(this).attr("font");
			var feedid = $(this).attr("feedid");
			if (assocfeed[feedid]!=undefined) feedid = assocfeed[feedid]; // convert tag:name to feedid
			if (associd[feedid] === undefined) {
                // console.log("Review config for feed id of " + $(this).attr("class"));
                return;
            }

			var timestamp = new Date(associd[feedid]["time"] * 1000)
			var day = "0" + timestamp.getDate();
			var month ="0" + (timestamp.getMonth() + 1);
			var year = timestamp.getFullYear();
			var hours = timestamp.getHours();
			var minutes = "0" + timestamp.getMinutes();
			var seconds = "0" + timestamp.getSeconds();

			var val;
			var hours2;
			var dateformat = $(this).attr("dateformat");
			var timeformat = $(this).attr("timeformat");

			if(hours==0){hours2=12; suffix="AM";}
			if(hours<12 && hours!=0){hours2=hours; suffix="AM";}
			if(hours==12){hours2=hours; suffix="PM";}
			if(hours>12){hours2=hours-12; suffix="PM";}

			if (dateformat === "0"){val = "";}
			if (dateformat === "1"){val=day.substr(-2) + "/" +month.substr(-2)+ "/" +year + " ";}
			if (dateformat === "2"){val=day.substr(-2) + "-" +month.substr(-2)+ "-" +year + " ";}
			if (dateformat === "3"){val=day.substr(-2) + "." +month.substr(-2)+ "." +year + " ";}
			if (dateformat === "4"){val=year + "/" +month.substr(-2) + "/" +day.substr(-2)+ " ";}
			if (dateformat === "5"){val=year + "-" +month.substr(-2) + "-" +day.substr(-2)+ " ";}
			if (dateformat === "6"){val=year + " " +month.substr(-2) + "." +day.substr(-2)+ " ";}
			if (dateformat === "7"){val=month.substr(-2) + "/" +day.substr(-2)+ "/" +year + " ";}
			if (dateformat === "8"){val=month.substr(-2) + "-" +day.substr(-2)+ "-" +year + " ";}
			if (dateformat === "9"){val=month.substr(-2) + "." +day.substr(-2)+ "." +year + " ";}

			if (timeformat === "1"){val=val + ("0"+hours).substr(-2) + ":" +minutes.substr(-2)+ ":" +seconds.substr(-2);}
			if (timeformat === "2"){val=val + ("0"+hours).substr(-2) + ":" +minutes.substr(-2);}
			if (timeformat === "3"){val=val + hours + ":" +minutes.substr(-2)+ ":" +seconds.substr(-2);}
			if (timeformat === "4"){val=val + hours + ":" +minutes.substr(-2);}
			if (timeformat === "5"){val=val + hours2 + ":" +minutes.substr(-2)+ ":" +seconds.substr(-2)+ " "+suffix;}
			if (timeformat === "6"){val=val + hours2 + ":" +minutes.substr(-2) + " "+suffix;}
			var size = $(this).attr("size");
			{
				var id = "can-"+$(this).attr("id");

				draw_feedtimestamp(feedvalue,
					0,
					0,
					$(this).attr("font"),
					$(this).attr("fstyle"),
					$(this).attr("fweight"),
					$(this).width(),
					$(this).height(),
					val,
					$(this).attr("units"),
					$(this).attr("colour"),
					$(this).attr("size"),
					$(this).attr("align"),
					$(this).attr("unitend")
					);
			}
		});
}

function feedtimestamp_init()
{
	$(".feedvalue").html("");
}
function feedtimestamp_slowupdate()
	{
		feedtimestamp_draw();
	}

function feedtimestamp_fastupdate()
	{
		feedtimestamp_draw();
	}
<|endoftext|>


<commit_before>
/* bs-tabbable: Bootstrap tabs implementation
 * @example
   <bs-tabbable ng-model="curTab">
     <tabbable title="First" value="first">
     <tabbable ng-repeat="t in tabs" title="{{t.title}}" value="{{t.id}}">
   </bs-tabbable>
   ...
   scope.curTab = "first"; //sets first tab to selected
   scope.curTab = "t[0].id"; //sets first tab in repeater to selected
 */

angular.module('ui.bootstrap').directive('bsTabbable', [function() {
  return {
    restrict: 'ECA',
    compile: function(element) {
      var navTabs = angular.element('

'),
          tabContent = angular.element('

');

      tabContent.append(element.contents());
      element.append(navTabs).append(tabContent);
      element.addClass('tabbable');
    },
    controller: ['$scope', '$element', function($scope, $element) {
      var navTabs = $element.contents().eq(0),
          ngModel = $element.controller('ngModel') || {},
          tabs = [],
          selectedTab;

      ngModel.$render = function() {
        var $viewValue = this.$viewValue;

        if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
          if(selectedTab) {
            selectedTab.paneElement.removeClass('active');
            selectedTab.tabElement.removeClass('active');
            selectedTab = null;
          }
          if($viewValue) {
            for(var i = 0, ii = tabs.length; i < ii; i++) {
              if ($viewValue == tabs[i].value) {
                selectedTab = tabs[i];
                break;
              }
            }
            if (selectedTab) {
              selectedTab.paneElement.addClass('active');
              selectedTab.tabElement.addClass('active');
            }
          }

        }
      };

      this.addPane = function(element, attr) {
        var li = angular.element('

'),
            a = li.find('a'),
            tab = {
              paneElement: element,
              paneAttrs: attr,
              tabElement: li
            };

        tabs.push(tab);

        attr.$observe('value', update)();
        attr.$observe('title', function(){ update(); a.text(tab.title); })();

        function update() {
          tab.title = attr.title;
          tab.value = attr.value || attr.title;
          if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
            // we are not part of angular
            ngModel.$viewValue = tab.value;
          }
          ngModel.$render();
        }

        navTabs.append(li);
        li.bind('click', function(event) {
          event.preventDefault();
          event.stopPropagation();
          if (ngModel.$setViewValue) {
            $scope.$apply(function() {
              ngModel.$setViewValue(tab.value);
              ngModel.$render();
            });
          } else {
            // we are not part of angular
            ngModel.$viewValue = tab.value;
            ngModel.$render();
          }
        });

        return function() {
          tab.tabElement.remove();
          for(var i = 0, ii = tabs.length; i < ii; i++ ) {
            if (tab == tabs[i]) {
              tabs.splice(i, 1);
            }
          }
        };
      };
    }]
  };
}]);
<commit_msg>Fix tabbable docs having typo<commit_after>
/* bs-tabbable: Bootstrap tabs implementation
 * @example
   <bs-tabbable ng-model="curTab">
     <tabbable title="First" value="first">
     <tabbable ng-repeat="t in tabs" title="{{t.title}}" value="{{t.id}}">
   </bs-tabbable>
   ...
   scope.curTab = "first"; //sets first tab to selected
   scope.curTab = t[0].id; //sets first tab in repeater to selected
 */

angular.module('ui.bootstrap').directive('bsTabbable', [function() {
  return {
    restrict: 'ECA',
    compile: function(element) {
      var navTabs = angular.element('

'),
          tabContent = angular.element('

');

      tabContent.append(element.contents());
      element.append(navTabs).append(tabContent);
      element.addClass('tabbable');
    },
    controller: ['$scope', '$element', function($scope, $element) {
      var navTabs = $element.contents().eq(0),
          ngModel = $element.controller('ngModel') || {},
          tabs = [],
          selectedTab;

      ngModel.$render = function() {
        var $viewValue = this.$viewValue;

        if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
          if(selectedTab) {
            selectedTab.paneElement.removeClass('active');
            selectedTab.tabElement.removeClass('active');
            selectedTab = null;
          }
          if($viewValue) {
            for(var i = 0, ii = tabs.length; i < ii; i++) {
              if ($viewValue == tabs[i].value) {
                selectedTab = tabs[i];
                break;
              }
            }
            if (selectedTab) {
              selectedTab.paneElement.addClass('active');
              selectedTab.tabElement.addClass('active');
            }
          }

        }
      };

      this.addPane = function(element, attr) {
        var li = angular.element('

'),
            a = li.find('a'),
            tab = {
              paneElement: element,
              paneAttrs: attr,
              tabElement: li
            };

        tabs.push(tab);

        attr.$observe('value', update)();
        attr.$observe('title', function(){ update(); a.text(tab.title); })();

        function update() {
          tab.title = attr.title;
          tab.value = attr.value || attr.title;
          if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
            // we are not part of angular
            ngModel.$viewValue = tab.value;
          }
          ngModel.$render();
        }

        navTabs.append(li);
        li.bind('click', function(event) {
          event.preventDefault();
          event.stopPropagation();
          if (ngModel.$setViewValue) {
            $scope.$apply(function() {
              ngModel.$setViewValue(tab.value);
              ngModel.$render();
            });
          } else {
            // we are not part of angular
            ngModel.$viewValue = tab.value;
            ngModel.$render();
          }
        });

        return function() {
          tab.tabElement.remove();
          for(var i = 0, ii = tabs.length; i < ii; i++ ) {
            if (tab == tabs[i]) {
              tabs.splice(i, 1);
            }
          }
        };
      };
    }]
  };
}]);
<|endoftext|>


<commit_before>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';

import sdk from '../../../index';

import { COUNTRIES } from '../../../phonenumber';
import { charactersToImageNode } from '../../../HtmlUtils';

const COUNTRIES_BY_ISO2 = new Object(null);
for (const c of COUNTRIES) {
    COUNTRIES_BY_ISO2[c.iso2] = c;
}

function countryMatchesSearchQuery(query, country) {
    if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
    if (country.iso2 == query.toUpperCase()) return true;
    if (country.prefix == query) return true;
    return false;
}

export default class CountryDropdown extends React.Component {
    constructor(props) {
        super(props);
        this._onSearchChange = this._onSearchChange.bind(this);
        this._onOptionChange = this._onOptionChange.bind(this);

        this.state = {
            searchQuery: '',
        }
    }

    componentWillMount() {
        if (!this.props.value) {
            // If no value is given, we start with the first
            // country selected, but our parent component
            // doesn't know this, therefore we do this.
            this.props.onOptionChange(COUNTRIES[0]);
        }
    }

    _onSearchChange(search) {
        this.setState({
            searchQuery: search,
        });
    }

    _onOptionChange(iso2) {
        this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
    }

    _flagImgForIso2(iso2) {
        // Unicode Regional Indicator Symbol letter 'A'
        const RIS_A = 0x1F1E6;
        const ASCII_A = 65;
        return charactersToImageNode(iso2, true,
            RIS_A + (iso2.charCodeAt(0) - ASCII_A),
            RIS_A + (iso2.charCodeAt(1) - ASCII_A),
        );
    }

    getCountryPrefix(iso2) {
        return COUNTRIES_BY_ISO2[iso2].prefix;
    }

    render() {
        const Dropdown = sdk.getComponent('elements.Dropdown');

        let displayedCountries;
        if (this.state.searchQuery) {
            displayedCountries = COUNTRIES.filter(
                countryMatchesSearchQuery.bind(this, this.state.searchQuery),
            );
            if (
                this.state.searchQuery.length == 2 &&
                COUNTRIES_BY_ISO2[this.state.searchQuery.toUpperCase()]
            ) {
                // exact ISO2 country name match: make the first result the matches ISO2
                const matched = COUNTRIES_BY_ISO2[this.state.searchQuery.toUpperCase()];
                displayedCountries = displayedCountries.filter((c) => {
                    return c.iso2 != matched.iso2;
                });
                displayedCountries.unshift(matched);
            }
        } else {
            displayedCountries = COUNTRIES;
        }

        const options = displayedCountries.map((country) => {
            return

                {this._flagImgForIso2(country.iso2)}
                {country.name}

;
        });

        // default value here too, otherwise we need to handle null / undefined
        // values between mounting and the initial value propgating
        const value = this.props.value || COUNTRIES[0].iso2;

        const getShortOption = this.props.isSmall ? this._flagImgForIso2 : undefined;

        return <Dropdown className={this.props.className}
            onOptionChange={this._onOptionChange} onSearchChange={this._onSearchChange}
            menuWidth={298} getShortOption={getShortOption}
            value={value} searchEnabled={true}
        >
            {options}
        </Dropdown>
    }
}

CountryDropdown.propTypes = {
    className: React.PropTypes.string,
    isSmall: React.PropTypes.bool,
    onOptionChange: React.PropTypes.func.isRequired,
    value: React.PropTypes.string,
};
<commit_msg>Remove redundant API for getting country prefix<commit_after>/*
Copyright 2017 Vector Creations Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';

import sdk from '../../../index';

import { COUNTRIES } from '../../../phonenumber';
import { charactersToImageNode } from '../../../HtmlUtils';

const COUNTRIES_BY_ISO2 = new Object(null);
for (const c of COUNTRIES) {
    COUNTRIES_BY_ISO2[c.iso2] = c;
}

function countryMatchesSearchQuery(query, country) {
    if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
    if (country.iso2 == query.toUpperCase()) return true;
    if (country.prefix == query) return true;
    return false;
}

export default class CountryDropdown extends React.Component {
    constructor(props) {
        super(props);
        this._onSearchChange = this._onSearchChange.bind(this);
        this._onOptionChange = this._onOptionChange.bind(this);

        this.state = {
            searchQuery: '',
        }
    }

    componentWillMount() {
        if (!this.props.value) {
            // If no value is given, we start with the first
            // country selected, but our parent component
            // doesn't know this, therefore we do this.
            this.props.onOptionChange(COUNTRIES[0]);
        }
    }

    _onSearchChange(search) {
        this.setState({
            searchQuery: search,
        });
    }

    _onOptionChange(iso2) {
        this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
    }

    _flagImgForIso2(iso2) {
        // Unicode Regional Indicator Symbol letter 'A'
        const RIS_A = 0x1F1E6;
        const ASCII_A = 65;
        return charactersToImageNode(iso2, true,
            RIS_A + (iso2.charCodeAt(0) - ASCII_A),
            RIS_A + (iso2.charCodeAt(1) - ASCII_A),
        );
    }

    render() {
        const Dropdown = sdk.getComponent('elements.Dropdown');

        let displayedCountries;
        if (this.state.searchQuery) {
            displayedCountries = COUNTRIES.filter(
                countryMatchesSearchQuery.bind(this, this.state.searchQuery),
            );
            if (
                this.state.searchQuery.length == 2 &&
                COUNTRIES_BY_ISO2[this.state.searchQuery.toUpperCase()]
            ) {
                // exact ISO2 country name match: make the first result the matches ISO2
                const matched = COUNTRIES_BY_ISO2[this.state.searchQuery.toUpperCase()];
                displayedCountries = displayedCountries.filter((c) => {
                    return c.iso2 != matched.iso2;
                });
                displayedCountries.unshift(matched);
            }
        } else {
            displayedCountries = COUNTRIES;
        }

        const options = displayedCountries.map((country) => {
            return

                {this._flagImgForIso2(country.iso2)}
                {country.name}

;
        });

        // default value here too, otherwise we need to handle null / undefined
        // values between mounting and the initial value propgating
        const value = this.props.value || COUNTRIES[0].iso2;

        const getShortOption = this.props.isSmall ? this._flagImgForIso2 : undefined;

        return <Dropdown className={this.props.className}
            onOptionChange={this._onOptionChange} onSearchChange={this._onSearchChange}
            menuWidth={298} getShortOption={getShortOption}
            value={value} searchEnabled={true}
        >
            {options}
        </Dropdown>
    }
}

CountryDropdown.propTypes = {
    className: React.PropTypes.string,
    isSmall: React.PropTypes.bool,
    onOptionChange: React.PropTypes.func.isRequired,
    value: React.PropTypes.string,
};
<|endoftext|>


<commit_before>/**
 * 负责请求知乎日报 API，并返回结果。
 */

var _ = require("lodash");
var config = require("config");
var cheerio = require("cheerio");
var querystring = require("querystring");
var imageRequest = require("request");
var dailyRequest = require("request").defaults({
    baseUrl : config.zhihu_daily_api
});

var utils = require("./utils");
const PREFIX = "/api/4/imgs/";

/**
 * 获取最新知乎日报 ID 列表。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchLatestStoryIDs = function (p_callback)
{
    if (!_.isFunction(p_callback)) return;

    dailyRequest.get({ url: "/news/latest", json: true }, function (err, res, body)
    {
        if (!err && res.statusCode == 200)
        {
            // 因知乎日报的 API 返回的图片太小，这里直接丢弃，后面再通过其他途径获取图片。
            p_callback(null, {
                date: body.date,
                ids: body.stories.map(function (value)
                {
                    return value.id;
                })
            });
        }
        else
        {
            p_callback(new Error("request Zhihu-Daily API ('/news/latest') error."));
        }
    });
};

/**
 * 获取最新热门知乎日报 ID 列表。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchTopStoryIDs = function (p_callback)
{
    dailyRequest.get({ url: "/news/latest", json: true }, function (err, res, body)
    {
        if (!err && res.statusCode == 200)
        {
            var images = [];
            var stories = { date: body.date };
            stories.ids = body.top_stories.map(function (value)
            {
                images.push(value.image);
                return {
                    id: value.id,
                    title: value.title,
                    image: PREFIX + querystring.escape(value.image)
                };
            });
            p_callback(null, {
                stories: stories,
                images: images
            });
        }
        else
        {
            p_callback(new Error("request Zhihu-Daily API ('/news/latest') error."));
        }
    });
};

/**
 * 获取指定日期的知乎日报 ID 列表。
 * @param {String} p_date 日期。如果小于"20130519"，返回值 res 为 {}。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchStoryIDs = function (p_date, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 因知乎日报 API 返回的是指定日期的前一天的日报，
    // 所以要加一天才能获取指定日期的日报。
    var date = utils.nextZhihuDay(p_date);
    if (date)
    {
        if (utils.convertZhihuDateToMoment(date).isBefore(utils.MIN_DATE))
        {
            // "20130519"之前是没有知乎日报的。
            p_callback(null, {});
        }
        else
        {
            dailyRequest.get({ url: "/news/before/" + date, json: true }, function (err, res, body)
            {
                if (!err && res.statusCode == 200)
                {
                    p_callback(null, {
                        date: body.date,
                        ids: body.stories.map(function (value)
                        {
                            return value.id;
                        })
                    });
                }
                else
                {
                    p_callback(new Error("request Zhihu-Daily API ('/news/before/:date') error."));
                }
            });
        }
    }
    else
    {
        p_callback(new Error("p_date has a wrong format."));
    }
};

/**
 * 获取指定 ID 的知乎日报。
 * @param {String} p_id ID。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchStory = function (p_id, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 检查 ID 是否为纯数字。
    if (/^\d+$/.test(p_id))
    {
        dailyRequest.get({ url: "/news/" + p_id, json: true }, function (err, res, body)
        {
            if (!err && res.statusCode == 200)
            {
                var images = [];
                var story = {};
                story.id = body.id;
                story.title = body.title;

                images.push(body.image);
                story.image = PREFIX + querystring.escape(body.image);

                story.imageSource = body.image_source;
                story.shareURL = body.share_url;

                if (body.body)
                {
                    var $ = cheerio.load(body.body, { decodeEntities: false });
                    story.backgrounds = $(".headline>.headline-background .headline-background-link").map(function (i, e)
                    {
                        return {
                            href: $(e).attr("href"),
                            title : $(e).children(".heading").text(),
                            text : $(e).children(".heading-content").text()
                        };
                    }).get();

                    story.contents = $(".content-inner>.question").map(function (i, e)
                    {
                        var question = {};
                        question.title = $(e).children(".question-title").text();
                        question.answers = $(e).children(".answer").map(function (i, e)
                        {
                            $(e).find(".content img").each(function (i, e)
                            {
                                var src = $(e).attr("src");
                                if (!_.isEmpty(src))
                                {
                                    images.push(src);
                                    $(e).attr("src", PREFIX + querystring.escape(src));
                                }
                            });

                            // TODO: 图片暂时不入库，后面再说。
                            var avatar = $(e).find(".meta>.avatar").attr("src");
                            if (_.isEmpty(avatar))
                            {
                                avatar = "";
                            }
                            else
                            {
                                images.push(avatar);
                                avatar = PREFIX + querystring.escape(avatar);
                            }

                            return {
                                avatar : avatar,
                                name: $(e).find(".meta>.author").text(),
                                bio : $(e).find(".meta>.bio").text(),
                                content : $(e).children(".content").html()
                            };
                        }).get();

                        var a = $(e).find(".view-more>a");
                        if (a.length > 0)
                        {
                            question.link = {
                                href : a.attr("href"),
                                text : a.text(),
                            };
                        }

                        return question;
                    }).get();
                }

                p_callback(null, {
                    story: story,
                    images: images
                });
            }
            else
            {
                p_callback(new Error("request Zhihu-Daily API ('/news/:id') error."));
            }
        });
    }
    else
    {
        p_callback(new Error("p_id has a wrong format."));
    }
};

/**
 * 获取指定地址的图片。
 * @param {String} p_url 地址。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchImage = function (p_url, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    if (_.isEmpty(p_url))
    {
        p_callback(new Error("p_url must not be null."));
    }
    else
    {
        imageRequest({ url: p_url, encoding: null }, function (err, res, body)
        {
            if (!err && res.statusCode == 200)
            {
                p_callback(null, {
                    id: p_url,
                    contentType: res.headers["content-type"],
                    data: body
                });
            }
            else
            {
                p_callback(new Error("failed to request image."));
            }
        });
    }
};
<commit_msg>remove TODO<commit_after>/**
 * 负责请求知乎日报 API，并返回结果。
 */

var _ = require("lodash");
var config = require("config");
var cheerio = require("cheerio");
var querystring = require("querystring");
var imageRequest = require("request");
var dailyRequest = require("request").defaults({
    baseUrl : config.zhihu_daily_api
});

var utils = require("./utils");
const PREFIX = "/api/4/imgs/";

/**
 * 获取最新知乎日报 ID 列表。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchLatestStoryIDs = function (p_callback)
{
    if (!_.isFunction(p_callback)) return;

    dailyRequest.get({ url: "/news/latest", json: true }, function (err, res, body)
    {
        if (!err && res.statusCode == 200)
        {
            // 因知乎日报的 API 返回的图片太小，这里直接丢弃，后面再通过其他途径获取图片。
            p_callback(null, {
                date: body.date,
                ids: body.stories.map(function (value)
                {
                    return value.id;
                })
            });
        }
        else
        {
            p_callback(new Error("request Zhihu-Daily API ('/news/latest') error."));
        }
    });
};

/**
 * 获取最新热门知乎日报 ID 列表。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchTopStoryIDs = function (p_callback)
{
    dailyRequest.get({ url: "/news/latest", json: true }, function (err, res, body)
    {
        if (!err && res.statusCode == 200)
        {
            var images = [];
            var stories = { date: body.date };
            stories.ids = body.top_stories.map(function (value)
            {
                images.push(value.image);
                return {
                    id: value.id,
                    title: value.title,
                    image: PREFIX + querystring.escape(value.image)
                };
            });
            p_callback(null, {
                stories: stories,
                images: images
            });
        }
        else
        {
            p_callback(new Error("request Zhihu-Daily API ('/news/latest') error."));
        }
    });
};

/**
 * 获取指定日期的知乎日报 ID 列表。
 * @param {String} p_date 日期。如果小于"20130519"，返回值 res 为 {}。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchStoryIDs = function (p_date, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 因知乎日报 API 返回的是指定日期的前一天的日报，
    // 所以要加一天才能获取指定日期的日报。
    var date = utils.nextZhihuDay(p_date);
    if (date)
    {
        if (utils.convertZhihuDateToMoment(date).isBefore(utils.MIN_DATE))
        {
            // "20130519"之前是没有知乎日报的。
            p_callback(null, {});
        }
        else
        {
            dailyRequest.get({ url: "/news/before/" + date, json: true }, function (err, res, body)
            {
                if (!err && res.statusCode == 200)
                {
                    p_callback(null, {
                        date: body.date,
                        ids: body.stories.map(function (value)
                        {
                            return value.id;
                        })
                    });
                }
                else
                {
                    p_callback(new Error("request Zhihu-Daily API ('/news/before/:date') error."));
                }
            });
        }
    }
    else
    {
        p_callback(new Error("p_date has a wrong format."));
    }
};

/**
 * 获取指定 ID 的知乎日报。
 * @param {String} p_id ID。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchStory = function (p_id, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 检查 ID 是否为纯数字。
    if (/^\d+$/.test(p_id))
    {
        dailyRequest.get({ url: "/news/" + p_id, json: true }, function (err, res, body)
        {
            if (!err && res.statusCode == 200)
            {
                var images = [];
                var story = {};
                story.id = body.id;
                story.title = body.title;

                images.push(body.image);
                story.image = PREFIX + querystring.escape(body.image);

                story.imageSource = body.image_source;
                story.shareURL = body.share_url;

                if (body.body)
                {
                    var $ = cheerio.load(body.body, { decodeEntities: false });
                    story.backgrounds = $(".headline>.headline-background .headline-background-link").map(function (i, e)
                    {
                        return {
                            href: $(e).attr("href"),
                            title : $(e).children(".heading").text(),
                            text : $(e).children(".heading-content").text()
                        };
                    }).get();

                    story.contents = $(".content-inner>.question").map(function (i, e)
                    {
                        var question = {};
                        question.title = $(e).children(".question-title").text();
                        question.answers = $(e).children(".answer").map(function (i, e)
                        {
                            $(e).find(".content img").each(function (i, e)
                            {
                                var src = $(e).attr("src");
                                if (!_.isEmpty(src))
                                {
                                    images.push(src);
                                    $(e).attr("src", PREFIX + querystring.escape(src));
                                }
                            });

                            var avatar = $(e).find(".meta>.avatar").attr("src");
                            if (_.isEmpty(avatar))
                            {
                                avatar = "";
                            }
                            else
                            {
                                images.push(avatar);
                                avatar = PREFIX + querystring.escape(avatar);
                            }

                            return {
                                avatar : avatar,
                                name: $(e).find(".meta>.author").text(),
                                bio : $(e).find(".meta>.bio").text(),
                                content : $(e).children(".content").html()
                            };
                        }).get();

                        var a = $(e).find(".view-more>a");
                        if (a.length > 0)
                        {
                            question.link = {
                                href : a.attr("href"),
                                text : a.text(),
                            };
                        }

                        return question;
                    }).get();
                }

                p_callback(null, {
                    story: story,
                    images: images
                });
            }
            else
            {
                p_callback(new Error("request Zhihu-Daily API ('/news/:id') error."));
            }
        });
    }
    else
    {
        p_callback(new Error("p_id has a wrong format."));
    }
};

/**
 * 获取指定地址的图片。
 * @param {String} p_url 地址。
 * @param {Function(err, res)} [p_callback]
 */
exports.fetchImage = function (p_url, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    if (_.isEmpty(p_url))
    {
        p_callback(new Error("p_url must not be null."));
    }
    else
    {
        imageRequest({ url: p_url, encoding: null }, function (err, res, body)
        {
            if (!err && res.statusCode == 200)
            {
                p_callback(null, {
                    id: p_url,
                    contentType: res.headers["content-type"],
                    data: body
                });
            }
            else
            {
                p_callback(new Error("failed to request image."));
            }
        });
    }
};
<|endoftext|>


<commit_before>'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
	function($stateProvider, $urlRouterProvider) {
		// Redirect to home view when route not found
		$urlRouterProvider.otherwise('/');

		// Home state routing
		$stateProvider.
		state('home', {
			url: '/',
			templateUrl: 'modules/core/views/home.client.view.html'
		}).
                state('test', {
                        url: '/test',
                        templateUrl: 'modules/core/views/test.client.view.html'
                }).
		state('item', {
			url: '/item',
			templateUrl: 'modules/core/views/item.client.view.html'
		}).
		state('itemformswizard', {
			url: '/item-form',
			templateUrl: 'modules/core/views/form.item.client.view.html'
		}).
        	state('itemformswizard.step_one', {
        		url: '/item-form/step-one',
            		templateUrl: 'modules/core/views/wizard/step_one.html'
        	})
        	.state('itemformswizard.step_two', {
        		url: '/item-form/step-two',
        		 templateUrl: 'modules/core/views/wizard/step_two.html'
        	})
        	.state('itemformswizard.step_three', {
            		url: '/item-form/step-three',
            		templateUrl: 'modules/core/views/wizard/step_three.html'
        	}).
		state('promo', {
			url: '/promo',
			templateUrl: 'modules/core/views/promo.client.view.html'
		}).
		state('promoformswizard', {
			url: '/promo-form',
			templateUrl: 'modules/core/views/form.promo.client.view.html'
		}).
        	state('promoformswizard.step_one', {
        		url: '/promo-form/step-one',
            		templateUrl: 'modules/core/views/promo/step_one.html'
        	})
        	.state('promoformswizard.step_two', {
        		url: '/promo-form/step-two',
        		 templateUrl: 'modules/core/views/promo/step_two.html'
        	})
        	.state('promoformswizard.step_three', {
            		url: '/promo-form/step-three',
            		templateUrl: 'modules/core/views/promo/step_three.html'
        	})

	;
	}
]);
<commit_msg>Update core.client.routes.js<commit_after>'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
	function($stateProvider, $urlRouterProvider) {
		// Redirect to home view when route not found
		$urlRouterProvider.otherwise('/');

		// Home state routing
		$stateProvider.
		state('home', {
			url: '/',
			templateUrl: 'modules/core/views/home.client.view.html'
		}).
                state('test', {
                        url: '/test',
                        templateUrl: 'modules/core/views/test.client.view.html'
                }).
		state('item', {
			url: '/item',
			templateUrl: 'modules/core/views/item.client.view.html'
		}).
		state('itemformswizard', {
			url: '/item-form',
			templateUrl: 'modules/core/views/form.item.client.view.html'
		}).
        	state('itemformswizard.step_one', {
        		url: '/item-form/step-one',
            		templateUrl: 'modules/core/views/wizard/step_one.html'
        	})
        	.state('itemformswizard.step_two', {
        		url: '/item-form/step-two',
        		 templateUrl: 'modules/core/views/wizard/step_two.html'
        	})
        	.state('itemformswizard.step_three', {
            		url: '/item-form/step-three',
            		templateUrl: 'modules/core/views/wizard/step_three.html'
        	}).
		state('promo', {
			url: '/promo',
			templateUrl: 'modules/core/views/promo.client.view.html'
		}).
		state('promoformswizard', {
			url: '/promo-form',
			templateUrl: 'modules/core/views/form.promo.client.view.html'
		}).
        	state('promoformswizard.step_one', {
        		url: '/step-one',
            		templateUrl: 'modules/core/views/promo/step_one.html'
        	})
        	.state('promoformswizard.step_two', {
        		url: '/step-two',
        		 templateUrl: 'modules/core/views/promo/step_two.html'
        	})
        	.state('promoformswizard.step_three', {
            		url: '/step-three',
            		templateUrl: 'modules/core/views/promo/step_three.html'
        	})

	;
	}
]);
<|endoftext|>


<commit_before>/*
 * grunt-nuget
 * https://github.com/spatools/grunt-nuget
 * Copyright (c) 2013 SPA Tools
 * Code below is licensed under MIT License
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

module.exports = function (grunt) {
    var _ = grunt.util._,
        async = grunt.util.async,
        nuget = require("../libs/nuget")(grunt);

    grunt.registerMultiTask('nugetpack', "NuGet Pack - Create NuGet package", function () {
        var params = this.options(),
            done = this.async();

        async.forEach(
            this.files,
            function (file, callback) {
                var dest = file.dest || "";

                async.forEach(
                    file.src,
                    function (src, complete) {
                        nuget.pack(src, _.extend(params, { outputDirectory: dest }), complete);
                    },
                    callback
                );
            },
            function (err) {
                if (err) {
                    grunt.log.error().error(err);
                    done(false);
                    return;
                }

                grunt.log.ok("NuGet Packages created !");
                done();
            }
        );
    });
};
<commit_msg>Update nuget-pack to create dest dir<commit_after>/*
 * grunt-nuget
 * https://github.com/spatools/grunt-nuget
 * Copyright (c) 2013 SPA Tools
 * Code below is licensed under MIT License
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

module.exports = function (grunt) {
    var _ = grunt.util._,
        async = grunt.util.async,
        nuget = require("../libs/nuget")(grunt);

    grunt.registerMultiTask('nugetpack', "NuGet Pack - Create NuGet package", function () {
        var params = this.options(),
            done = this.async();

        async.forEach(
            this.files,
            function (file, callback) {
                var dest = file.dest || "";

                async.forEach(
                    file.src,
                    function (src, complete) {
                        grunt.file.mkdir(dest);
                        nuget.pack(src, _.extend(params, { outputDirectory: dest }), complete);
                    },
                    callback
                );
            },
            function (err) {
                if (err) {
                    grunt.log.error().error(err);
                    done(false);
                    return;
                }

                grunt.log.ok("NuGet Packages created !");
                done();
            }
        );
    });
};
<|endoftext|>