faye-authentication 0.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ })();