1 | 'use strict';
|
---|
2 |
|
---|
3 | // Few cool transports do work only for same-origin. In order to make
|
---|
4 | // them work cross-domain we shall use iframe, served from the
|
---|
5 | // remote domain. New browsers have capabilities to communicate with
|
---|
6 | // cross domain iframe using postMessage(). In IE it was implemented
|
---|
7 | // from IE 8+, but of course, IE got some details wrong:
|
---|
8 | // http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx
|
---|
9 | // http://stevesouders.com/misc/test-postmessage.php
|
---|
10 |
|
---|
11 | var inherits = require('inherits')
|
---|
12 | , JSON3 = require('json3')
|
---|
13 | , EventEmitter = require('events').EventEmitter
|
---|
14 | , version = require('../version')
|
---|
15 | , urlUtils = require('../utils/url')
|
---|
16 | , iframeUtils = require('../utils/iframe')
|
---|
17 | , eventUtils = require('../utils/event')
|
---|
18 | , random = require('../utils/random')
|
---|
19 | ;
|
---|
20 |
|
---|
21 | var debug = function() {};
|
---|
22 | if (process.env.NODE_ENV !== 'production') {
|
---|
23 | debug = require('debug')('sockjs-client:transport:iframe');
|
---|
24 | }
|
---|
25 |
|
---|
26 | function IframeTransport(transport, transUrl, baseUrl) {
|
---|
27 | if (!IframeTransport.enabled()) {
|
---|
28 | throw new Error('Transport created when disabled');
|
---|
29 | }
|
---|
30 | EventEmitter.call(this);
|
---|
31 |
|
---|
32 | var self = this;
|
---|
33 | this.origin = urlUtils.getOrigin(baseUrl);
|
---|
34 | this.baseUrl = baseUrl;
|
---|
35 | this.transUrl = transUrl;
|
---|
36 | this.transport = transport;
|
---|
37 | this.windowId = random.string(8);
|
---|
38 |
|
---|
39 | var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId;
|
---|
40 | debug(transport, transUrl, iframeUrl);
|
---|
41 |
|
---|
42 | this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) {
|
---|
43 | debug('err callback');
|
---|
44 | self.emit('close', 1006, 'Unable to load an iframe (' + r + ')');
|
---|
45 | self.close();
|
---|
46 | });
|
---|
47 |
|
---|
48 | this.onmessageCallback = this._message.bind(this);
|
---|
49 | eventUtils.attachEvent('message', this.onmessageCallback);
|
---|
50 | }
|
---|
51 |
|
---|
52 | inherits(IframeTransport, EventEmitter);
|
---|
53 |
|
---|
54 | IframeTransport.prototype.close = function() {
|
---|
55 | debug('close');
|
---|
56 | this.removeAllListeners();
|
---|
57 | if (this.iframeObj) {
|
---|
58 | eventUtils.detachEvent('message', this.onmessageCallback);
|
---|
59 | try {
|
---|
60 | // When the iframe is not loaded, IE raises an exception
|
---|
61 | // on 'contentWindow'.
|
---|
62 | this.postMessage('c');
|
---|
63 | } catch (x) {
|
---|
64 | // intentionally empty
|
---|
65 | }
|
---|
66 | this.iframeObj.cleanup();
|
---|
67 | this.iframeObj = null;
|
---|
68 | this.onmessageCallback = this.iframeObj = null;
|
---|
69 | }
|
---|
70 | };
|
---|
71 |
|
---|
72 | IframeTransport.prototype._message = function(e) {
|
---|
73 | debug('message', e.data);
|
---|
74 | if (!urlUtils.isOriginEqual(e.origin, this.origin)) {
|
---|
75 | debug('not same origin', e.origin, this.origin);
|
---|
76 | return;
|
---|
77 | }
|
---|
78 |
|
---|
79 | var iframeMessage;
|
---|
80 | try {
|
---|
81 | iframeMessage = JSON3.parse(e.data);
|
---|
82 | } catch (ignored) {
|
---|
83 | debug('bad json', e.data);
|
---|
84 | return;
|
---|
85 | }
|
---|
86 |
|
---|
87 | if (iframeMessage.windowId !== this.windowId) {
|
---|
88 | debug('mismatched window id', iframeMessage.windowId, this.windowId);
|
---|
89 | return;
|
---|
90 | }
|
---|
91 |
|
---|
92 | switch (iframeMessage.type) {
|
---|
93 | case 's':
|
---|
94 | this.iframeObj.loaded();
|
---|
95 | // window global dependency
|
---|
96 | this.postMessage('s', JSON3.stringify([
|
---|
97 | version
|
---|
98 | , this.transport
|
---|
99 | , this.transUrl
|
---|
100 | , this.baseUrl
|
---|
101 | ]));
|
---|
102 | break;
|
---|
103 | case 't':
|
---|
104 | this.emit('message', iframeMessage.data);
|
---|
105 | break;
|
---|
106 | case 'c':
|
---|
107 | var cdata;
|
---|
108 | try {
|
---|
109 | cdata = JSON3.parse(iframeMessage.data);
|
---|
110 | } catch (ignored) {
|
---|
111 | debug('bad json', iframeMessage.data);
|
---|
112 | return;
|
---|
113 | }
|
---|
114 | this.emit('close', cdata[0], cdata[1]);
|
---|
115 | this.close();
|
---|
116 | break;
|
---|
117 | }
|
---|
118 | };
|
---|
119 |
|
---|
120 | IframeTransport.prototype.postMessage = function(type, data) {
|
---|
121 | debug('postMessage', type, data);
|
---|
122 | this.iframeObj.post(JSON3.stringify({
|
---|
123 | windowId: this.windowId
|
---|
124 | , type: type
|
---|
125 | , data: data || ''
|
---|
126 | }), this.origin);
|
---|
127 | };
|
---|
128 |
|
---|
129 | IframeTransport.prototype.send = function(message) {
|
---|
130 | debug('send', message);
|
---|
131 | this.postMessage('m', message);
|
---|
132 | };
|
---|
133 |
|
---|
134 | IframeTransport.enabled = function() {
|
---|
135 | return iframeUtils.iframeEnabled;
|
---|
136 | };
|
---|
137 |
|
---|
138 | IframeTransport.transportName = 'iframe';
|
---|
139 | IframeTransport.roundTrips = 2;
|
---|
140 |
|
---|
141 | module.exports = IframeTransport;
|
---|