| 'use strict'; |
| |
| // Few cool transports do work only for same-origin. In order to make |
| // them work cross-domain we shall use iframe, served from the |
| // remote domain. New browsers have capabilities to communicate with |
| // cross domain iframe using postMessage(). In IE it was implemented |
| // from IE 8+, but of course, IE got some details wrong: |
| // http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx |
| // http://stevesouders.com/misc/test-postmessage.php |
| |
| var inherits = require('inherits') |
| , JSON3 = require('json3') |
| , EventEmitter = require('events').EventEmitter |
| , version = require('../version') |
| , urlUtils = require('../utils/url') |
| , iframeUtils = require('../utils/iframe') |
| , eventUtils = require('../utils/event') |
| , random = require('../utils/random') |
| ; |
| |
| var debug = function() {}; |
| if (process.env.NODE_ENV !== 'production') { |
| debug = require('debug')('sockjs-client:transport:iframe'); |
| } |
| |
| function IframeTransport(transport, transUrl, baseUrl) { |
| if (!IframeTransport.enabled()) { |
| throw new Error('Transport created when disabled'); |
| } |
| EventEmitter.call(this); |
| |
| var self = this; |
| this.origin = urlUtils.getOrigin(baseUrl); |
| this.baseUrl = baseUrl; |
| this.transUrl = transUrl; |
| this.transport = transport; |
| this.windowId = random.string(8); |
| |
| var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId; |
| debug(transport, transUrl, iframeUrl); |
| |
| this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) { |
| debug('err callback'); |
| self.emit('close', 1006, 'Unable to load an iframe (' + r + ')'); |
| self.close(); |
| }); |
| |
| this.onmessageCallback = this._message.bind(this); |
| eventUtils.attachEvent('message', this.onmessageCallback); |
| } |
| |
| inherits(IframeTransport, EventEmitter); |
| |
| IframeTransport.prototype.close = function() { |
| debug('close'); |
| this.removeAllListeners(); |
| if (this.iframeObj) { |
| eventUtils.detachEvent('message', this.onmessageCallback); |
| try { |
| // When the iframe is not loaded, IE raises an exception |
| // on 'contentWindow'. |
| this.postMessage('c'); |
| } catch (x) { |
| // intentionally empty |
| } |
| this.iframeObj.cleanup(); |
| this.iframeObj = null; |
| this.onmessageCallback = this.iframeObj = null; |
| } |
| }; |
| |
| IframeTransport.prototype._message = function(e) { |
| debug('message', e.data); |
| if (!urlUtils.isOriginEqual(e.origin, this.origin)) { |
| debug('not same origin', e.origin, this.origin); |
| return; |
| } |
| |
| var iframeMessage; |
| try { |
| iframeMessage = JSON3.parse(e.data); |
| } catch (ignored) { |
| debug('bad json', e.data); |
| return; |
| } |
| |
| if (iframeMessage.windowId !== this.windowId) { |
| debug('mismatched window id', iframeMessage.windowId, this.windowId); |
| return; |
| } |
| |
| switch (iframeMessage.type) { |
| case 's': |
| this.iframeObj.loaded(); |
| // window global dependency |
| this.postMessage('s', JSON3.stringify([ |
| version |
| , this.transport |
| , this.transUrl |
| , this.baseUrl |
| ])); |
| break; |
| case 't': |
| this.emit('message', iframeMessage.data); |
| break; |
| case 'c': |
| var cdata; |
| try { |
| cdata = JSON3.parse(iframeMessage.data); |
| } catch (ignored) { |
| debug('bad json', iframeMessage.data); |
| return; |
| } |
| this.emit('close', cdata[0], cdata[1]); |
| this.close(); |
| break; |
| } |
| }; |
| |
| IframeTransport.prototype.postMessage = function(type, data) { |
| debug('postMessage', type, data); |
| this.iframeObj.post(JSON3.stringify({ |
| windowId: this.windowId |
| , type: type |
| , data: data || '' |
| }), this.origin); |
| }; |
| |
| IframeTransport.prototype.send = function(message) { |
| debug('send', message); |
| this.postMessage('m', message); |
| }; |
| |
| IframeTransport.enabled = function() { |
| return iframeUtils.iframeEnabled; |
| }; |
| |
| IframeTransport.transportName = 'iframe'; |
| IframeTransport.roundTrips = 2; |
| |
| module.exports = IframeTransport; |