diff --git a/oauth_patch.js b/oauth_patch.js deleted file mode 100644 index cefcc18..0000000 --- a/oauth_patch.js +++ /dev/null @@ -1,68 +0,0 @@ -// Meteor's OAuth flow currently only works with popups. Phonegap does -// not handle this well. Using the InAppBrowser plugin we can load the -// OAuth popup into it. Using the plugin by itself would not work with -// the MeteorRider phonegap method, this fixes it. This has not been -// tested on other Meteor phonegap methods. (tested on PG 3.3, android,iOS) -// -// http://docs.phonegap.com/en/3.3.0/cordova_inappbrowser_inappbrowser.md.html -// https://github.com/zeroasterisk/MeteorRider - -var __open = window.open, - oauthWin, - timer; - -// Create an object to return from a monkeypatched window.open call. Handles -// open/closed state of popup to keep Meteor happy. Allows one to save window -// referrence to a variable and close it later. e.g., -// `var foo = window.open('url'); foo.close(); -// -window.IAB = { - closed: true, - - open: function(url) { - var self = this; - - // TODO add options param and append to current options - oauthWin = __open(url, '_blank', 'location=no,hidden=yes'); - - oauthWin.addEventListener('loadstop', checkIfOauthIsDone); - - // use hidden=yes as a hack for Android, allows popup to yield events with - // each #show call. Lets events run on Meteor app, otherwise all listeners - // will *only* run when tapping done button or oauthWin.close - // - // FIXME should be a better way to do this... - if (device.platform === 'Android') { - timer = setInterval(oauthWin.show, 200); - } else { - oauthWin.show(); - } - - // check if uri contains an error or code param, then manually close popup - function checkIfOauthIsDone(event) { - if (!event.url || !event.url.match(/error|code=/)) return; - - clearInterval(timer); - oauthWin.removeEventListener('loadstop', checkIfOauthIsDone); - self.close(); - } - - this.closed = false; - }, - - close: function() { - if (!oauthWin) return; - oauthWin.close(); - this.closed = true; - } -}; - -// monkeypatch window.open on the phonegap platform -if (typeof device !== "undefined") { - window.open = function(url) { - IAB.open(url); - // return InAppBrowser so you can set foo = open(...) and then foo.close() - return IAB; - }; -} - diff --git a/package.js b/package.js index 91767d2..ef87d1b 100644 --- a/package.js +++ b/package.js @@ -1,8 +1,11 @@ Package.describe({ - summary: 'A fix for Meteor OAuth with Phonegap' + summary: 'A fix for Meteor OAuth with Phonegap' }); Package.on_use(function (api) { - api.add_files(["oauth_patch.js"], "client"); -}); + api.use(['logging', 'oauth'], 'server'); + api.use(['oauth', 'accounts-oauth'], 'client'); + api.add_files('patch_window.js', 'client'); + api.add_files('patch_login_response.js', 'server'); +}); \ No newline at end of file diff --git a/patch_login_response.js b/patch_login_response.js new file mode 100644 index 0000000..6355fd6 --- /dev/null +++ b/patch_login_response.js @@ -0,0 +1,42 @@ +// Patch OAuth._endOfLoginResponse to expose the credentialToken and +// credentialSecret in the url for the InAppBrowser to retrieve. +// See https://groups.google.com/forum/#!topic/meteor-core/Ma3XTZk4Kqg and +// https://github.com/meteor/meteor/blob/3dfcb42eac34f40b6611a0a0a780b9210467ccb2/packages/oauth/oauth_server.js#L219-L255 +OAuth._endOfLoginResponse = function (res, details) { + res.writeHead(200, {'Content-Type': 'text/html'}); + + var content = function (setCredentialSecret) { + return '
'; + }; + + if (details.error) { + Log.warn("Error in OAuth Server: " + + (details.error instanceof Error ? + details.error.message : details.error)); + res.end(content(""), 'utf-8'); + return; + } + + if ("close" in details.query) { + // If we have a credentialSecret, report it back to the parent + // window, with the corresponding credentialToken. The parent window + // uses the credentialToken and credentialSecret to log in over DDP. + var setCredentialSecret = ''; + if (details.credentials.token && details.credentials.secret) { + setCredentialSecret = 'var credentialToken = ' + + JSON.stringify(details.credentials.token) + ';' + + 'var credentialSecret = ' + + JSON.stringify(details.credentials.secret) + ';' + + 'if (window.opener) window.opener.Package.oauth.OAuth._handleCredentialSecret(' + + 'credentialToken, credentialSecret);' + + // add the token and secret to the hash + 'else window.location.hash = "credentialToken=" + credentialToken + ' + + '"&credentialSecret=" + credentialSecret;'; + } + res.end(content(setCredentialSecret), "utf-8"); + } else { + res.end("", "utf-8"); + } +}; \ No newline at end of file diff --git a/patch_window.js b/patch_window.js new file mode 100644 index 0000000..1c83b5b --- /dev/null +++ b/patch_window.js @@ -0,0 +1,112 @@ +// Meteor's OAuth flow currently only works with popups. Phonegap does +// not handle this well. Using the InAppBrowser plugin we can load the +// OAuth popup into it. Using the plugin by itself would not work with +// the MeteorRider phonegap method, this fixes it. This has not been +// tested on other Meteor phonegap methods. (tested on PG 3.3, android, iOS) +// +// http://docs.phonegap.com/en/3.3.0/cordova_inappbrowser_inappbrowser.md.html +// https://github.com/zeroasterisk/MeteorRider +window.patchWindow = function () { + // Prevent the window from being patched twice. + if (window.IAB) return; + + // Make sure the InAppBrowser is loaded before patching the window. + try { + window.cordova.require('org.apache.cordova.inappbrowser.inappbrowser'); + } catch (e) { + return false; + } + + // Plugin messages are not processed on Android until the next + // message this prevents the oauthWin event listeners from firing. + // Call exec on an interval to force process messages. + // http://stackoverflow.com/q/23352940/230462 and + // http://stackoverflow.com/a/24319063/230462 + if (device.platform === 'Android') { + setInterval(function () { + cordova.exec(null, null, '', '', []) + }, 200); + } + + // Keep a reference to the in app browser's window.open. + var __open = window.open, + oauthWin; + + // Create an object to return from a monkeypatched window.open call. Handles + // open/closed state of popup to keep Meteor happy. Allows one to save window + // reference to a variable and close it later. e.g., + // `var foo = window.open('url'); foo.close(); + window.IAB = { + closed: true, + + open: function (url) { + // XXX add options param and append to current options + oauthWin = __open(url, '_blank', 'location=no,hidden=yes'); + + oauthWin.addEventListener('loadstop', checkIfOauthIsDone); + + // Close the InAppBrowser on exit -- triggered when the + // user goes back when there are not pages in the history. + oauthWin.addEventListener('exit', close); + + oauthWin.show(); + + function close() { + // close the window + IAB.close(); + + // remove the listeners + oauthWin.removeEventListener('loadstop', checkIfOauthIsDone); + oauthWin.removeEventListener('exit', close); + } + + // check if uri contains an error or code param, then manually close popup + function checkIfOauthIsDone(event) { + // if this is the oauth prompt url, we are not done + if (url === event.url) return; + + if (!event.url || !event.url.match(/close|error|code=/)) return; + + if (event.url.indexOf('credentialToken') > -1) { + // Get the credentialToken and credentialSecret from the InAppBrowser's url hash. + var hashes = event.url.slice(event.url.indexOf('#') + 1).split('&'); + var credentialToken = hashes[0].split('=')[1]; + + if (event.url.indexOf('credentialSecret') > -1) { + var credentialSecret = hashes[1].split('=')[1]; + OAuth._handleCredentialSecret(credentialToken, credentialSecret); + } + + Accounts.oauth.tryLoginAfterPopupClosed(credentialToken); + } + + close(); + } + + this.closed = false; + }, + + close: function () { + if (!oauthWin) return; + + oauthWin.close(); + + this.closed = true; + } + }; + + // monkeypatch window.open on the phonegap platform + if (typeof device !== "undefined") { + window.open = function (url) { + IAB.open(url); + // return InAppBrowser so you can set foo = open(...) and then foo.close() + return IAB; + }; + } + + return true; +}; + +// Patch the window after cordova is finished loading +// if the InAppBrowser is not available yet. +if (!window.patchWindow()) document.addEventListener('deviceready', window.patchWindow, false);