diff --git a/oauth3.implicit.js b/oauth3.implicit.js
index fbcbc89..4494500 100644
--- a/oauth3.implicit.js
+++ b/oauth3.implicit.js
@@ -251,7 +251,7 @@
       ).then(function (params) {
         // discWin.child.close()
         // TODO params should have response_type indicating json, binary, etc
-        var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils.urlSafeBase64ToBase64(params.result || params.directives)));
+        var directives = JSON.parse(OAUTH3.utils.atob(OAUTH3.utils._urlSafeBase64ToBase64(params.result || params.directives)));
         return OAUTH3.hooks.directives.set(providerUri, directives);
       });
     }
@@ -310,7 +310,6 @@
             xhr = new XMLHttpRequest();
           }
           xhr.onreadystatechange = function () {
-            console.error('state change');
             var data;
             if (xhr.readyState !== XMLHttpRequest.DONE) {
               // nothing to do here
@@ -365,7 +364,10 @@
         );
 
         // TODO ability to reuse iframe instead of closing
-        return OAUTH3._browser._iframe.insert(discObj.url, discObj.state, opts).then(function (params) {
+        opts._windowType = opts.windowType;
+        opts.windowType = opts.windowType || 'background';
+        return OAUTH3._browser.frameRequest(discObj.url, discObj.state, opts).then(function (params) {
+          opts.windowType = opts._windowType;
           OAUTH3._browser.closeFrame(discObj.state, { debug: opts.debug || params.debug });
           if (params.error) {
             return OAUTH3.utils._formatError(providerUri, params.error);
@@ -378,47 +380,67 @@
         });
       }
     , frameRequest: function (url, state, opts) {
+        opts = opts || {};
         var previousFrame = OAUTH3._browser._frames[state];
 
-        if (!opts.windowType) {
-          opts.windowType = 'popup';
+        var windowType = opts.windowType;
+        if (!windowType) {
+          windowType = 'popup';
         }
 
-        opts = opts || {};
+        var timeout = opts.timeout;
         if (opts.debug) {
-          opts.timeout = opts.timeout || 15 * 60 * 1000;
+          timeout = timeout || 3 * 60 * 1000;
+        }
+        else {
+          timeout = timeout || ('background' === windowType ? 15 * 1000 : 3 * 60 * 1000);
         }
 
         return new OAUTH3.PromiseA(function (resolve, reject) {
+          // TODO periodically garbage collect expired handlers from window object
           var tok;
 
           function cleanup() {
             delete window['--oauth3-callback-' + state];
             clearTimeout(tok);
             tok = null;
+            // the actual close is done later (by the caller) so that the window/frame
+            // can be reused or self-closes synchronously itself / by parent
+            // (probably won't ever happen, but that's a negotiable implementation detail)
           }
 
+
+          console.log('[oauth3.implicit.js] callbackName', '--oauth3-callback-' + state);
           window['--oauth3-callback-' + state] = function (params) {
             resolve(params);
             cleanup();
           };
 
           tok = setTimeout(function () {
-            var err = new Error("the iframe request did not complete within 15 seconds");
+            var err = new Error(
+              "the '" + windowType + "' request did not complete within " + Math.round(timeout / 1000) + "s"
+            );
             err.code = "E_TIMEOUT";
             reject(err);
             cleanup();
-          }, opts.timeout || 15 * 1000);
+          }, timeout);
 
-          if ('background' === opts.windowType) {
+          setTimeout(function () {
+            if (!OAUTH3._browser._frames[state]) {
+              reject(new Error("TODO: open the iframe first and discover oauth3 directives before popup"));
+              cleanup();
+            }
+          }, 0);
+
+          if ('background' === windowType) {
             if (previousFrame) {
               previousFrame.location = url;
               //promise = previousFrame.promise;
             }
             else {
-              OAUTH3._browser._iframe.insert(url, state, opts);
+              OAUTH3._browser._frames[state] = OAUTH3._browser.iframe(url, state, opts);
             }
-          } else if ('popup' === opts.windowType) {
+          } else if ('popup' === windowType) {
             if (previousFrame) {
               previousFrame.location = url;
               if (opts.debug) {
@@ -426,9 +448,9 @@
               }
             }
             else {
-              OAUTH3._browser.frame.open(url, state, opts);
+              OAUTH3._browser._frames[state] = OAUTH3._browser.frame(url, state, opts);
             }
-          } else if ('inline' === opts.windowType) {
+          } else if ('inline' === windowType) {
             // callback function will never execute and would need to redirect back to current page
             // rather than the callback.html
             url += '&original_url=' + OAUTH3._browser.window.location.href;
@@ -478,72 +500,32 @@
         }
       }
     , _frames: {}
-    , iframe: {
-        insert: function (url, state, opts) {
-          // TODO hidden / non-hidden (via directive even)
-          var framesrc = '';
-
-          var frame = OAUTH3._browser._frames[state] = OAUTH3._browser.window.document.createElement('div');
-          OAUTH3._browser._frames[state].innerHTML = framesrc;
-          OAUTH3._browser.window.document.body.appendChild(OAUTH3._browser._frames[state]);
-
-          return frame;
+    , iframe: function (url, state, opts) {
+        var framesrc = '';
+
+        var frame = OAUTH3._browser.window.document.createElement('div');
+        frame.innerHTML = framesrc;
+        OAUTH3._browser.window.document.body.appendChild(frame);
+
+        return frame;
       }
-    , frame: {
-        open: function (url, state, opts) {
-          if (opts.debug) {
-            opts.timeout = opts.timeout || 15 * 60 * 1000;
-          }
+    , frame: function (url, state, opts) {
 
-          var promise = new OAUTH3.PromiseA(function (resolve, reject) {
-            var tok;
-
-            function cleanup() {
-              clearTimeout(tok);
-              tok = null;
-              delete window['--oauth3-callback-' + state];
-              // close is done later in case the window is reused or self-closes synchronously itself / by parent
-              // (probably won't ever happen, but that's a negotiable implementation detail)
-            }
-
-            window['--oauth3-callback-' + state] = function (params) {
-              console.log('YOLO!!');
-              resolve(params);
-              cleanup();
-            };
-
-            tok = setTimeout(function () {
-              var err = new Error("the windowed request did not complete within 3 minutes");
-              err.code = "E_TIMEOUT";
-              reject(err);
-              cleanup();
-            }, opts.timeout || 3 * 60 * 1000);
-
-            setTimeout(function () {
-              if (!promise.child) {
-                reject("TODO: open the iframe first and discover oauth3 directives before popup");
-                cleanup();
-              }
-            }, 0);
-          });
-
-          // TODO allow size changes (via directive even)
-          OAUTH3._browser._frames[state] = window.open(
-            url
-          , 'oauth3-login-' + (opts.reuseWindow || state)
-          , 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620)
-          );
-          // TODO periodically garbage collect expired handlers from window object
-          return promise;
-        }
+        // TODO allow size changes (via directive even)
+        return window.open(
+          url
+        , 'oauth3-login-' + (opts.reuseWindow || state)
+        , 'height=' + (opts.height || 720) + ',width=' + (opts.width || 620)
+        );
       }
     }
   };