@@ -12,7 +12,11 @@ Hook.Plugin.OAuth.prototype.popup = function(provider, options) {
1212 var self = this ,
1313 href = this . client . url + "oauth/" + provider + "?X-App-Id=" + this . client . app_id + "&X-App-Key=" + this . client . key ,
1414 href_relay = this . client . url + "oauth/relay_frame" + "?X-App-Id=" + this . client . app_id + "&X-App-Key=" + this . client . key ,
15- deferred = when . defer ( ) ;
15+ deferred = when . defer ( ) ,
16+ window_width = 500 ,
17+ window_height = 500 ,
18+ window_left = ( screen . width / 2 ) - ( window_width / 2 ) ,
19+ window_top = ( screen . height / 2 ) - ( window_height / 2 ) ;
1620
1721 href += "&" + this . client . serialize ( { options : options } ) ;
1822
@@ -24,7 +28,7 @@ Hook.Plugin.OAuth.prototype.popup = function(provider, options) {
2428 WinChan . open ( {
2529 url : href ,
2630 relay_url : href_relay ,
27- window_features : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=700 ,height=375" ,
31+ window_features : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=" + window_width + " ,height=" + window_height + ",top=" + window_top + ",left=" + window_left
2832 } , function ( err , r ) {
2933 // err is a string on failure, otherwise r is the response object
3034
@@ -42,8 +46,313 @@ Hook.Plugin.OAuth.prototype.popup = function(provider, options) {
4246 } ) ;
4347 }
4448
49+
4550 return deferred . promise ;
4651} ;
4752
4853// Register plugin
4954Hook . Plugin . Manager . register ( 'oauth' , Hook . Plugin . OAuth ) ;
55+
56+
57+ /*
58+ *
59+ * winchan
60+ * https://github.com/mozilla/winchan
61+ *
62+ */
63+
64+ ; WinChan = ( function ( ) {
65+ var RELAY_FRAME_NAME = "__winchan_relay_frame" ;
66+ var CLOSE_CMD = "die" ;
67+
68+ // a portable addListener implementation
69+ function addListener ( w , event , cb ) {
70+ if ( w . attachEvent ) w . attachEvent ( 'on' + event , cb ) ;
71+ else if ( w . addEventListener ) w . addEventListener ( event , cb , false ) ;
72+ }
73+
74+ // a portable removeListener implementation
75+ function removeListener ( w , event , cb ) {
76+ if ( w . detachEvent ) w . detachEvent ( 'on' + event , cb ) ;
77+ else if ( w . removeEventListener ) w . removeEventListener ( event , cb , false ) ;
78+ }
79+
80+
81+ // checking for IE8 or above
82+ function isInternetExplorer ( ) {
83+ var rv = - 1 ; // Return value assumes failure.
84+ var ua = navigator . userAgent ;
85+ if ( navigator . appName === 'Microsoft Internet Explorer' ) {
86+ var re = new RegExp ( "MSIE ([0-9]{1,}[\.0-9]{0,})" ) ;
87+ if ( re . exec ( ua ) != null )
88+ rv = parseFloat ( RegExp . $1 ) ;
89+ }
90+ // IE > 11
91+ else if ( ua . indexOf ( "Trident" ) > - 1 ) {
92+ var re = new RegExp ( "rv:([0-9]{2,2}[\.0-9]{0,})" ) ;
93+ if ( re . exec ( ua ) !== null ) {
94+ rv = parseFloat ( RegExp . $1 ) ;
95+ }
96+ }
97+
98+ return rv >= 8 ;
99+ }
100+
101+ // checking Mobile Firefox (Fennec)
102+ function isFennec ( ) {
103+ try {
104+ // We must check for both XUL and Java versions of Fennec. Both have
105+ // distinct UA strings.
106+ var userAgent = navigator . userAgent ;
107+ return ( userAgent . indexOf ( 'Fennec/' ) != - 1 ) || // XUL
108+ ( userAgent . indexOf ( 'Firefox/' ) != - 1 && userAgent . indexOf ( 'Android' ) != - 1 ) ; // Java
109+ } catch ( e ) { }
110+ return false ;
111+ }
112+
113+ // feature checking to see if this platform is supported at all
114+ function isSupported ( ) {
115+ return ( window . JSON && window . JSON . stringify &&
116+ window . JSON . parse && window . postMessage ) ;
117+ }
118+
119+ // given a URL, extract the origin
120+ function extractOrigin ( url ) {
121+ if ( ! / ^ h t t p s ? : \/ \/ / . test ( url ) ) url = window . location . href ;
122+ var a = document . createElement ( 'a' ) ;
123+ a . href = url ;
124+ return a . protocol + "//" + a . host ;
125+ }
126+
127+ // find the relay iframe in the opener
128+ function findRelay ( ) {
129+ var loc = window . location ;
130+ var frames = window . opener . frames ;
131+ for ( var i = frames . length - 1 ; i >= 0 ; i -- ) {
132+ try {
133+ if ( frames [ i ] . location . protocol === window . location . protocol &&
134+ frames [ i ] . location . host === window . location . host &&
135+ frames [ i ] . name === RELAY_FRAME_NAME )
136+ {
137+ return frames [ i ] ;
138+ }
139+ } catch ( e ) { }
140+ }
141+ return ;
142+ }
143+
144+ var isIE = isInternetExplorer ( ) ;
145+
146+ if ( isSupported ( ) ) {
147+ /* General flow:
148+ * 0. user clicks
149+ * (IE SPECIFIC) 1. caller adds relay iframe (served from trusted domain) to DOM
150+ * 2. caller opens window (with content from trusted domain)
151+ * 3. window on opening adds a listener to 'message'
152+ * (IE SPECIFIC) 4. window on opening finds iframe
153+ * 5. window checks if iframe is "loaded" - has a 'doPost' function yet
154+ * (IE SPECIFIC5) 5a. if iframe.doPost exists, window uses it to send ready event to caller
155+ * (IE SPECIFIC5) 5b. if iframe.doPost doesn't exist, window waits for frame ready
156+ * (IE SPECIFIC5) 5bi. once ready, window calls iframe.doPost to send ready event
157+ * 6. caller upon reciept of 'ready', sends args
158+ */
159+ return {
160+ open : function ( opts , cb ) {
161+ if ( ! cb ) throw "missing required callback argument" ;
162+
163+ // test required options
164+ var err ;
165+ if ( ! opts . url ) err = "missing required 'url' parameter" ;
166+ if ( ! opts . relay_url ) err = "missing required 'relay_url' parameter" ;
167+ if ( err ) setTimeout ( function ( ) { cb ( err ) ; } , 0 ) ;
168+
169+ // supply default options
170+ if ( ! opts . window_name ) opts . window_name = null ;
171+ if ( ! opts . window_features || isFennec ( ) ) opts . window_features = undefined ;
172+
173+ // opts.params may be undefined
174+
175+ var iframe ;
176+
177+ // sanity check, are url and relay_url the same origin?
178+ var origin = extractOrigin ( opts . url ) ;
179+ if ( origin !== extractOrigin ( opts . relay_url ) ) {
180+ return setTimeout ( function ( ) {
181+ cb ( 'invalid arguments: origin of url and relay_url must match' ) ;
182+ } , 0 ) ;
183+ }
184+
185+ var messageTarget ;
186+
187+ if ( isIE ) {
188+ // first we need to add a "relay" iframe to the document that's served
189+ // from the target domain. We can postmessage into a iframe, but not a
190+ // window
191+ iframe = document . createElement ( "iframe" ) ;
192+ // iframe.setAttribute('name', framename);
193+ iframe . setAttribute ( 'src' , opts . relay_url ) ;
194+ iframe . style . display = "none" ;
195+ iframe . setAttribute ( 'name' , RELAY_FRAME_NAME ) ;
196+ document . body . appendChild ( iframe ) ;
197+ messageTarget = iframe . contentWindow ;
198+ }
199+
200+ var w = window . open ( opts . url , opts . window_name , opts . window_features ) ;
201+
202+ if ( ! messageTarget ) messageTarget = w ;
203+
204+ // lets listen in case the window blows up before telling us
205+ var closeInterval = setInterval ( function ( ) {
206+ if ( w && w . closed ) {
207+ cleanup ( ) ;
208+ if ( cb ) {
209+ cb ( 'unknown closed window' ) ;
210+ cb = null ;
211+ }
212+ }
213+ } , 500 ) ;
214+
215+ var req = JSON . stringify ( { a : 'request' , d : opts . params } ) ;
216+
217+ // cleanup on unload
218+ function cleanup ( ) {
219+ if ( iframe ) document . body . removeChild ( iframe ) ;
220+ iframe = undefined ;
221+ if ( closeInterval ) closeInterval = clearInterval ( closeInterval ) ;
222+ removeListener ( window , 'message' , onMessage ) ;
223+ removeListener ( window , 'unload' , cleanup ) ;
224+ if ( w ) {
225+ try {
226+ w . close ( ) ;
227+ } catch ( securityViolation ) {
228+ // This happens in Opera 12 sometimes
229+ // see https://github.com/mozilla/browserid/issues/1844
230+ messageTarget . postMessage ( CLOSE_CMD , origin ) ;
231+ }
232+ }
233+ w = messageTarget = undefined ;
234+ }
235+
236+ addListener ( window , 'unload' , cleanup ) ;
237+
238+ function onMessage ( e ) {
239+ if ( e . origin !== origin ) { return ; }
240+ try {
241+ var d = JSON . parse ( e . data ) ;
242+ if ( d . a === 'ready' ) messageTarget . postMessage ( req , origin ) ;
243+ else if ( d . a === 'error' ) {
244+ cleanup ( ) ;
245+ if ( cb ) {
246+ cb ( d . d ) ;
247+ cb = null ;
248+ }
249+ } else if ( d . a === 'response' ) {
250+ cleanup ( ) ;
251+ if ( cb ) {
252+ cb ( null , d . d ) ;
253+ cb = null ;
254+ }
255+ }
256+ } catch ( err ) { }
257+ }
258+
259+ addListener ( window , 'message' , onMessage ) ;
260+
261+ return {
262+ close : cleanup ,
263+ focus : function ( ) {
264+ if ( w ) {
265+ try {
266+ w . focus ( ) ;
267+ } catch ( e ) {
268+ // IE7 blows up here, do nothing
269+ }
270+ }
271+ }
272+ } ;
273+ } ,
274+ onOpen : function ( cb ) {
275+ var o = "*" ;
276+ var msgTarget = isIE ? findRelay ( ) : window . opener ;
277+ if ( ! msgTarget ) throw "can't find relay frame" ;
278+ function doPost ( msg ) {
279+ msg = JSON . stringify ( msg ) ;
280+ if ( isIE ) msgTarget . doPost ( msg , o ) ;
281+ else msgTarget . postMessage ( msg , o ) ;
282+ }
283+
284+ function onMessage ( e ) {
285+ // only one message gets through, but let's make sure it's actually
286+ // the message we're looking for (other code may be using
287+ // postmessage) - we do this by ensuring the payload can
288+ // be parsed, and it's got an 'a' (action) value of 'request'.
289+ var d ;
290+ try {
291+ d = JSON . parse ( e . data ) ;
292+ } catch ( err ) { }
293+ if ( ! d || d . a !== 'request' ) return ;
294+ removeListener ( window , 'message' , onMessage ) ;
295+ o = e . origin ;
296+ if ( cb ) {
297+ // this setTimeout is critically important for IE8 -
298+ // in ie8 sometimes addListener for 'message' can synchronously
299+ // cause your callback to be invoked. awesome.
300+ setTimeout ( function ( ) {
301+ cb ( o , d . d , function ( r ) {
302+ cb = undefined ;
303+ doPost ( { a : 'response' , d : r } ) ;
304+ } ) ;
305+ } , 0 ) ;
306+ }
307+ }
308+
309+ function onDie ( e ) {
310+ if ( e . data === CLOSE_CMD ) {
311+ try { window . close ( ) ; } catch ( o_O ) { }
312+ }
313+ }
314+ addListener ( isIE ? msgTarget : window , 'message' , onMessage ) ;
315+ addListener ( isIE ? msgTarget : window , 'message' , onDie ) ;
316+
317+ // we cannot post to our parent that we're ready before the iframe
318+ // is loaded. (IE specific possible failure)
319+ try {
320+ doPost ( { a : "ready" } ) ;
321+ } catch ( e ) {
322+ // this code should never be exectued outside IE
323+ addListener ( msgTarget , 'load' , function ( e ) {
324+ doPost ( { a : "ready" } ) ;
325+ } ) ;
326+ }
327+
328+ // if window is unloaded and the client hasn't called cb, it's an error
329+ var onUnload = function ( ) {
330+ try {
331+ // IE8 doesn't like this...
332+ removeListener ( isIE ? msgTarget : window , 'message' , onDie ) ;
333+ } catch ( ohWell ) { }
334+ if ( cb ) doPost ( { a : 'error' , d : 'client closed window' } ) ;
335+ cb = undefined ;
336+ // explicitly close the window, in case the client is trying to reload or nav
337+ try { window . close ( ) ; } catch ( e ) { }
338+ } ;
339+ addListener ( window , 'unload' , onUnload ) ;
340+ return {
341+ detach : function ( ) {
342+ removeListener ( window , 'unload' , onUnload ) ;
343+ }
344+ } ;
345+ }
346+ } ;
347+ } else {
348+ return {
349+ open : function ( url , winopts , arg , cb ) {
350+ setTimeout ( function ( ) { cb ( "unsupported browser" ) ; } , 0 ) ;
351+ } ,
352+ onOpen : function ( cb ) {
353+ setTimeout ( function ( ) { cb ( "unsupported browser" ) ; } , 0 ) ;
354+ }
355+ } ;
356+ }
357+ } ) ( ) ;
358+
0 commit comments