Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/externals/javelin/lib/Request.js
12242 views
1
/**
2
* @requires javelin-install
3
* javelin-stratcom
4
* javelin-util
5
* javelin-behavior
6
* javelin-json
7
* javelin-dom
8
* javelin-resource
9
* javelin-routable
10
* @provides javelin-request
11
* @javelin
12
*/
13
14
/**
15
* Make basic AJAX XMLHTTPRequests.
16
*/
17
JX.install('Request', {
18
construct : function(uri, handler) {
19
this.setURI(uri);
20
if (handler) {
21
this.listen('done', handler);
22
}
23
},
24
25
events : ['start', 'open', 'send', 'statechange', 'done', 'error', 'finally',
26
'uploadprogress'],
27
28
members : {
29
30
_xhrkey : null,
31
_transport : null,
32
_sent : false,
33
_finished : false,
34
_block : null,
35
_data : null,
36
37
_getSameOriginTransport : function() {
38
try {
39
try {
40
return new XMLHttpRequest();
41
} catch (x) {
42
return new ActiveXObject('Msxml2.XMLHTTP');
43
}
44
} catch (x) {
45
return new ActiveXObject('Microsoft.XMLHTTP');
46
}
47
},
48
49
_getCORSTransport : function() {
50
try {
51
var xport = new XMLHttpRequest();
52
if ('withCredentials' in xport) {
53
// XHR supports CORS
54
} else if (typeof XDomainRequest != 'undefined') {
55
xport = new XDomainRequest();
56
}
57
return xport;
58
} catch (x) {
59
return new XDomainRequest();
60
}
61
},
62
63
getTransport : function() {
64
if (!this._transport) {
65
this._transport = this.getCORS() ? this._getCORSTransport() :
66
this._getSameOriginTransport();
67
}
68
return this._transport;
69
},
70
71
getRoutable: function() {
72
var routable = new JX.Routable();
73
routable.listen('start', JX.bind(this, function() {
74
// Pass the event to allow other listeners to "start" to configure this
75
// request before it fires.
76
JX.Stratcom.pass(JX.Stratcom.context());
77
this.send();
78
}));
79
this.listen('finally', JX.bind(routable, routable.done));
80
return routable;
81
},
82
83
send : function() {
84
if (this._sent || this._finished) {
85
if (__DEV__) {
86
if (this._sent) {
87
JX.$E(
88
'JX.Request.send(): ' +
89
'attempting to send a Request that has already been sent.');
90
}
91
if (this._finished) {
92
JX.$E(
93
'JX.Request.send(): ' +
94
'attempting to send a Request that has finished or aborted.');
95
}
96
}
97
return;
98
}
99
100
// Fire the "start" event before doing anything. A listener may
101
// perform pre-processing or validation on this request
102
this.invoke('start', this);
103
if (this._finished) {
104
return;
105
}
106
107
var xport = this.getTransport();
108
xport.onreadystatechange = JX.bind(this, this._onreadystatechange);
109
if (xport.upload) {
110
xport.upload.onprogress = JX.bind(this, this._onuploadprogress);
111
}
112
113
var method = this.getMethod().toUpperCase();
114
115
if (__DEV__) {
116
if (this.getRawData()) {
117
if (method != 'POST') {
118
JX.$E(
119
'JX.Request.send(): ' +
120
'attempting to send post data over GET. You must use POST.');
121
}
122
}
123
}
124
125
var list_of_pairs = this._data || [];
126
list_of_pairs.push(['__ajax__', true]);
127
128
this._block = JX.Stratcom.allocateMetadataBlock();
129
list_of_pairs.push(['__metablock__', this._block]);
130
131
var q = (this.getDataSerializer() ||
132
JX.Request.defaultDataSerializer)(list_of_pairs);
133
var uri = this.getURI();
134
135
// If we're sending a file, submit the metadata via the URI instead of
136
// via the request body, because the entire post body will be consumed by
137
// the file content.
138
if (method == 'GET' || this.getRawData()) {
139
uri += ((uri.indexOf('?') === -1) ? '?' : '&') + q;
140
}
141
142
if (this.getTimeout()) {
143
this._timer = setTimeout(
144
JX.bind(
145
this,
146
this._fail,
147
JX.Request.ERROR_TIMEOUT),
148
this.getTimeout());
149
}
150
151
xport.open(method, uri, true);
152
153
// Must happen after xport.open so that listeners can modify the transport
154
// Some transport properties can only be set after the transport is open
155
this.invoke('open', this);
156
if (this._finished) {
157
return;
158
}
159
160
this.invoke('send', this);
161
if (this._finished) {
162
return;
163
}
164
165
if (method == 'POST') {
166
if (this.getRawData()) {
167
xport.send(this.getRawData());
168
} else {
169
xport.setRequestHeader(
170
'Content-Type',
171
'application/x-www-form-urlencoded');
172
xport.send(q);
173
}
174
} else {
175
xport.send(null);
176
}
177
178
this._sent = true;
179
},
180
181
abort : function() {
182
this._cleanup();
183
},
184
185
_onuploadprogress : function(progress) {
186
this.invoke('uploadprogress', progress);
187
},
188
189
_onreadystatechange : function() {
190
var xport = this.getTransport();
191
var response;
192
try {
193
this.invoke('statechange', this);
194
if (this._finished) {
195
return;
196
}
197
if (xport.readyState != 4) {
198
return;
199
}
200
// XHR requests to 'file:///' domains return 0 for success, which is why
201
// we treat it as a good result in addition to HTTP 2XX responses.
202
if (xport.status !== 0 && (xport.status < 200 || xport.status >= 300)) {
203
this._fail();
204
return;
205
}
206
207
if (__DEV__) {
208
var expect_guard = this.getExpectCSRFGuard();
209
210
if (!xport.responseText.length) {
211
JX.$E(
212
'JX.Request("'+this.getURI()+'", ...): '+
213
'server returned an empty response.');
214
}
215
if (expect_guard && xport.responseText.indexOf('for (;;);') !== 0) {
216
JX.$E(
217
'JX.Request("'+this.getURI()+'", ...): '+
218
'server returned an invalid response.');
219
}
220
if (expect_guard && xport.responseText == 'for (;;);') {
221
JX.$E(
222
'JX.Request("'+this.getURI()+'", ...): '+
223
'server returned an empty response.');
224
}
225
}
226
227
response = this._extractResponse(xport);
228
if (!response) {
229
JX.$E(
230
'JX.Request("'+this.getURI()+'", ...): '+
231
'server returned an invalid response.');
232
}
233
} catch (exception) {
234
235
if (__DEV__) {
236
JX.log(
237
'JX.Request("'+this.getURI()+'", ...): '+
238
'caught exception processing response: '+exception);
239
}
240
this._fail();
241
return;
242
}
243
244
try {
245
this._handleResponse(response);
246
this._cleanup();
247
} catch (exception) {
248
// In Firefox+Firebug, at least, something eats these. :/
249
setTimeout(function() {
250
throw exception;
251
}, 0);
252
}
253
},
254
255
_extractResponse : function(xport) {
256
var text = xport.responseText;
257
258
if (this.getExpectCSRFGuard()) {
259
text = text.substring('for (;;);'.length);
260
}
261
262
var type = this.getResponseType().toUpperCase();
263
if (type == 'TEXT') {
264
return text;
265
} else if (type == 'JSON' || type == 'JAVELIN') {
266
return JX.JSON.parse(text);
267
} else if (type == 'XML') {
268
var doc;
269
try {
270
if (typeof DOMParser != 'undefined') {
271
var parser = new DOMParser();
272
doc = parser.parseFromString(text, 'text/xml');
273
} else { // IE
274
// an XDomainRequest
275
doc = new ActiveXObject('Microsoft.XMLDOM');
276
doc.async = false;
277
doc.loadXML(xport.responseText);
278
}
279
280
return doc.documentElement;
281
} catch (exception) {
282
if (__DEV__) {
283
JX.log(
284
'JX.Request("'+this.getURI()+'", ...): '+
285
'caught exception extracting response: '+exception);
286
}
287
this._fail();
288
return null;
289
}
290
}
291
292
if (__DEV__) {
293
JX.$E(
294
'JX.Request("'+this.getURI()+'", ...): '+
295
'unrecognized response type.');
296
}
297
return null;
298
},
299
300
_fail : function(error) {
301
this._cleanup();
302
303
this.invoke('error', error, this);
304
this.invoke('finally');
305
},
306
307
_done : function(response) {
308
this._cleanup();
309
310
if (response.onload) {
311
for (var ii = 0; ii < response.onload.length; ii++) {
312
(new Function(response.onload[ii]))();
313
}
314
}
315
316
var payload;
317
if (this.getRaw()) {
318
payload = response;
319
} else {
320
payload = response.payload;
321
JX.Request._parseResponsePayload(payload);
322
}
323
324
this.invoke('done', payload, this);
325
this.invoke('finally');
326
},
327
328
_cleanup : function() {
329
this._finished = true;
330
clearTimeout(this._timer);
331
this._timer = null;
332
333
// Should not abort the transport request if it has already completed
334
// Otherwise, we may see an "HTTP request aborted" error in the console
335
// despite it possibly having succeeded.
336
if (this._transport && this._transport.readyState != 4) {
337
this._transport.abort();
338
}
339
},
340
341
setData : function(dictionary) {
342
this._data = null;
343
this.addData(dictionary);
344
return this;
345
},
346
347
addData : function(dictionary) {
348
if (!this._data) {
349
this._data = [];
350
}
351
for (var k in dictionary) {
352
this._data.push([k, dictionary[k]]);
353
}
354
return this;
355
},
356
357
setDataWithListOfPairs : function(list_of_pairs) {
358
this._data = list_of_pairs;
359
return this;
360
},
361
362
_handleResponse : function(response) {
363
if (this.getResponseType().toUpperCase() == 'JAVELIN') {
364
if (response.error) {
365
this._fail(response.error);
366
} else {
367
JX.Stratcom.mergeData(
368
this._block,
369
response.javelin_metadata || {});
370
371
var when_complete = JX.bind(this, function() {
372
this._done(response);
373
JX.initBehaviors(response.javelin_behaviors || {});
374
});
375
376
if (response.javelin_resources) {
377
JX.Resource.load(response.javelin_resources, when_complete);
378
} else {
379
when_complete();
380
}
381
}
382
} else {
383
this._cleanup();
384
this.invoke('done', response, this);
385
this.invoke('finally');
386
}
387
}
388
},
389
390
statics : {
391
ERROR_TIMEOUT : -9000,
392
defaultDataSerializer : function(list_of_pairs) {
393
var uri = [];
394
for (var ii = 0; ii < list_of_pairs.length; ii++) {
395
var pair = list_of_pairs[ii];
396
397
if (pair[1] === null) {
398
continue;
399
}
400
401
var name = encodeURIComponent(pair[0]);
402
var value = encodeURIComponent(pair[1]);
403
uri.push(name + '=' + value);
404
}
405
return uri.join('&');
406
},
407
408
/**
409
* When we receive a JSON blob, parse it to introduce meaningful objects
410
* where there are magic keys for placeholders.
411
*
412
* Objects with the magic key '__html' are translated into JX.HTML objects.
413
*
414
* This function destructively modifies its input.
415
*/
416
_parseResponsePayload: function(parent, index) {
417
var recurse = JX.Request._parseResponsePayload;
418
var obj = (typeof index !== 'undefined') ? parent[index] : parent;
419
if (JX.isArray(obj)) {
420
for (var ii = 0; ii < obj.length; ii++) {
421
recurse(obj, ii);
422
}
423
} else if (obj && typeof obj == 'object') {
424
if (('__html' in obj) && (obj.__html !== null)) {
425
parent[index] = JX.$H(obj.__html);
426
} else {
427
for (var key in obj) {
428
recurse(obj, key);
429
}
430
}
431
}
432
}
433
},
434
435
properties : {
436
URI : null,
437
dataSerializer : null,
438
/**
439
* Configure which HTTP method to use for the request. Permissible values
440
* are "POST" (default) or "GET".
441
*
442
* @param string HTTP method, one of "POST" or "GET".
443
*/
444
method : 'POST',
445
/**
446
* Set the data parameter of transport.send. Useful if you want to send a
447
* file or FormData. Not that you cannot send raw data and data at the same
448
* time.
449
*
450
* @param Data, argument to transport.send
451
*/
452
rawData: null,
453
raw : false,
454
455
/**
456
* Configure a timeout, in milliseconds. If the request has not resolved
457
* (either with success or with an error) within the provided timeframe,
458
* it will automatically fail with error JX.Request.ERROR_TIMEOUT.
459
*
460
* @param int Timeout, in milliseconds (e.g. 3000 = 3 seconds).
461
*/
462
timeout : null,
463
464
/**
465
* Whether or not we should expect the CSRF guard in the response.
466
*
467
* @param bool
468
*/
469
expectCSRFGuard : true,
470
471
/**
472
* Whether it should be a CORS (Cross-Origin Resource Sharing) request to
473
* a third party domain other than the current site.
474
*
475
* @param bool
476
*/
477
CORS : false,
478
479
/**
480
* Type of the response.
481
*
482
* @param enum 'JAVELIN', 'JSON', 'XML', 'TEXT'
483
*/
484
responseType : 'JAVELIN'
485
}
486
487
});
488
489