/* eslint-env browser */
/* globals cloneInto, createObjectIn, exportFunction, unsafeWindow */
"use strict";

const DEFAULT_TIMEOUT_SECONDS = 30;

var nextCallbackID = 0;

function sendToChrome(msg, callback, timeout) {
  var origin = document.location.origin;
  var callbackID = nextCallbackID++;

  timeout = 1000 * (timeout || DEFAULT_TIMEOUT_SECONDS);
  var timer = setTimeout(function() {
    var value = cloneInto({errorCode: 5}, document.defaultView);
    callback(value);
    timer = null;
  }, timeout);

  self.port.once("U2FRequestResponse", function onResponse(id, response) {
    if (id != callbackID || !timer) {
      return;
    }

    if (response.errorMessage)
      console.info("U2F error response:", response.errorMessage);

    delete response.errorMessage;

    var value = cloneInto(response, document.defaultView);

    try {
      callback(value);
      clearTimeout(timer);
      timer = null;
    } catch (ex) {
      console.info(ex + "");
    }
  });

  self.port.emit("U2FRequest", msg, callbackID, origin, timeout);
}

function cloneFunctions(obj, clone) {
  for (var i in obj) {
    if (!obj.hasOwnProperty(i))
      continue;
    else if (typeof obj[i] == "function")
      exportFunction(obj[i], clone, {
        defineAs: i
      });
    else if (typeof obj[i] == "object")
      cloneFunctions(obj[i], clone[i]);
  }
}

var u2f = {
  register: function(requests, signRequests, callback, timeout) {
    if (typeof(timeout) == "function" && typeof(callback) != "function") {
      let appId, keys;
      [appId, requests, keys, callback, timeout] = Array.from(arguments);
      Array.forEach(requests, v => v.appId = appId);
      signRequests = Array.map(keys, v => ({
        version: v.version,
        challenge: requests[0].challenge,
        keyHandle: v.keyHandle,
        appId: appId
      }));
    }

    sendToChrome({
      type: "register",
      requests: requests,
      signRequests: signRequests
    }, callback, timeout);
  },

  sign: function(signRequests, callback, timeout, extra) {
    if (typeof(extra) == "function" && typeof(callback) != "function") {
      let appId, challenge, keys;
      [appId, challenge, keys, callback, timeout] = Array.from(arguments);
      signRequests = Array.map(keys, v => ({
        version: v.version,
        challenge: challenge,
        keyHandle: v.keyHandle,
        appId: appId
      }));
    }

    sendToChrome({
      type: "sign",
      signRequests: signRequests
    }, callback, timeout);
  }
};

var u2fOnPage = createObjectIn(unsafeWindow, {
  defineAs: "u2f"
});
cloneFunctions(u2f, u2fOnPage);

Object.freeze(u2fOnPage);

@@ -165,16 +165,74 @@
 D = 0;%0A%0A
+var noopOnPage = exportFunction(() =%3E %7B%7D, unsafeWindow);%0A%0A
 function
@@ -1146,124 +1146,199 @@
 %7B%0A  
-for (var i in obj) %7B%0A    if (!obj.hasOwnProperty(i))%0A      continue;%0A    else if (typeof obj%5Bi%5D == %22funct
+Object.getOwnPropertyNames(obj).forEach(i =%3E %7B%0A    if (typeof obj%5Bi%5D == %22function%22) %7B%0A      // instead of freezing the clone use accessor property to allow further extens
 ion
-%22)
 %0A     
+ let value =
  exp
@@ -1366,31 +1366,240 @@
 lone
-, %7B%0A        defineAs: i
+);%0A      let getter = exportFunction(() =%3E %7B%0A        return value;%0A      %7D, clone);%0A      Object.defineProperty(clone, i, %7B%0A        get: getter,%0A        set: noopOnPage // readonly: silently avoid strict mode TypeError on assignment
 %0A   
@@ -1608,16 +1608,18 @@
  %7D);%0A   
+ %7D
  else if
@@ -1646,16 +1646,18 @@
 object%22)
+ %7B
 %0A      c
@@ -1691,17 +1691,25 @@
 %5Bi%5D);%0A  
-%7D
+  %7D%0A  %7D);
 %0A%7D%0A%0Avar 
@@ -2955,31 +2955,4 @@
 e);%0A
-%0AObject.freeze(u2fOnPage);%0A

Allow extensions to the exported u2f object