forked from balderdashy/backbone-to-sails
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsails.io.backbone.js
332 lines (221 loc) · 7.02 KB
/
sails.io.backbone.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*!
* Backbone SDK for Sails and Socket.io
* (override for Backbone.sync and Backbone.Collection)
*
* c. 2013 @mikermcneil
* MIT Licensed
*
*
* Inspired by:
* backbone.iobind - Backbone.sync replacement
* Copyright(c) 2011 Jake Luer <[email protected]>
* MIT Licensed
*/
(function () {
// The active `socket`
var socket;
// Also keep track of where it came from
var socketSrc;
// Used to simplify app-level connection logic-- i.e. so you don't
// have to wait for the socket to be connected to start trying to
// synchronize data.
var requestQueue = [];
// A `setTimeout` that, if necessary, is used to check if the socket
// is ready yet (polls).
var socketTimer;
/**
* _acquireSocket()
*
* Grab hold of our active socket object, set it on `socket` closure variable above.
* (if your connected socket exists on a non-standard variable, change here)
*
* @api private
*/
var _acquireSocket = function ( ) {
if (socket) return;
if (Backbone.socket) {
socket = Backbone.socket;
socketSrc = '`Backbone.socket`';
}
else if (window.socket) {
socket = window.socket;
socketSrc = '`window.socket`';
}
// The first time a socket is acquired, bind comet listener
if (socket) _bindCometListener();
};
/**
* Checks if the socket is ready- if so, runs the request queue.
* If not, sets the timer again.
*/
var _keepTryingToRunRequestQueue = function ( ) {
clearTimeout(socketTimer);
// Check if socket is connected (synchronous)
var socketIsConnected = socket.socket && socket.socket.connected;
if (socketIsConnected) {
// Run the request queue
_.each(requestQueue, function (request) {
Backbone.sync(request.method, request.model, request.options);
});
}
else {
// Reset the timer
socketTimer = setTimeout(_keepTryingToRunRequestQueue, 250);
// TODO:
// After a configurable period of time, if the socket has still not connected,
// throw an error, since the `socket` might be improperly configured.
// throw new Error(
// '\n' +
// 'Backbone is trying to communicate with the Sails server using '+ socketSrc +',\n'+
// 'but its `connected` property is still set to false.\n' +
// 'But maybe Socket.io just hasn\'t finished connecting yet?\n' +
// '\n' +
// 'You might check to be sure you\'re waiting for `socket.on(\'connect\')`\n' +
// 'before using sync methods on your Backbone models and collections.'
// );
}
};
// Set up `async.until`-esque mechanism which will attempt to acquire a socket.
var attempts = 0,
maxAttempts = 3,
interval = 1500,
initialInterval = 250;
var _attemptToAcquireSocket = function () {
if ( socket ) return;
attempts++;
_acquireSocket();
if (attempts >= maxAttempts) return;
setTimeout(_attemptToAcquireSocket, interval);
};
// Attempt to acquire the socket more quickly the first time,
// in case the user is on a fast connection and it's available.
setTimeout(_attemptToAcquireSocket, initialInterval);
/**
* Backbone.on('comet', ...)
*
* Since Backbone is already a listener (extends Backbone.Events)
* all we have to do is trigger the event on the Backbone global when
* we receive a new message from the server.
*
* I realize this doesn't do a whole lot right now-- that's ok.
* Let's start light and layer on additional functionality carefully.
*/
var _bindCometListener = function socketAcquiredForFirstTime () {
socket.on('message', function cometMessageReceived (message) {
Backbone.trigger('comet', message);
});
};
/**
* # Backbone.sync
*
* Replaces default Backbone.sync function with socket.io transport
*
* @param {String} method
* @param {Backbone.Model|Backbone.Collection} model
* @param {Object} options
*
* @name sync
*/
Backbone.sync = function (method, model, options) {
// Clone options to avoid smashing anything unexpected
options = _.extend({}, options);
// If socket is not defined yet, try to grab it again.
_acquireSocket();
// Handle missing socket
if (!socket) {
throw new Error(
'\n' +
'Backbone cannot find a suitable `socket` object.\n' +
'This SDK expects the active socket to be located at `window.socket`, '+
'`Backbone.socket` or the `socket` property\n' +
'of the Backbone model or collection attempting to communicate w/ the server.\n'
);
}
// Ensures the socket is connected and able to communicate w/ the server.
//
var socketIsConnected = socket.socket && socket.socket.connected;
if ( !socketIsConnected ) {
// If the socket is not connected, the request is queued
// (so it can be replayed when the socket comes online.)
requestQueue.push({
method: method,
model: model,
options: options
});
// If we haven't already, start polling the socket to see if it's ready
_keepTryingToRunRequestQueue();
return;
}
// Get the actual URL (call `.url()` if it's a function)
var url;
if (options.url) {
url = _.result(options, 'url');
}
else if (model.url) {
url = _.result(model, 'url');
}
// Throw an error when a URL is needed, and none is supplied.
// Copied from backbone.js#1558
else throw new Error('A "url" property or function must be specified');
// Build parameters to send to the server
var params = {};
if ( !options.data && model ) {
params = options.attrs || model.toJSON(options) || {};
}
if (options.patch === true && _.isObject(options.data) && options.data.id === null && model) {
params.id = model.id;
}
if (_.isObject(options.data)) {
_(params).extend(options.data);
}
// Map Backbone's concept of CRUD methods to HTTP verbs
var verb;
switch (method) {
case 'create':
verb = 'post';
break;
case 'read':
verb = 'get';
break;
case 'update':
verb = 'put';
break;
default:
verb = method;
}
// Send a simulated HTTP request to Sails via Socket.io
var simulatedXHR =
socket.request(url, params, function serverResponded ( response ) {
if (options.success) options.success(response);
}, verb);
// Trigget the `request` event on the Backbone model
model.trigger('request', model, simulatedXHR, options);
return simulatedXHR;
};
/**
* TODO:
* Replace sails.io.js with `jQuery-to-sails.js`, which can be a prerequisite of
* this SDK.
*
* Will allow for better client-side error handling, proper simulation of $.ajax,
* easier client-side support of headers, and overall a better experience.
*/
/*
var simulatedXHR = $.Deferred();
// Send a simulated HTTP request to Sails via Socket.io
io.emit(verb, params, function serverResponded (err, response) {
if (err) {
if (options.error) options.error(err);
simulatedXHR.reject();
return;
}
if (options.success) options.success(response);
simulatedXHR.resolve();
});
var promise = simulatedXHR.promise();
// Trigger the model's `request` event
model.trigger('request', model, promise, options);
// Return a promise to allow chaining of sync methods.
return promise;
*/
})();