faye-authentication 0.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,7 +31,7 @@ describe('Faye extension', function() {
31
31
  function stubSignature(context, callback) {
32
32
  var self = context;
33
33
  self.client.handshake(function() {
34
- var jwtsign = new jwt.WebToken('{"clientId": "' + self.client._clientId + '", "channel": "/foobar", "exp": 2803694528}', '{"alg": "HS256"}');
34
+ var jwtsign = new jwt.WebToken('{"clientId": "' + self.client._dispatcher.clientId + '", "channel": "/foobar", "exp": 2803694528}', '{"alg": "HS256"}');
35
35
  var signature = jwtsign.serialize("macaroni");
36
36
 
37
37
  jasmine.Ajax.stubRequest('/faye/auth').andReturn({
@@ -11,8 +11,8 @@
11
11
  # - dist/**/*.js
12
12
  #
13
13
  src_files:
14
- - app/assets/javascripts/*.js
15
14
  - spec/utils/javascripts/*.js
15
+ - app/assets/javascripts/*.js
16
16
 
17
17
  # stylesheets
18
18
  #
@@ -1,10 +1,11 @@
1
1
  require 'spec_helper'
2
- require 'faye/authentication/server_extension'
2
+ require 'faye/authentication'
3
3
 
4
4
  describe Faye::Authentication::ServerExtension do
5
5
 
6
6
  let(:secret) { 'macaroni' }
7
7
  let(:extension) { Faye::Authentication::ServerExtension.new(secret) }
8
+ let(:channel) { '/channel' }
8
9
 
9
10
  describe '#incoming' do
10
11
  shared_examples 'signature_has_error' do
@@ -22,31 +23,20 @@ describe Faye::Authentication::ServerExtension do
22
23
  end
23
24
 
24
25
  shared_examples 'authentication_actions' do
25
- context 'not signed' do
26
- context '/public' do
27
- context 'no globbing' do
28
- let(:channel) { '/public/foo' }
29
- it_should_behave_like 'signature_has_no_error'
30
- end
26
+ context 'does not require signature' do
27
+ before { expect(Faye::Authentication).to receive(:authentication_required?).with(message, {}).and_return(false) }
28
+ it_should_behave_like 'signature_has_no_error'
29
+ end
31
30
 
32
- context 'globbing' do
33
- let(:channel) { '/public/foo/*'}
34
- it_should_behave_like 'signature_has_error'
35
- end
31
+ context 'requires signature' do
32
+ before { expect(Faye::Authentication).to receive(:authentication_required?).with(message, {}).and_return(true) }
33
+ context 'with signature' do
34
+ before { message['signature'] = Faye::Authentication.sign(message.merge({'channel' => channel}), secret) }
35
+ it_should_behave_like 'signature_has_no_error'
36
36
  end
37
37
 
38
- context 'not public' do
39
- context 'not signed' do
40
- let(:channel) { '/whatever' }
41
- it_should_behave_like 'signature_has_error'
42
- end
43
-
44
- context 'signed' do
45
- let(:channel) { '/foo/bar' }
46
- before { message['signature'] = Faye::Authentication.sign(message.merge({'channel' => channel}), secret) }
47
- it_should_behave_like 'signature_has_no_error'
48
- end
49
-
38
+ context 'without signature' do
39
+ it_should_behave_like 'signature_has_error'
50
40
  end
51
41
  end
52
42
  end
@@ -70,7 +60,7 @@ describe Faye::Authentication::ServerExtension do
70
60
  ['/meta/handshake', '/meta/connect', '/meta/unsubscribe', '/meta/disconnect'].each do |channel|
71
61
  it "does not check the signature for #{channel}" do
72
62
  message = {'channel' => channel, 'clientId' => '42', 'text' => 'whatever', 'signature' => 'hello'}
73
- expect(Faye::Authentication).to_not receive(:valid?)
63
+ expect(Faye::Authentication).to_not receive(:validate)
74
64
  extension.incoming(message, ->(_) {});
75
65
  end
76
66
  end
@@ -8,7 +8,7 @@ describe Faye::Authentication do
8
8
  let(:message) { {'channel' => channel, 'clientId' => clientId, 'text' => 'whatever'} }
9
9
  let(:secret) { 'helloworld' }
10
10
  let(:signature) { Faye::Authentication.sign(message, secret) }
11
-
11
+
12
12
  describe '#sign' do
13
13
  it 'returns with a default expiry'
14
14
  end
@@ -55,4 +55,94 @@ describe Faye::Authentication do
55
55
  end
56
56
  end
57
57
 
58
+ describe '#authentication_required?' do
59
+
60
+ before(:each) { Faye.logger = nil }
61
+
62
+ shared_examples 'subscribe_and_publish' do
63
+ it 'returns true if no options are passed' do
64
+ expect(Faye::Authentication.authentication_required?(message)).to be(true)
65
+ end
66
+
67
+ it 'returns true if empty options are passed' do
68
+ expect(Faye::Authentication.authentication_required?(message, {})).to be(true)
69
+ end
70
+
71
+ it 'returns true if not a lamda / proc' do
72
+ expect(Faye::Authentication.authentication_required?(message, {whitelist: 42})).to be(true)
73
+ end
74
+
75
+ it 'calls the lambda with the channel or subscription' do
76
+ block = double
77
+ expect(block).to receive(:call).with(message['subscription'] || message['channel'])
78
+ Faye::Authentication.authentication_required?(message, {whitelist: block})
79
+ end
80
+
81
+ it 'returns true if lambda raises' do
82
+ expect(Faye::Authentication.authentication_required?(message, {whitelist: lambda { |message| raise "oops" }})).to be(true)
83
+ end
84
+
85
+ it 'logs the error if lambda raises' do
86
+ Faye.logger = double()
87
+ expect(Faye.logger).to receive(:error).with("[Module] Error caught when evaluating whitelist lambda : oops")
88
+ Faye::Authentication.authentication_required?(message, {whitelist: lambda { |message| raise "oops" }})
89
+ end
90
+
91
+ it 'returns true if lambda returns false' do
92
+ expect(Faye::Authentication.authentication_required?(message, {whitelist: lambda { |message| false }})).to be(true)
93
+ end
94
+
95
+ it 'returns false if lambda returns true' do
96
+ expect(Faye::Authentication.authentication_required?(message, {whitelist: lambda { |message| true }})).to be(false)
97
+ end
98
+ end
99
+
100
+ shared_examples 'meta_except_subscribe' do
101
+ it 'returns false if no options are passed' do
102
+ expect(Faye::Authentication.authentication_required?(message)).to be(false)
103
+ end
104
+
105
+ it 'returns false if empty options are passed' do
106
+ expect(Faye::Authentication.authentication_required?(message, {})).to be(false)
107
+ end
108
+
109
+ it 'returns false even if lambda returns false' do
110
+ expect(Faye::Authentication.authentication_required?(message, {whitelist: lambda { |message| false }})).to be(false)
111
+ end
112
+
113
+ it 'does not call lambda / proc' do
114
+ not_called = double()
115
+ expect(not_called).to_not receive(:call)
116
+ (Faye::Authentication.authentication_required?(message, {whitelist: not_called}))
117
+ end
118
+
119
+ end
120
+
121
+ context 'publish' do
122
+ let(:message) { {'channel' => '/foobar'} }
123
+ it_behaves_like 'subscribe_and_publish'
124
+ end
125
+
126
+ context 'subscribe' do
127
+ let(:message) { {'channel' => '/meta/subscribe', 'subscription' => '/foobar'} }
128
+ it_behaves_like 'subscribe_and_publish'
129
+ end
130
+
131
+ context 'handshake' do
132
+ let(:message) { {'channel' => '/meta/handshake'} }
133
+ it_behaves_like 'meta_except_subscribe'
134
+ end
135
+
136
+ context 'connect' do
137
+ let(:message) { {'channel' => '/meta/connect'} }
138
+ it_behaves_like 'meta_except_subscribe'
139
+ end
140
+
141
+ context 'unsubscribe' do
142
+ let(:message) { {'channel' => '/meta/unsubscribe', 'subscription' => '/foobar'} }
143
+ it_behaves_like 'meta_except_subscribe'
144
+ end
145
+
146
+ end
147
+
58
148
  end
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var Faye = {
5
- VERSION: '1.0.1',
5
+ VERSION: '1.0.3',
6
6
 
7
7
  BAYEUX_VERSION: '1.0',
8
8
  ID_LENGTH: 160,
@@ -26,7 +26,10 @@ var Faye = {
26
26
 
27
27
  random: function(bitlength) {
28
28
  bitlength = bitlength || this.ID_LENGTH;
29
- return csprng(bitlength, 36);
29
+ var maxLength = Math.ceil(bitlength * Math.log(2) / Math.log(36));
30
+ var string = csprng(bitlength, 36);
31
+ while (string.length < maxLength) string = '0' + string;
32
+ return string;
30
33
  },
31
34
 
32
35
  clientIdFromMessages: function(messages) {
@@ -744,7 +747,7 @@ Faye.Deferrable = {
744
747
  setDeferredStatus: function(status, value) {
745
748
  if (this._timer) Faye.ENV.clearTimeout(this._timer);
746
749
 
747
- var promise = this.then();
750
+ this.then();
748
751
 
749
752
  if (status === 'succeeded')
750
753
  this._fulfill(value);
@@ -801,7 +804,7 @@ Faye.Timeouts = {
801
804
  this._timeouts = this._timeouts || {};
802
805
  var timeout = this._timeouts[name];
803
806
  if (!timeout) return;
804
- clearTimeout(timeout);
807
+ Faye.ENV.clearTimeout(timeout);
805
808
  delete this._timeouts[name];
806
809
  },
807
810
 
@@ -823,13 +826,13 @@ Faye.Logging = {
823
826
  writeLog: function(messageArgs, level) {
824
827
  if (!Faye.logger) return;
825
828
 
826
- var messageArgs = Array.prototype.slice.apply(messageArgs),
827
- banner = '[Faye',
828
- klass = this.className,
829
+ var args = Array.prototype.slice.apply(messageArgs),
830
+ banner = '[Faye',
831
+ klass = this.className,
829
832
 
830
- message = messageArgs.shift().replace(/\?/g, function() {
833
+ message = args.shift().replace(/\?/g, function() {
831
834
  try {
832
- return Faye.toJSON(messageArgs.shift());
835
+ return Faye.toJSON(args.shift());
833
836
  } catch (e) {
834
837
  return '[Object]';
835
838
  }
@@ -852,11 +855,11 @@ Faye.Logging = {
852
855
 
853
856
  (function() {
854
857
  for (var key in Faye.Logging.LOG_LEVELS)
855
- (function(level, value) {
858
+ (function(level) {
856
859
  Faye.Logging[level] = function() {
857
860
  this.writeLog(arguments, level);
858
861
  };
859
- })(key, Faye.Logging.LOG_LEVELS[key]);
862
+ })(key);
860
863
  })();
861
864
 
862
865
  Faye.Grammar = {
@@ -999,12 +1002,11 @@ Faye.extend(Faye.Channel, {
999
1002
  },
1000
1003
 
1001
1004
  subscribe: function(names, callback, context) {
1002
- if (!callback) return;
1003
1005
  var name;
1004
1006
  for (var i = 0, n = names.length; i < n; i++) {
1005
1007
  name = names[i];
1006
1008
  var channel = this._channels[name] = this._channels[name] || new Faye.Channel(name);
1007
- channel.bind('message', callback, context);
1009
+ if (callback) channel.bind('message', callback, context);
1008
1010
  }
1009
1011
  },
1010
1012
 
@@ -1032,17 +1034,6 @@ Faye.extend(Faye.Channel, {
1032
1034
  })
1033
1035
  });
1034
1036
 
1035
- Faye.Envelope = Faye.Class({
1036
- initialize: function(message, timeout) {
1037
- this.id = message.id;
1038
- this.message = message;
1039
-
1040
- if (timeout !== undefined) this.timeout(timeout / 1000, false);
1041
- }
1042
- });
1043
-
1044
- Faye.extend(Faye.Envelope.prototype, Faye.Deferrable);
1045
-
1046
1037
  Faye.Publication = Faye.Class(Faye.Deferrable);
1047
1038
 
1048
1039
  Faye.Subscription = Faye.Class({
@@ -1068,65 +1059,55 @@ Faye.Subscription = Faye.Class({
1068
1059
  Faye.extend(Faye.Subscription.prototype, Faye.Deferrable);
1069
1060
 
1070
1061
  Faye.Client = Faye.Class({
1071
- UNCONNECTED: 1,
1072
- CONNECTING: 2,
1073
- CONNECTED: 3,
1074
- DISCONNECTED: 4,
1062
+ UNCONNECTED: 1,
1063
+ CONNECTING: 2,
1064
+ CONNECTED: 3,
1065
+ DISCONNECTED: 4,
1075
1066
 
1076
- HANDSHAKE: 'handshake',
1077
- RETRY: 'retry',
1078
- NONE: 'none',
1067
+ HANDSHAKE: 'handshake',
1068
+ RETRY: 'retry',
1069
+ NONE: 'none',
1079
1070
 
1080
- CONNECTION_TIMEOUT: 60,
1081
- DEFAULT_RETRY: 5,
1082
- MAX_REQUEST_SIZE: 2048,
1071
+ CONNECTION_TIMEOUT: 60,
1083
1072
 
1084
- DEFAULT_ENDPOINT: '/bayeux',
1085
- INTERVAL: 0,
1073
+ DEFAULT_ENDPOINT: '/bayeux',
1074
+ INTERVAL: 0,
1086
1075
 
1087
1076
  initialize: function(endpoint, options) {
1088
1077
  this.info('New client created for ?', endpoint);
1078
+ options = options || {};
1089
1079
 
1090
- this._options = options || {};
1091
- this.endpoint = Faye.URI.parse(endpoint || this.DEFAULT_ENDPOINT);
1092
- this.endpoints = this._options.endpoints || {};
1093
- this.transports = {};
1094
- this.cookies = Faye.CookieJar && new Faye.CookieJar();
1095
- this.headers = {};
1096
- this.ca = this._options.ca;
1097
- this._disabled = [];
1098
- this._retry = this._options.retry || this.DEFAULT_RETRY;
1099
-
1100
- for (var key in this.endpoints)
1101
- this.endpoints[key] = Faye.URI.parse(this.endpoints[key]);
1102
-
1103
- this.maxRequestSize = this.MAX_REQUEST_SIZE;
1080
+ this._endpoint = endpoint || this.DEFAULT_ENDPOINT;
1081
+ this._channels = new Faye.Channel.Set();
1082
+ this._dispatcher = new Faye.Dispatcher(this, this._endpoint, options);
1104
1083
 
1105
- this._state = this.UNCONNECTED;
1106
- this._channels = new Faye.Channel.Set();
1107
1084
  this._messageId = 0;
1085
+ this._state = this.UNCONNECTED;
1108
1086
 
1109
1087
  this._responseCallbacks = {};
1110
1088
 
1111
1089
  this._advice = {
1112
1090
  reconnect: this.RETRY,
1113
- interval: 1000 * (this._options.interval || this.INTERVAL),
1114
- timeout: 1000 * (this._options.timeout || this.CONNECTION_TIMEOUT)
1091
+ interval: 1000 * (options.interval || this.INTERVAL),
1092
+ timeout: 1000 * (options.timeout || this.CONNECTION_TIMEOUT)
1115
1093
  };
1094
+ this._dispatcher.timeout = this._advice.timeout / 1000;
1095
+
1096
+ this._dispatcher.bind('message', this._receiveMessage, this);
1116
1097
 
1117
1098
  if (Faye.Event && Faye.ENV.onbeforeunload !== undefined)
1118
1099
  Faye.Event.on(Faye.ENV, 'beforeunload', function() {
1119
- if (Faye.indexOf(this._disabled, 'autodisconnect') < 0)
1100
+ if (Faye.indexOf(this._dispatcher._disabled, 'autodisconnect') < 0)
1120
1101
  this.disconnect();
1121
1102
  }, this);
1122
1103
  },
1123
1104
 
1124
1105
  disable: function(feature) {
1125
- this._disabled.push(feature);
1106
+ return this._dispatcher.disable(feature);
1126
1107
  },
1127
1108
 
1128
1109
  setHeader: function(name, value) {
1129
- this.headers[name] = value;
1110
+ return this._dispatcher.setHeader(name, value);
1130
1111
  },
1131
1112
 
1132
1113
  // Request
@@ -1155,30 +1136,30 @@ Faye.Client = Faye.Class({
1155
1136
  this._state = this.CONNECTING;
1156
1137
  var self = this;
1157
1138
 
1158
- this.info('Initiating handshake with ?', Faye.URI.stringify(this.endpoint));
1159
- this._selectTransport(Faye.MANDATORY_CONNECTION_TYPES);
1139
+ this.info('Initiating handshake with ?', Faye.URI.stringify(this._endpoint));
1140
+ this._dispatcher.selectTransport(Faye.MANDATORY_CONNECTION_TYPES);
1160
1141
 
1161
- this._send({
1142
+ this._sendMessage({
1162
1143
  channel: Faye.Channel.HANDSHAKE,
1163
1144
  version: Faye.BAYEUX_VERSION,
1164
- supportedConnectionTypes: [this._transport.connectionType]
1145
+ supportedConnectionTypes: [this._dispatcher.connectionType]
1165
1146
 
1166
- }, function(response) {
1147
+ }, {}, function(response) {
1167
1148
 
1168
1149
  if (response.successful) {
1169
- this._state = this.CONNECTED;
1170
- this._clientId = response.clientId;
1150
+ this._state = this.CONNECTED;
1151
+ this._dispatcher.clientId = response.clientId;
1171
1152
 
1172
- this._selectTransport(response.supportedConnectionTypes);
1153
+ this._dispatcher.selectTransport(response.supportedConnectionTypes);
1173
1154
 
1174
- this.info('Handshake successful: ?', this._clientId);
1155
+ this.info('Handshake successful: ?', this._dispatcher.clientId);
1175
1156
 
1176
1157
  this.subscribe(this._channels.getKeys(), true);
1177
1158
  if (callback) Faye.Promise.defer(function() { callback.call(context) });
1178
1159
 
1179
1160
  } else {
1180
1161
  this.info('Handshake unsuccessful');
1181
- Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._advice.interval);
1162
+ Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._dispatcher.retry * 1000);
1182
1163
  this._state = this.UNCONNECTED;
1183
1164
  }
1184
1165
  }, this);
@@ -1203,21 +1184,21 @@ Faye.Client = Faye.Class({
1203
1184
  this.callback(callback, context);
1204
1185
  if (this._state !== this.CONNECTED) return;
1205
1186
 
1206
- this.info('Calling deferred actions for ?', this._clientId);
1187
+ this.info('Calling deferred actions for ?', this._dispatcher.clientId);
1207
1188
  this.setDeferredStatus('succeeded');
1208
1189
  this.setDeferredStatus('unknown');
1209
1190
 
1210
1191
  if (this._connectRequest) return;
1211
1192
  this._connectRequest = true;
1212
1193
 
1213
- this.info('Initiating connection for ?', this._clientId);
1194
+ this.info('Initiating connection for ?', this._dispatcher.clientId);
1214
1195
 
1215
- this._send({
1196
+ this._sendMessage({
1216
1197
  channel: Faye.Channel.CONNECT,
1217
- clientId: this._clientId,
1218
- connectionType: this._transport.connectionType
1198
+ clientId: this._dispatcher.clientId,
1199
+ connectionType: this._dispatcher.connectionType
1219
1200
 
1220
- }, this._cycleConnection, this);
1201
+ }, {}, this._cycleConnection, this);
1221
1202
  },
1222
1203
 
1223
1204
  // Request Response
@@ -1231,19 +1212,17 @@ Faye.Client = Faye.Class({
1231
1212
  if (this._state !== this.CONNECTED) return;
1232
1213
  this._state = this.DISCONNECTED;
1233
1214
 
1234
- this.info('Disconnecting ?', this._clientId);
1215
+ this.info('Disconnecting ?', this._dispatcher.clientId);
1235
1216
 
1236
- this._send({
1217
+ this._sendMessage({
1237
1218
  channel: Faye.Channel.DISCONNECT,
1238
- clientId: this._clientId
1219
+ clientId: this._dispatcher.clientId
1239
1220
 
1240
- }, function(response) {
1241
- if (!response.successful) return;
1242
- this._transport.close();
1243
- delete this._transport;
1221
+ }, {}, function(response) {
1222
+ if (response.successful) this._dispatcher.close();
1244
1223
  }, this);
1245
1224
 
1246
- this.info('Clearing channel listeners for ?', this._clientId);
1225
+ this.info('Clearing channel listeners for ?', this._dispatcher.clientId);
1247
1226
  this._channels = new Faye.Channel.Set();
1248
1227
  },
1249
1228
 
@@ -1274,22 +1253,22 @@ Faye.Client = Faye.Class({
1274
1253
  }
1275
1254
 
1276
1255
  this.connect(function() {
1277
- this.info('Client ? attempting to subscribe to ?', this._clientId, channel);
1256
+ this.info('Client ? attempting to subscribe to ?', this._dispatcher.clientId, channel);
1278
1257
  if (!force) this._channels.subscribe([channel], callback, context);
1279
1258
 
1280
- this._send({
1259
+ this._sendMessage({
1281
1260
  channel: Faye.Channel.SUBSCRIBE,
1282
- clientId: this._clientId,
1261
+ clientId: this._dispatcher.clientId,
1283
1262
  subscription: channel
1284
1263
 
1285
- }, function(response) {
1264
+ }, {}, function(response) {
1286
1265
  if (!response.successful) {
1287
1266
  subscription.setDeferredStatus('failed', Faye.Error.parse(response.error));
1288
1267
  return this._channels.unsubscribe(channel, callback, context);
1289
1268
  }
1290
1269
 
1291
1270
  var channels = [].concat(response.subscription);
1292
- this.info('Subscription acknowledged for ? to ?', this._clientId, channels);
1271
+ this.info('Subscription acknowledged for ? to ?', this._dispatcher.clientId, channels);
1293
1272
  subscription.setDeferredStatus('succeeded');
1294
1273
  }, this);
1295
1274
  }, this);
@@ -1317,18 +1296,18 @@ Faye.Client = Faye.Class({
1317
1296
  if (!dead) return;
1318
1297
 
1319
1298
  this.connect(function() {
1320
- this.info('Client ? attempting to unsubscribe from ?', this._clientId, channel);
1299
+ this.info('Client ? attempting to unsubscribe from ?', this._dispatcher.clientId, channel);
1321
1300
 
1322
- this._send({
1301
+ this._sendMessage({
1323
1302
  channel: Faye.Channel.UNSUBSCRIBE,
1324
- clientId: this._clientId,
1303
+ clientId: this._dispatcher.clientId,
1325
1304
  subscription: channel
1326
1305
 
1327
- }, function(response) {
1306
+ }, {}, function(response) {
1328
1307
  if (!response.successful) return;
1329
1308
 
1330
1309
  var channels = [].concat(response.subscription);
1331
- this.info('Unsubscription acknowledged for ? from ?', this._clientId, channels);
1310
+ this.info('Unsubscription acknowledged for ? from ?', this._dispatcher.clientId, channels);
1332
1311
  }, this);
1333
1312
  }, this);
1334
1313
  },
@@ -1339,18 +1318,18 @@ Faye.Client = Faye.Class({
1339
1318
  // MAY include: * clientId MAY include: * id
1340
1319
  // * id * error
1341
1320
  // * ext * ext
1342
- publish: function(channel, data) {
1321
+ publish: function(channel, data, options) {
1343
1322
  var publication = new Faye.Publication();
1344
1323
 
1345
1324
  this.connect(function() {
1346
- this.info('Client ? queueing published message to ?: ?', this._clientId, channel, data);
1325
+ this.info('Client ? queueing published message to ?: ?', this._dispatcher.clientId, channel, data);
1347
1326
 
1348
- this._send({
1327
+ this._sendMessage({
1349
1328
  channel: channel,
1350
1329
  data: data,
1351
- clientId: this._clientId
1330
+ clientId: this._dispatcher.clientId
1352
1331
 
1353
- }, function(response) {
1332
+ }, options, function(response) {
1354
1333
  if (response.successful)
1355
1334
  publication.setDeferredStatus('succeeded');
1356
1335
  else
@@ -1361,8 +1340,28 @@ Faye.Client = Faye.Class({
1361
1340
  return publication;
1362
1341
  },
1363
1342
 
1364
- receiveMessage: function(message) {
1365
- var id = message.id, timeout, callback;
1343
+ _sendMessage: function(message, options, callback, context) {
1344
+ message.id = this._generateMessageId();
1345
+
1346
+ var timeout = this._advice.timeout
1347
+ ? 1.2 * this._advice.timeout / 1000
1348
+ : 1.2 * this._dispatcher.retry;
1349
+
1350
+ this.pipeThroughExtensions('outgoing', message, null, function(message) {
1351
+ if (!message) return;
1352
+ if (callback) this._responseCallbacks[message.id] = [callback, context];
1353
+ this._dispatcher.sendMessage(message, timeout, options || {});
1354
+ }, this);
1355
+ },
1356
+
1357
+ _generateMessageId: function() {
1358
+ this._messageId += 1;
1359
+ if (this._messageId >= Math.pow(2,32)) this._messageId = 0;
1360
+ return this._messageId.toString(36);
1361
+ },
1362
+
1363
+ _receiveMessage: function(message) {
1364
+ var id = message.id, callback;
1366
1365
 
1367
1366
  if (message.successful !== undefined) {
1368
1367
  callback = this._responseCallbacks[id];
@@ -1371,39 +1370,90 @@ Faye.Client = Faye.Class({
1371
1370
 
1372
1371
  this.pipeThroughExtensions('incoming', message, null, function(message) {
1373
1372
  if (!message) return;
1374
-
1375
1373
  if (message.advice) this._handleAdvice(message.advice);
1376
1374
  this._deliverMessage(message);
1377
-
1378
1375
  if (callback) callback[0].call(callback[1], message);
1379
1376
  }, this);
1380
-
1381
- if (this._transportUp === true) return;
1382
- this._transportUp = true;
1383
- this.trigger('transport:up');
1384
1377
  },
1385
1378
 
1386
- messageError: function(messages, immediate) {
1387
- var retry = this._retry,
1388
- self = this,
1389
- id, message, timeout;
1379
+ _handleAdvice: function(advice) {
1380
+ Faye.extend(this._advice, advice);
1381
+ this._dispatcher.timeout = this._advice.timeout / 1000;
1390
1382
 
1391
- for (var i = 0, n = messages.length; i < n; i++) {
1392
- message = messages[i];
1393
- id = message.id;
1383
+ if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) {
1384
+ this._state = this.UNCONNECTED;
1385
+ this._dispatcher.clientId = null;
1386
+ this._cycleConnection();
1387
+ }
1388
+ },
1394
1389
 
1395
- if (immediate)
1396
- this._transportSend(message);
1397
- else
1398
- Faye.ENV.setTimeout(function() { self._transportSend(message) }, retry * 1000);
1390
+ _deliverMessage: function(message) {
1391
+ if (!message.channel || message.data === undefined) return;
1392
+ this.info('Client ? calling listeners for ? with ?', this._dispatcher.clientId, message.channel, message.data);
1393
+ this._channels.distributeMessage(message);
1394
+ },
1395
+
1396
+ _cycleConnection: function() {
1397
+ if (this._connectRequest) {
1398
+ this._connectRequest = null;
1399
+ this.info('Closed connection for ?', this._dispatcher.clientId);
1399
1400
  }
1401
+ var self = this;
1402
+ Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval);
1403
+ }
1404
+ });
1405
+
1406
+ Faye.extend(Faye.Client.prototype, Faye.Deferrable);
1407
+ Faye.extend(Faye.Client.prototype, Faye.Publisher);
1408
+ Faye.extend(Faye.Client.prototype, Faye.Logging);
1409
+ Faye.extend(Faye.Client.prototype, Faye.Extensible);
1410
+
1411
+ Faye.Dispatcher = Faye.Class({
1412
+ MAX_REQUEST_SIZE: 2048,
1413
+ DEFAULT_RETRY: 5,
1414
+
1415
+ UP: 1,
1416
+ DOWN: 2,
1417
+
1418
+ initialize: function(client, endpoint, options) {
1419
+ this._client = client;
1420
+ this.endpoint = Faye.URI.parse(endpoint);
1421
+ this._alternates = options.endpoints || {};
1422
+
1423
+ this.ca = options.ca;
1424
+ this.cookies = Faye.Cookies && new Faye.Cookies.CookieJar();
1425
+ this._disabled = [];
1426
+ this._envelopes = {};
1427
+ this.headers = {};
1428
+ this.retry = options.retry || this.DEFAULT_RETRY;
1429
+ this._state = 0;
1430
+ this.transports = {};
1431
+
1432
+ for (var type in this._alternates)
1433
+ this._alternates[type] = Faye.URI.parse(this._alternates[type]);
1434
+
1435
+ this.maxRequestSize = this.MAX_REQUEST_SIZE;
1436
+ },
1400
1437
 
1401
- if (immediate || this._transportUp === false) return;
1402
- this._transportUp = false;
1403
- this.trigger('transport:down');
1438
+ endpointFor: function(connectionType) {
1439
+ return this._alternates[connectionType] || this.endpoint;
1404
1440
  },
1405
1441
 
1406
- _selectTransport: function(transportTypes) {
1442
+ disable: function(feature) {
1443
+ this._disabled.push(feature);
1444
+ },
1445
+
1446
+ setHeader: function(name, value) {
1447
+ this.headers[name] = value;
1448
+ },
1449
+
1450
+ close: function() {
1451
+ var transport = this._transport;
1452
+ delete this._transport;
1453
+ if (transport) transport.close();
1454
+ },
1455
+
1456
+ selectTransport: function(transportTypes) {
1407
1457
  Faye.Transport.get(this, transportTypes, this._disabled, function(transport) {
1408
1458
  this.debug('Selected ? transport for ?', transport.connectionType, Faye.URI.stringify(transport.endpoint));
1409
1459
 
@@ -1411,186 +1461,220 @@ Faye.Client = Faye.Class({
1411
1461
  if (this._transport) this._transport.close();
1412
1462
 
1413
1463
  this._transport = transport;
1464
+ this.connectionType = transport.connectionType;
1414
1465
  }, this);
1415
1466
  },
1416
1467
 
1417
- _send: function(message, callback, context) {
1468
+ sendMessage: function(message, timeout, options) {
1418
1469
  if (!this._transport) return;
1419
- message.id = message.id || this._generateMessageId();
1470
+ options = options || {};
1420
1471
 
1421
- this.pipeThroughExtensions('outgoing', message, null, function(message) {
1422
- if (!message) return;
1423
- if (callback) this._responseCallbacks[message.id] = [callback, context];
1424
- this._transportSend(message);
1425
- }, this);
1426
- },
1472
+ var self = this,
1473
+ id = message.id,
1474
+ attempts = options.attempts,
1475
+ deadline = options.deadline && new Date().getTime() + (options.deadline * 1000),
1427
1476
 
1428
- _transportSend: function(message) {
1429
- if (!this._transport) return;
1477
+ envelope = this._envelopes[id] = this._envelopes[id] ||
1478
+ {message: message, timeout: timeout, attempts: attempts, deadline: deadline};
1430
1479
 
1431
- var timeout = 1.2 * (this._advice.timeout || this._retry * 1000),
1432
- envelope = new Faye.Envelope(message, timeout);
1480
+ if (envelope.request || envelope.timer) return;
1433
1481
 
1434
- envelope.errback(function(immediate) {
1435
- this.messageError([message], immediate);
1436
- }, this);
1482
+ if (this._attemptsExhausted(envelope) || this._deadlinePassed(envelope)) {
1483
+ delete this._envelopes[id];
1484
+ return;
1485
+ }
1486
+
1487
+ envelope.timer = Faye.ENV.setTimeout(function() {
1488
+ self.handleError(message);
1489
+ }, timeout * 1000);
1437
1490
 
1438
- this._transport.send(envelope);
1491
+ envelope.request = this._transport.sendMessage(message);
1439
1492
  },
1440
1493
 
1441
- _generateMessageId: function() {
1442
- this._messageId += 1;
1443
- if (this._messageId >= Math.pow(2,32)) this._messageId = 0;
1444
- return this._messageId.toString(36);
1494
+ handleResponse: function(reply) {
1495
+ var envelope = this._envelopes[reply.id];
1496
+
1497
+ if (reply.successful !== undefined && envelope) {
1498
+ delete this._envelopes[reply.id];
1499
+ Faye.ENV.clearTimeout(envelope.timer);
1500
+ }
1501
+
1502
+ this.trigger('message', reply);
1503
+
1504
+ if (this._state === this.UP) return;
1505
+ this._state = this.UP;
1506
+ this._client.trigger('transport:up');
1445
1507
  },
1446
1508
 
1447
- _handleAdvice: function(advice) {
1448
- Faye.extend(this._advice, advice);
1509
+ handleError: function(message, immediate) {
1510
+ var envelope = this._envelopes[message.id],
1511
+ request = envelope && envelope.request,
1512
+ self = this;
1449
1513
 
1450
- if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) {
1451
- this._state = this.UNCONNECTED;
1452
- this._clientId = null;
1453
- this._cycleConnection();
1514
+ if (!request) return;
1515
+
1516
+ request.then(function(req) {
1517
+ if (req && req.abort) req.abort();
1518
+ });
1519
+
1520
+ Faye.ENV.clearTimeout(envelope.timer);
1521
+ envelope.request = envelope.timer = null;
1522
+
1523
+ if (immediate) {
1524
+ this.sendMessage(envelope.message, envelope.timeout);
1525
+ } else {
1526
+ envelope.timer = Faye.ENV.setTimeout(function() {
1527
+ envelope.timer = null;
1528
+ self.sendMessage(envelope.message, envelope.timeout);
1529
+ }, this.retry * 1000);
1454
1530
  }
1531
+
1532
+ if (this._state === this.DOWN) return;
1533
+ this._state = this.DOWN;
1534
+ this._client.trigger('transport:down');
1455
1535
  },
1456
1536
 
1457
- _deliverMessage: function(message) {
1458
- if (!message.channel || message.data === undefined) return;
1459
- this.info('Client ? calling listeners for ? with ?', this._clientId, message.channel, message.data);
1460
- this._channels.distributeMessage(message);
1537
+ _attemptsExhausted: function(envelope) {
1538
+ if (envelope.attempts === undefined) return false;
1539
+ envelope.attempts -= 1;
1540
+ if (envelope.attempts >= 0) return false;
1541
+ return true;
1461
1542
  },
1462
1543
 
1463
- _cycleConnection: function() {
1464
- if (this._connectRequest) {
1465
- this._connectRequest = null;
1466
- this.info('Closed connection for ?', this._clientId);
1467
- }
1468
- var self = this;
1469
- Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval);
1544
+ _deadlinePassed: function(envelope) {
1545
+ var deadline = envelope.deadline;
1546
+ if (deadline === undefined) return false;
1547
+ if (new Date().getTime() <= deadline) return false;
1548
+ return true;
1470
1549
  }
1471
1550
  });
1472
1551
 
1473
- Faye.extend(Faye.Client.prototype, Faye.Deferrable);
1474
- Faye.extend(Faye.Client.prototype, Faye.Publisher);
1475
- Faye.extend(Faye.Client.prototype, Faye.Logging);
1476
- Faye.extend(Faye.Client.prototype, Faye.Extensible);
1552
+ Faye.extend(Faye.Dispatcher.prototype, Faye.Publisher);
1553
+ Faye.extend(Faye.Dispatcher.prototype, Faye.Logging);
1477
1554
 
1478
1555
  Faye.Transport = Faye.extend(Faye.Class({
1479
1556
  MAX_DELAY: 0,
1480
1557
  batching: true,
1481
1558
 
1482
- initialize: function(client, endpoint) {
1483
- this._client = client;
1484
- this.endpoint = endpoint;
1485
- this._outbox = [];
1559
+ initialize: function(dispatcher, endpoint) {
1560
+ this._dispatcher = dispatcher;
1561
+ this.endpoint = endpoint;
1562
+ this._outbox = [];
1486
1563
  },
1487
1564
 
1488
1565
  close: function() {},
1489
1566
 
1490
- encode: function(envelopes) {
1567
+ encode: function(messages) {
1491
1568
  return '';
1492
1569
  },
1493
1570
 
1494
- send: function(envelope) {
1495
- var message = envelope.message;
1496
-
1571
+ sendMessage: function(message) {
1497
1572
  this.debug('Client ? sending message to ?: ?',
1498
- this._client._clientId, Faye.URI.stringify(this.endpoint), message);
1573
+ this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), message);
1499
1574
 
1500
- if (!this.batching) return this.request([envelope]);
1575
+ if (!this.batching) return Faye.Promise.fulfilled(this.request([message]));
1501
1576
 
1502
- this._outbox.push(envelope);
1577
+ this._outbox.push(message);
1578
+ this._flushLargeBatch();
1579
+ this._promise = this._promise || new Faye.Promise();
1503
1580
 
1504
- if (message.channel === Faye.Channel.HANDSHAKE)
1505
- return this.addTimeout('publish', 0.01, this.flush, this);
1581
+ if (message.channel === Faye.Channel.HANDSHAKE) {
1582
+ this.addTimeout('publish', 0.01, this._flush, this);
1583
+ return this._promise;
1584
+ }
1506
1585
 
1507
1586
  if (message.channel === Faye.Channel.CONNECT)
1508
1587
  this._connectMessage = message;
1509
1588
 
1510
- this.flushLargeBatch();
1511
- this.addTimeout('publish', this.MAX_DELAY, this.flush, this);
1589
+ this.addTimeout('publish', this.MAX_DELAY, this._flush, this);
1590
+ return this._promise;
1512
1591
  },
1513
1592
 
1514
- flush: function() {
1593
+ _flush: function() {
1515
1594
  this.removeTimeout('publish');
1516
1595
 
1517
1596
  if (this._outbox.length > 1 && this._connectMessage)
1518
1597
  this._connectMessage.advice = {timeout: 0};
1519
1598
 
1520
- this.request(this._outbox);
1599
+ Faye.Promise.fulfill(this._promise, this.request(this._outbox));
1600
+ delete this._promise;
1521
1601
 
1522
1602
  this._connectMessage = null;
1523
1603
  this._outbox = [];
1524
1604
  },
1525
1605
 
1526
- flushLargeBatch: function() {
1606
+ _flushLargeBatch: function() {
1527
1607
  var string = this.encode(this._outbox);
1528
- if (string.length < this._client.maxRequestSize) return;
1608
+ if (string.length < this._dispatcher.maxRequestSize) return;
1529
1609
  var last = this._outbox.pop();
1530
- this.flush();
1610
+ this._flush();
1531
1611
  if (last) this._outbox.push(last);
1532
1612
  },
1533
1613
 
1534
- receive: function(envelopes, responses) {
1535
- var n = envelopes.length;
1536
- while (n--) envelopes[n].setDeferredStatus('succeeded');
1537
-
1538
- responses = [].concat(responses);
1614
+ _receive: function(replies) {
1615
+ replies = [].concat(replies);
1539
1616
 
1540
- this.debug('Client ? received from ?: ?',
1541
- this._client._clientId, Faye.URI.stringify(this.endpoint), responses);
1617
+ this.debug('Client ? received from ? via ?: ?',
1618
+ this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), this.connectionType, replies);
1542
1619
 
1543
- for (var i = 0, n = responses.length; i < n; i++)
1544
- this._client.receiveMessage(responses[i]);
1620
+ for (var i = 0, n = replies.length; i < n; i++)
1621
+ this._dispatcher.handleResponse(replies[i]);
1545
1622
  },
1546
1623
 
1547
- handleError: function(envelopes, immediate) {
1548
- var n = envelopes.length;
1549
- while (n--) envelopes[n].setDeferredStatus('failed', immediate);
1624
+ _handleError: function(messages, immediate) {
1625
+ messages = [].concat(messages);
1626
+
1627
+ this.debug('Client ? failed to send to ? via ?: ?',
1628
+ this._dispatcher.clientId, Faye.URI.stringify(this.endpoint), this.connectionType, messages);
1629
+
1630
+ for (var i = 0, n = messages.length; i < n; i++)
1631
+ this._dispatcher.handleError(messages[i]);
1550
1632
  },
1551
1633
 
1552
1634
  _getCookies: function() {
1553
- var cookies = this._client.cookies;
1635
+ var cookies = this._dispatcher.cookies,
1636
+ url = Faye.URI.stringify(this.endpoint);
1637
+
1554
1638
  if (!cookies) return '';
1555
1639
 
1556
- return cookies.getCookies({
1557
- domain: this.endpoint.hostname,
1558
- path: this.endpoint.path,
1559
- secure: this.endpoint.protocol === 'https:'
1560
- }).toValueString();
1640
+ return Faye.map(cookies.getCookiesSync(url), function(cookie) {
1641
+ return cookie.cookieString();
1642
+ }).join('; ');
1561
1643
  },
1562
1644
 
1563
1645
  _storeCookies: function(setCookie) {
1564
- if (!setCookie || !this._client.cookies) return;
1646
+ var cookies = this._dispatcher.cookies,
1647
+ url = Faye.URI.stringify(this.endpoint),
1648
+ cookie;
1649
+
1650
+ if (!setCookie || !cookies) return;
1565
1651
  setCookie = [].concat(setCookie);
1566
- var cookie;
1567
1652
 
1568
1653
  for (var i = 0, n = setCookie.length; i < n; i++) {
1569
- cookie = this._client.cookies.setCookie(setCookie[i]);
1570
- cookie = cookie[0] || cookie;
1571
- cookie.domain = cookie.domain || this.endpoint.hostname;
1654
+ cookie = Faye.Cookies.Cookie.parse(setCookie[i]);
1655
+ cookies.setCookieSync(cookie, url);
1572
1656
  }
1573
1657
  }
1574
1658
 
1575
1659
  }), {
1576
- get: function(client, allowed, disabled, callback, context) {
1577
- var endpoint = client.endpoint;
1660
+ get: function(dispatcher, allowed, disabled, callback, context) {
1661
+ var endpoint = dispatcher.endpoint;
1578
1662
 
1579
1663
  Faye.asyncEach(this._transports, function(pair, resume) {
1580
1664
  var connType = pair[0], klass = pair[1],
1581
- connEndpoint = client.endpoints[connType] || endpoint;
1665
+ connEndpoint = dispatcher.endpointFor(connType);
1582
1666
 
1583
1667
  if (Faye.indexOf(disabled, connType) >= 0)
1584
1668
  return resume();
1585
1669
 
1586
1670
  if (Faye.indexOf(allowed, connType) < 0) {
1587
- klass.isUsable(client, connEndpoint, function() {});
1671
+ klass.isUsable(dispatcher, connEndpoint, function() {});
1588
1672
  return resume();
1589
1673
  }
1590
1674
 
1591
- klass.isUsable(client, connEndpoint, function(isUsable) {
1675
+ klass.isUsable(dispatcher, connEndpoint, function(isUsable) {
1592
1676
  if (!isUsable) return resume();
1593
- var transport = klass.hasOwnProperty('create') ? klass.create(client, connEndpoint) : new klass(client, connEndpoint);
1677
+ var transport = klass.hasOwnProperty('create') ? klass.create(dispatcher, connEndpoint) : new klass(dispatcher, connEndpoint);
1594
1678
  callback.call(context, transport);
1595
1679
  });
1596
1680
  }, function() {
@@ -2154,16 +2238,23 @@ Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2154
2238
  this.connect();
2155
2239
  },
2156
2240
 
2157
- request: function(envelopes) {
2241
+ request: function(messages) {
2158
2242
  this._pending = this._pending || new Faye.Set();
2159
- for (var i = 0, n = envelopes.length; i < n; i++) this._pending.add(envelopes[i]);
2243
+ for (var i = 0, n = messages.length; i < n; i++) this._pending.add(messages[i]);
2244
+
2245
+ var promise = new Faye.Promise();
2160
2246
 
2161
2247
  this.callback(function(socket) {
2162
2248
  if (!socket) return;
2163
- var messages = Faye.map(envelopes, function(e) { return e.message });
2164
2249
  socket.send(Faye.toJSON(messages));
2250
+ Faye.Promise.fulfill(promise, socket);
2165
2251
  }, this);
2252
+
2166
2253
  this.connect();
2254
+
2255
+ return {
2256
+ abort: function() { promise.then(function(ws) { ws.close() }) }
2257
+ };
2167
2258
  },
2168
2259
 
2169
2260
  connect: function() {
@@ -2204,28 +2295,25 @@ Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2204
2295
  delete self._pending;
2205
2296
 
2206
2297
  if (wasConnected) {
2207
- self.handleError(pending, true);
2298
+ self._handleError(pending, true);
2208
2299
  } else if (self._everConnected) {
2209
- self.handleError(pending);
2300
+ self._handleError(pending);
2210
2301
  } else {
2211
2302
  self.setDeferredStatus('failed');
2212
2303
  }
2213
2304
  };
2214
2305
 
2215
2306
  socket.onmessage = function(event) {
2216
- var messages = JSON.parse(event.data),
2217
- envelopes = [],
2218
- envelope;
2307
+ var replies = JSON.parse(event.data);
2308
+ if (!replies) return;
2219
2309
 
2220
- if (!messages) return;
2221
- messages = [].concat(messages);
2310
+ replies = [].concat(replies);
2222
2311
 
2223
- for (var i = 0, n = messages.length; i < n; i++) {
2224
- if (messages[i].successful === undefined) continue;
2225
- envelope = self._pending.remove(messages[i]);
2226
- if (envelope) envelopes.push(envelope);
2312
+ for (var i = 0, n = replies.length; i < n; i++) {
2313
+ if (replies[i].successful === undefined) continue;
2314
+ self._pending.remove(replies[i]);
2227
2315
  }
2228
- self.receive(envelopes, messages);
2316
+ self._receive(replies);
2229
2317
  };
2230
2318
  },
2231
2319
 
@@ -2236,7 +2324,8 @@ Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2236
2324
 
2237
2325
  _createSocket: function() {
2238
2326
  var url = Faye.Transport.WebSocket.getSocketUrl(this.endpoint),
2239
- options = {headers: Faye.copyObject(this._client.headers), ca: this._client.ca};
2327
+ headers = Faye.copyObject(this._dispatcher.headers),
2328
+ options = {headers: headers, ca: this._dispatcher.ca};
2240
2329
 
2241
2330
  options.headers['Cookie'] = this._getCookies();
2242
2331
 
@@ -2248,7 +2337,7 @@ Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2248
2337
  _ping: function() {
2249
2338
  if (!this._socket) return;
2250
2339
  this._socket.send('[]');
2251
- this.addTimeout('ping', this._client._advice.timeout/2000, this._ping, this);
2340
+ this.addTimeout('ping', this._dispatcher.timeout / 2, this._ping, this);
2252
2341
  }
2253
2342
 
2254
2343
  }), {
@@ -2257,9 +2346,9 @@ Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2257
2346
  'https:': 'wss:'
2258
2347
  },
2259
2348
 
2260
- create: function(client, endpoint) {
2261
- var sockets = client.transports.websocket = client.transports.websocket || {};
2262
- sockets[endpoint.href] = sockets[endpoint.href] || new this(client, endpoint);
2349
+ create: function(dispatcher, endpoint) {
2350
+ var sockets = dispatcher.transports.websocket = dispatcher.transports.websocket || {};
2351
+ sockets[endpoint.href] = sockets[endpoint.href] || new this(dispatcher, endpoint);
2263
2352
  return sockets[endpoint.href];
2264
2353
  },
2265
2354
 
@@ -2269,28 +2358,28 @@ Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
2269
2358
  return Faye.URI.stringify(endpoint);
2270
2359
  },
2271
2360
 
2272
- isUsable: function(client, endpoint, callback, context) {
2273
- this.create(client, endpoint).isUsable(callback, context);
2361
+ isUsable: function(dispatcher, endpoint, callback, context) {
2362
+ this.create(dispatcher, endpoint).isUsable(callback, context);
2274
2363
  }
2275
2364
  });
2276
2365
 
2277
2366
  Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable);
2278
2367
  Faye.Transport.register('websocket', Faye.Transport.WebSocket);
2279
2368
 
2280
- if (Faye.Event)
2369
+ if (Faye.Event && Faye.ENV.onbeforeunload !== undefined)
2281
2370
  Faye.Event.on(Faye.ENV, 'beforeunload', function() {
2282
2371
  Faye.Transport.WebSocket._unloaded = true;
2283
2372
  });
2284
2373
 
2285
2374
  Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, {
2286
- initialize: function(client, endpoint) {
2287
- Faye.Transport.prototype.initialize.call(this, client, endpoint);
2375
+ initialize: function(dispatcher, endpoint) {
2376
+ Faye.Transport.prototype.initialize.call(this, dispatcher, endpoint);
2288
2377
  if (!Faye.ENV.EventSource) return this.setDeferredStatus('failed');
2289
2378
 
2290
- this._xhr = new Faye.Transport.XHR(client, endpoint);
2379
+ this._xhr = new Faye.Transport.XHR(dispatcher, endpoint);
2291
2380
 
2292
2381
  endpoint = Faye.copyObject(endpoint);
2293
- endpoint.pathname += '/' + client._clientId;
2382
+ endpoint.pathname += '/' + dispatcher.clientId;
2294
2383
 
2295
2384
  var socket = new EventSource(Faye.URI.stringify(endpoint)),
2296
2385
  self = this;
@@ -2302,7 +2391,7 @@ Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, {
2302
2391
 
2303
2392
  socket.onerror = function() {
2304
2393
  if (self._everConnected) {
2305
- self._client.messageError([]);
2394
+ self._handleError([]);
2306
2395
  } else {
2307
2396
  self.setDeferredStatus('failed');
2308
2397
  socket.close();
@@ -2310,7 +2399,7 @@ Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, {
2310
2399
  };
2311
2400
 
2312
2401
  socket.onmessage = function(event) {
2313
- self.receive([], JSON.parse(event.data));
2402
+ self._receive(JSON.parse(event.data));
2314
2403
  };
2315
2404
 
2316
2405
  this._socket = socket;
@@ -2328,34 +2417,34 @@ Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, {
2328
2417
  this.errback(function() { callback.call(context, false) });
2329
2418
  },
2330
2419
 
2331
- encode: function(envelopes) {
2332
- return this._xhr.encode(envelopes);
2420
+ encode: function(messages) {
2421
+ return this._xhr.encode(messages);
2333
2422
  },
2334
2423
 
2335
- request: function(envelopes) {
2336
- this._xhr.request(envelopes);
2424
+ request: function(messages) {
2425
+ return this._xhr.request(messages);
2337
2426
  }
2338
2427
 
2339
2428
  }), {
2340
- isUsable: function(client, endpoint, callback, context) {
2341
- var id = client._clientId;
2429
+ isUsable: function(dispatcher, endpoint, callback, context) {
2430
+ var id = dispatcher.clientId;
2342
2431
  if (!id) return callback.call(context, false);
2343
2432
 
2344
- Faye.Transport.XHR.isUsable(client, endpoint, function(usable) {
2433
+ Faye.Transport.XHR.isUsable(dispatcher, endpoint, function(usable) {
2345
2434
  if (!usable) return callback.call(context, false);
2346
- this.create(client, endpoint).isUsable(callback, context);
2435
+ this.create(dispatcher, endpoint).isUsable(callback, context);
2347
2436
  }, this);
2348
2437
  },
2349
2438
 
2350
- create: function(client, endpoint) {
2351
- var sockets = client.transports.eventsource = client.transports.eventsource || {},
2352
- id = client._clientId;
2439
+ create: function(dispatcher, endpoint) {
2440
+ var sockets = dispatcher.transports.eventsource = dispatcher.transports.eventsource || {},
2441
+ id = dispatcher.clientId;
2353
2442
 
2354
2443
  endpoint = Faye.copyObject(endpoint);
2355
2444
  endpoint.pathname += '/' + (id || '');
2356
2445
  var url = Faye.URI.stringify(endpoint);
2357
2446
 
2358
- sockets[url] = sockets[url] || new this(client, endpoint);
2447
+ sockets[url] = sockets[url] || new this(dispatcher, endpoint);
2359
2448
  return sockets[url];
2360
2449
  }
2361
2450
  });
@@ -2364,58 +2453,58 @@ Faye.extend(Faye.Transport.EventSource.prototype, Faye.Deferrable);
2364
2453
  Faye.Transport.register('eventsource', Faye.Transport.EventSource);
2365
2454
 
2366
2455
  Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, {
2367
- encode: function(envelopes) {
2368
- var messages = Faye.map(envelopes, function(e) { return e.message });
2456
+ encode: function(messages) {
2369
2457
  return Faye.toJSON(messages);
2370
2458
  },
2371
2459
 
2372
- request: function(envelopes) {
2373
- var path = this.endpoint.path,
2460
+ request: function(messages) {
2461
+ var href = this.endpoint.href,
2374
2462
  xhr = Faye.ENV.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(),
2375
2463
  self = this;
2376
2464
 
2377
- xhr.open('POST', path, true);
2465
+ xhr.open('POST', href, true);
2378
2466
  xhr.setRequestHeader('Content-Type', 'application/json');
2379
2467
  xhr.setRequestHeader('Pragma', 'no-cache');
2380
2468
  xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
2381
2469
 
2382
- var headers = this._client.headers;
2470
+ var headers = this._dispatcher.headers;
2383
2471
  for (var key in headers) {
2384
2472
  if (!headers.hasOwnProperty(key)) continue;
2385
2473
  xhr.setRequestHeader(key, headers[key]);
2386
2474
  }
2387
2475
 
2388
2476
  var abort = function() { xhr.abort() };
2389
- Faye.Event.on(Faye.ENV, 'beforeunload', abort);
2477
+ if (Faye.ENV.onbeforeunload !== undefined) Faye.Event.on(Faye.ENV, 'beforeunload', abort);
2390
2478
 
2391
2479
  xhr.onreadystatechange = function() {
2392
2480
  if (!xhr || xhr.readyState !== 4) return;
2393
2481
 
2394
- var parsedMessage = null,
2395
- status = xhr.status,
2396
- text = xhr.responseText,
2397
- successful = (status >= 200 && status < 300) || status === 304 || status === 1223;
2482
+ var replies = null,
2483
+ status = xhr.status,
2484
+ text = xhr.responseText,
2485
+ successful = (status >= 200 && status < 300) || status === 304 || status === 1223;
2398
2486
 
2399
- Faye.Event.detach(Faye.ENV, 'beforeunload', abort);
2487
+ if (Faye.ENV.onbeforeunload !== undefined) Faye.Event.detach(Faye.ENV, 'beforeunload', abort);
2400
2488
  xhr.onreadystatechange = function() {};
2401
2489
  xhr = null;
2402
2490
 
2403
- if (!successful) return self.handleError(envelopes);
2491
+ if (!successful) return self._handleError(messages);
2404
2492
 
2405
2493
  try {
2406
- parsedMessage = JSON.parse(text);
2494
+ replies = JSON.parse(text);
2407
2495
  } catch (e) {}
2408
2496
 
2409
- if (parsedMessage)
2410
- self.receive(envelopes, parsedMessage);
2497
+ if (replies)
2498
+ self._receive(replies);
2411
2499
  else
2412
- self.handleError(envelopes);
2500
+ self._handleError(messages);
2413
2501
  };
2414
2502
 
2415
- xhr.send(this.encode(envelopes));
2503
+ xhr.send(this.encode(messages));
2504
+ return xhr;
2416
2505
  }
2417
2506
  }), {
2418
- isUsable: function(client, endpoint, callback, context) {
2507
+ isUsable: function(dispatcher, endpoint, callback, context) {
2419
2508
  callback.call(context, Faye.URI.isSameOrigin(endpoint));
2420
2509
  }
2421
2510
  });
@@ -2423,15 +2512,14 @@ Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, {
2423
2512
  Faye.Transport.register('long-polling', Faye.Transport.XHR);
2424
2513
 
2425
2514
  Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, {
2426
- encode: function(envelopes) {
2427
- var messages = Faye.map(envelopes, function(e) { return e.message });
2515
+ encode: function(messages) {
2428
2516
  return 'message=' + encodeURIComponent(Faye.toJSON(messages));
2429
2517
  },
2430
2518
 
2431
- request: function(envelopes) {
2519
+ request: function(messages) {
2432
2520
  var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest,
2433
2521
  xhr = new xhrClass(),
2434
- headers = this._client.headers,
2522
+ headers = this._dispatcher.headers,
2435
2523
  self = this,
2436
2524
  key;
2437
2525
 
@@ -2452,29 +2540,30 @@ Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, {
2452
2540
  };
2453
2541
 
2454
2542
  xhr.onload = function() {
2455
- var parsedMessage = null;
2543
+ var replies = null;
2456
2544
  try {
2457
- parsedMessage = JSON.parse(xhr.responseText);
2545
+ replies = JSON.parse(xhr.responseText);
2458
2546
  } catch (e) {}
2459
2547
 
2460
2548
  cleanUp();
2461
2549
 
2462
- if (parsedMessage)
2463
- self.receive(envelopes, parsedMessage);
2550
+ if (replies)
2551
+ self._receive(replies);
2464
2552
  else
2465
- self.handleError(envelopes);
2553
+ self._handleError(messages);
2466
2554
  };
2467
2555
 
2468
2556
  xhr.onerror = xhr.ontimeout = function() {
2469
2557
  cleanUp();
2470
- self.handleError(envelopes);
2558
+ self._handleError(messages);
2471
2559
  };
2472
2560
 
2473
2561
  xhr.onprogress = function() {};
2474
- xhr.send(this.encode(envelopes));
2562
+ xhr.send(this.encode(messages));
2563
+ return xhr;
2475
2564
  }
2476
2565
  }), {
2477
- isUsable: function(client, endpoint, callback, context) {
2566
+ isUsable: function(dispatcher, endpoint, callback, context) {
2478
2567
  if (Faye.URI.isSameOrigin(endpoint))
2479
2568
  return callback.call(context, false);
2480
2569
 
@@ -2492,17 +2581,15 @@ Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, {
2492
2581
  Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS);
2493
2582
 
2494
2583
  Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, {
2495
- encode: function(envelopes) {
2496
- var messages = Faye.map(envelopes, function(e) { return e.message });
2584
+ encode: function(messages) {
2497
2585
  var url = Faye.copyObject(this.endpoint);
2498
2586
  url.query.message = Faye.toJSON(messages);
2499
2587
  url.query.jsonp = '__jsonp' + Faye.Transport.JSONP._cbCount + '__';
2500
2588
  return Faye.URI.stringify(url);
2501
2589
  },
2502
2590
 
2503
- request: function(envelopes) {
2504
- var messages = Faye.map(envelopes, function(e) { return e.message }),
2505
- head = document.getElementsByTagName('head')[0],
2591
+ request: function(messages) {
2592
+ var head = document.getElementsByTagName('head')[0],
2506
2593
  script = document.createElement('script'),
2507
2594
  callbackName = Faye.Transport.JSONP.getCallbackName(),
2508
2595
  endpoint = Faye.copyObject(this.endpoint),
@@ -2511,17 +2598,28 @@ Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, {
2511
2598
  endpoint.query.message = Faye.toJSON(messages);
2512
2599
  endpoint.query.jsonp = callbackName;
2513
2600
 
2514
- Faye.ENV[callbackName] = function(data) {
2601
+ var cleanup = function() {
2515
2602
  if (!Faye.ENV[callbackName]) return false;
2516
2603
  Faye.ENV[callbackName] = undefined;
2517
2604
  try { delete Faye.ENV[callbackName] } catch (e) {}
2518
2605
  script.parentNode.removeChild(script);
2519
- self.receive(envelopes, data);
2606
+ };
2607
+
2608
+ Faye.ENV[callbackName] = function(replies) {
2609
+ cleanup();
2610
+ self._receive(replies);
2520
2611
  };
2521
2612
 
2522
2613
  script.type = 'text/javascript';
2523
2614
  script.src = Faye.URI.stringify(endpoint);
2524
2615
  head.appendChild(script);
2616
+
2617
+ script.onerror = function() {
2618
+ cleanup();
2619
+ self._handleError(messages);
2620
+ };
2621
+
2622
+ return {abort: cleanup};
2525
2623
  }
2526
2624
  }), {
2527
2625
  _cbCount: 0,
@@ -2531,11 +2629,11 @@ Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, {
2531
2629
  return '__jsonp' + this._cbCount + '__';
2532
2630
  },
2533
2631
 
2534
- isUsable: function(client, endpoint, callback, context) {
2632
+ isUsable: function(dispatcher, endpoint, callback, context) {
2535
2633
  callback.call(context, true);
2536
2634
  }
2537
2635
  });
2538
2636
 
2539
2637
  Faye.Transport.register('callback-polling', Faye.Transport.JSONP);
2540
2638
 
2541
- })();
2639
+ })();