5209 lines
607 KiB
JavaScript
5209 lines
607 KiB
JavaScript
|
//////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// This is a generated file. You can view the original //
|
||
|
// source in your browser if your browser supports source maps. //
|
||
|
// //
|
||
|
// If you are using Chrome, open the Developer Tools and click the gear //
|
||
|
// icon in its lower right corner. In the General Settings panel, turn //
|
||
|
// on 'Enable source maps'. //
|
||
|
// //
|
||
|
// If you are using Firefox 23, go to `about:config` and set the //
|
||
|
// `devtools.debugger.source-maps-enabled` preference to true. //
|
||
|
// (The preference should be on by default in Firefox 24; versions //
|
||
|
// older than 23 do not support source maps.) //
|
||
|
// //
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
/* Imports */
|
||
|
var Meteor = Package.meteor.Meteor;
|
||
|
var check = Package.check.check;
|
||
|
var Match = Package.check.Match;
|
||
|
var Random = Package.random.Random;
|
||
|
var EJSON = Package.ejson.EJSON;
|
||
|
var JSON = Package.json.JSON;
|
||
|
var _ = Package.underscore._;
|
||
|
var Tracker = Package.tracker.Tracker;
|
||
|
var Deps = Package.tracker.Deps;
|
||
|
var Log = Package.logging.Log;
|
||
|
var Retry = Package.retry.Retry;
|
||
|
var LocalCollection = Package.minimongo.LocalCollection;
|
||
|
var Minimongo = Package.minimongo.Minimongo;
|
||
|
|
||
|
/* Package-scope variables */
|
||
|
var DDP, LivedataTest, SockJS, toSockjsUrl, toWebsocketUrl, Heartbeat, SUPPORTED_DDP_VERSIONS, MethodInvocation, parseDDP, stringifyDDP, RandomStream, makeRpcSeed, allConnections;
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/common.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
/** // 1
|
||
|
* @namespace DDP // 2
|
||
|
* @summary The namespace for DDP-related methods. // 3
|
||
|
*/ // 4
|
||
|
DDP = {}; // 5
|
||
|
LivedataTest = {}; // 6
|
||
|
// 7
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/sockjs-0.3.4.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// XXX METEOR changes in <METEOR> // 1
|
||
|
// 2
|
||
|
/* SockJS client, version 0.3.4, http://sockjs.org, MIT License // 3
|
||
|
// 4
|
||
|
Copyright (c) 2011-2012 VMware, Inc. // 5
|
||
|
// 6
|
||
|
Permission is hereby granted, free of charge, to any person obtaining a copy // 7
|
||
|
of this software and associated documentation files (the "Software"), to deal // 8
|
||
|
in the Software without restriction, including without limitation the rights // 9
|
||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // 10
|
||
|
copies of the Software, and to permit persons to whom the Software is // 11
|
||
|
furnished to do so, subject to the following conditions: // 12
|
||
|
// 13
|
||
|
The above copyright notice and this permission notice shall be included in // 14
|
||
|
all copies or substantial portions of the Software. // 15
|
||
|
// 16
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // 17
|
||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // 18
|
||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // 19
|
||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 20
|
||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // 21
|
||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // 22
|
||
|
THE SOFTWARE. // 23
|
||
|
*/ // 24
|
||
|
// 25
|
||
|
// <METEOR> Commented out JSO implementation (use json package instead). // 26
|
||
|
// JSON2 by Douglas Crockford (minified). // 27
|
||
|
// var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()
|
||
|
// </METEOR> // 29
|
||
|
// 30
|
||
|
// [*] Including lib/index.js // 31
|
||
|
// Public object // 32
|
||
|
SockJS = (function(){ // 33
|
||
|
var _document = document; // 34
|
||
|
var _window = window; // 35
|
||
|
var utils = {}; // 36
|
||
|
// 37
|
||
|
// 38
|
||
|
// [*] Including lib/reventtarget.js // 39
|
||
|
/* // 40
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 41
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 42
|
||
|
* // 43
|
||
|
* For the license see COPYING. // 44
|
||
|
* ***** END LICENSE BLOCK ***** // 45
|
||
|
*/ // 46
|
||
|
// 47
|
||
|
/* Simplified implementation of DOM2 EventTarget. // 48
|
||
|
* http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget // 49
|
||
|
*/ // 50
|
||
|
var REventTarget = function() {}; // 51
|
||
|
REventTarget.prototype.addEventListener = function (eventType, listener) { // 52
|
||
|
if(!this._listeners) { // 53
|
||
|
this._listeners = {}; // 54
|
||
|
} // 55
|
||
|
if(!(eventType in this._listeners)) { // 56
|
||
|
this._listeners[eventType] = []; // 57
|
||
|
} // 58
|
||
|
var arr = this._listeners[eventType]; // 59
|
||
|
if(utils.arrIndexOf(arr, listener) === -1) { // 60
|
||
|
arr.push(listener); // 61
|
||
|
} // 62
|
||
|
return; // 63
|
||
|
}; // 64
|
||
|
// 65
|
||
|
REventTarget.prototype.removeEventListener = function (eventType, listener) { // 66
|
||
|
if(!(this._listeners && (eventType in this._listeners))) { // 67
|
||
|
return; // 68
|
||
|
} // 69
|
||
|
var arr = this._listeners[eventType]; // 70
|
||
|
var idx = utils.arrIndexOf(arr, listener); // 71
|
||
|
if (idx !== -1) { // 72
|
||
|
if(arr.length > 1) { // 73
|
||
|
this._listeners[eventType] = arr.slice(0, idx).concat( arr.slice(idx+1) ); // 74
|
||
|
} else { // 75
|
||
|
delete this._listeners[eventType]; // 76
|
||
|
} // 77
|
||
|
return; // 78
|
||
|
} // 79
|
||
|
return; // 80
|
||
|
}; // 81
|
||
|
// 82
|
||
|
REventTarget.prototype.dispatchEvent = function (event) { // 83
|
||
|
var t = event.type; // 84
|
||
|
var args = Array.prototype.slice.call(arguments, 0); // 85
|
||
|
if (this['on'+t]) { // 86
|
||
|
this['on'+t].apply(this, args); // 87
|
||
|
} // 88
|
||
|
if (this._listeners && t in this._listeners) { // 89
|
||
|
for(var i=0; i < this._listeners[t].length; i++) { // 90
|
||
|
this._listeners[t][i].apply(this, args); // 91
|
||
|
} // 92
|
||
|
} // 93
|
||
|
}; // 94
|
||
|
// [*] End of lib/reventtarget.js // 95
|
||
|
// 96
|
||
|
// 97
|
||
|
// [*] Including lib/simpleevent.js // 98
|
||
|
/* // 99
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 100
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 101
|
||
|
* // 102
|
||
|
* For the license see COPYING. // 103
|
||
|
* ***** END LICENSE BLOCK ***** // 104
|
||
|
*/ // 105
|
||
|
// 106
|
||
|
var SimpleEvent = function(type, obj) { // 107
|
||
|
this.type = type; // 108
|
||
|
if (typeof obj !== 'undefined') { // 109
|
||
|
for(var k in obj) { // 110
|
||
|
if (!obj.hasOwnProperty(k)) continue; // 111
|
||
|
this[k] = obj[k]; // 112
|
||
|
} // 113
|
||
|
} // 114
|
||
|
}; // 115
|
||
|
// 116
|
||
|
SimpleEvent.prototype.toString = function() { // 117
|
||
|
var r = []; // 118
|
||
|
for(var k in this) { // 119
|
||
|
if (!this.hasOwnProperty(k)) continue; // 120
|
||
|
var v = this[k]; // 121
|
||
|
if (typeof v === 'function') v = '[function]'; // 122
|
||
|
r.push(k + '=' + v); // 123
|
||
|
} // 124
|
||
|
return 'SimpleEvent(' + r.join(', ') + ')'; // 125
|
||
|
}; // 126
|
||
|
// [*] End of lib/simpleevent.js // 127
|
||
|
// 128
|
||
|
// 129
|
||
|
// [*] Including lib/eventemitter.js // 130
|
||
|
/* // 131
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 132
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 133
|
||
|
* // 134
|
||
|
* For the license see COPYING. // 135
|
||
|
* ***** END LICENSE BLOCK ***** // 136
|
||
|
*/ // 137
|
||
|
// 138
|
||
|
var EventEmitter = function(events) { // 139
|
||
|
var that = this; // 140
|
||
|
that._events = events || []; // 141
|
||
|
that._listeners = {}; // 142
|
||
|
}; // 143
|
||
|
EventEmitter.prototype.emit = function(type) { // 144
|
||
|
var that = this; // 145
|
||
|
that._verifyType(type); // 146
|
||
|
if (that._nuked) return; // 147
|
||
|
// 148
|
||
|
var args = Array.prototype.slice.call(arguments, 1); // 149
|
||
|
if (that['on'+type]) { // 150
|
||
|
that['on'+type].apply(that, args); // 151
|
||
|
} // 152
|
||
|
if (type in that._listeners) { // 153
|
||
|
for(var i = 0; i < that._listeners[type].length; i++) { // 154
|
||
|
that._listeners[type][i].apply(that, args); // 155
|
||
|
} // 156
|
||
|
} // 157
|
||
|
}; // 158
|
||
|
// 159
|
||
|
EventEmitter.prototype.on = function(type, callback) { // 160
|
||
|
var that = this; // 161
|
||
|
that._verifyType(type); // 162
|
||
|
if (that._nuked) return; // 163
|
||
|
// 164
|
||
|
if (!(type in that._listeners)) { // 165
|
||
|
that._listeners[type] = []; // 166
|
||
|
} // 167
|
||
|
that._listeners[type].push(callback); // 168
|
||
|
}; // 169
|
||
|
// 170
|
||
|
EventEmitter.prototype._verifyType = function(type) { // 171
|
||
|
var that = this; // 172
|
||
|
if (utils.arrIndexOf(that._events, type) === -1) { // 173
|
||
|
utils.log('Event ' + JSON.stringify(type) + // 174
|
||
|
' not listed ' + JSON.stringify(that._events) + // 175
|
||
|
' in ' + that); // 176
|
||
|
} // 177
|
||
|
}; // 178
|
||
|
// 179
|
||
|
EventEmitter.prototype.nuke = function() { // 180
|
||
|
var that = this; // 181
|
||
|
that._nuked = true; // 182
|
||
|
for(var i=0; i<that._events.length; i++) { // 183
|
||
|
delete that[that._events[i]]; // 184
|
||
|
} // 185
|
||
|
that._listeners = {}; // 186
|
||
|
}; // 187
|
||
|
// [*] End of lib/eventemitter.js // 188
|
||
|
// 189
|
||
|
// 190
|
||
|
// [*] Including lib/utils.js // 191
|
||
|
/* // 192
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 193
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 194
|
||
|
* // 195
|
||
|
* For the license see COPYING. // 196
|
||
|
* ***** END LICENSE BLOCK ***** // 197
|
||
|
*/ // 198
|
||
|
// 199
|
||
|
var random_string_chars = 'abcdefghijklmnopqrstuvwxyz0123456789_'; // 200
|
||
|
utils.random_string = function(length, max) { // 201
|
||
|
max = max || random_string_chars.length; // 202
|
||
|
var i, ret = []; // 203
|
||
|
for(i=0; i < length; i++) { // 204
|
||
|
ret.push( random_string_chars.substr(Math.floor(Math.random() * max),1) ); // 205
|
||
|
} // 206
|
||
|
return ret.join(''); // 207
|
||
|
}; // 208
|
||
|
utils.random_number = function(max) { // 209
|
||
|
return Math.floor(Math.random() * max); // 210
|
||
|
}; // 211
|
||
|
utils.random_number_string = function(max) { // 212
|
||
|
var t = (''+(max - 1)).length; // 213
|
||
|
var p = Array(t+1).join('0'); // 214
|
||
|
return (p + utils.random_number(max)).slice(-t); // 215
|
||
|
}; // 216
|
||
|
// 217
|
||
|
// Assuming that url looks like: http://asdasd:111/asd // 218
|
||
|
utils.getOrigin = function(url) { // 219
|
||
|
url += '/'; // 220
|
||
|
var parts = url.split('/').slice(0, 3); // 221
|
||
|
return parts.join('/'); // 222
|
||
|
}; // 223
|
||
|
// 224
|
||
|
utils.isSameOriginUrl = function(url_a, url_b) { // 225
|
||
|
// location.origin would do, but it's not always available. // 226
|
||
|
if (!url_b) url_b = _window.location.href; // 227
|
||
|
// 228
|
||
|
return (url_a.split('/').slice(0,3).join('/') // 229
|
||
|
=== // 230
|
||
|
url_b.split('/').slice(0,3).join('/')); // 231
|
||
|
}; // 232
|
||
|
// 233
|
||
|
// <METEOR> // 234
|
||
|
// https://github.com/sockjs/sockjs-client/issues/79 // 235
|
||
|
utils.isSameOriginScheme = function(url_a, url_b) { // 236
|
||
|
if (!url_b) url_b = _window.location.href; // 237
|
||
|
// 238
|
||
|
return (url_a.split(':')[0] // 239
|
||
|
=== // 240
|
||
|
url_b.split(':')[0]); // 241
|
||
|
}; // 242
|
||
|
// </METEOR> // 243
|
||
|
// 244
|
||
|
// 245
|
||
|
utils.getParentDomain = function(url) { // 246
|
||
|
// ipv4 ip address // 247
|
||
|
if (/^[0-9.]*$/.test(url)) return url; // 248
|
||
|
// ipv6 ip address // 249
|
||
|
if (/^\[/.test(url)) return url; // 250
|
||
|
// no dots // 251
|
||
|
if (!(/[.]/.test(url))) return url; // 252
|
||
|
// 253
|
||
|
var parts = url.split('.').slice(1); // 254
|
||
|
return parts.join('.'); // 255
|
||
|
}; // 256
|
||
|
// 257
|
||
|
utils.objectExtend = function(dst, src) { // 258
|
||
|
for(var k in src) { // 259
|
||
|
if (src.hasOwnProperty(k)) { // 260
|
||
|
dst[k] = src[k]; // 261
|
||
|
} // 262
|
||
|
} // 263
|
||
|
return dst; // 264
|
||
|
}; // 265
|
||
|
// 266
|
||
|
var WPrefix = '_jp'; // 267
|
||
|
// 268
|
||
|
utils.polluteGlobalNamespace = function() { // 269
|
||
|
if (!(WPrefix in _window)) { // 270
|
||
|
_window[WPrefix] = {}; // 271
|
||
|
} // 272
|
||
|
}; // 273
|
||
|
// 274
|
||
|
utils.closeFrame = function (code, reason) { // 275
|
||
|
return 'c'+JSON.stringify([code, reason]); // 276
|
||
|
}; // 277
|
||
|
// 278
|
||
|
utils.userSetCode = function (code) { // 279
|
||
|
return code === 1000 || (code >= 3000 && code <= 4999); // 280
|
||
|
}; // 281
|
||
|
// 282
|
||
|
// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/ // 283
|
||
|
// and RFC 2988. // 284
|
||
|
utils.countRTO = function (rtt) { // 285
|
||
|
var rto; // 286
|
||
|
if (rtt > 100) { // 287
|
||
|
rto = 3 * rtt; // rto > 300msec // 288
|
||
|
} else { // 289
|
||
|
rto = rtt + 200; // 200msec < rto <= 300msec // 290
|
||
|
} // 291
|
||
|
return rto; // 292
|
||
|
} // 293
|
||
|
// 294
|
||
|
utils.log = function() { // 295
|
||
|
if (_window.console && console.log && console.log.apply) { // 296
|
||
|
console.log.apply(console, arguments); // 297
|
||
|
} // 298
|
||
|
}; // 299
|
||
|
// 300
|
||
|
utils.bind = function(fun, that) { // 301
|
||
|
if (fun.bind) { // 302
|
||
|
return fun.bind(that); // 303
|
||
|
} else { // 304
|
||
|
return function() { // 305
|
||
|
return fun.apply(that, arguments); // 306
|
||
|
}; // 307
|
||
|
} // 308
|
||
|
}; // 309
|
||
|
// 310
|
||
|
utils.flatUrl = function(url) { // 311
|
||
|
return url.indexOf('?') === -1 && url.indexOf('#') === -1; // 312
|
||
|
}; // 313
|
||
|
// 314
|
||
|
// `relativeTo` is an optional absolute URL. If provided, `url` will be // 315
|
||
|
// interpreted relative to `relativeTo`. Defaults to `document.location`. // 316
|
||
|
// <METEOR> // 317
|
||
|
utils.amendUrl = function(url, relativeTo) { // 318
|
||
|
var baseUrl; // 319
|
||
|
if (relativeTo === undefined) { // 320
|
||
|
baseUrl = _document.location; // 321
|
||
|
} else { // 322
|
||
|
var protocolMatch = /^([a-z0-9.+-]+:)/i.exec(relativeTo); // 323
|
||
|
if (protocolMatch) { // 324
|
||
|
var protocol = protocolMatch[0].toLowerCase(); // 325
|
||
|
var rest = relativeTo.substring(protocol.length); // 326
|
||
|
var hostMatch = /[a-z0-9\.-]+(:[0-9]+)?/.exec(rest); // 327
|
||
|
if (hostMatch) // 328
|
||
|
var host = hostMatch[0]; // 329
|
||
|
} // 330
|
||
|
if (! protocol || ! host) // 331
|
||
|
throw new Error("relativeTo must be an absolute url"); // 332
|
||
|
baseUrl = { // 333
|
||
|
protocol: protocol, // 334
|
||
|
host: host // 335
|
||
|
}; // 336
|
||
|
} // 337
|
||
|
if (!url) { // 338
|
||
|
throw new Error('Wrong url for SockJS'); // 339
|
||
|
} // 340
|
||
|
if (!utils.flatUrl(url)) { // 341
|
||
|
throw new Error('Only basic urls are supported in SockJS'); // 342
|
||
|
} // 343
|
||
|
// 344
|
||
|
// '//abc' --> 'http://abc' // 345
|
||
|
if (url.indexOf('//') === 0) { // 346
|
||
|
url = baseUrl.protocol + url; // 347
|
||
|
} // 348
|
||
|
// '/abc' --> 'http://localhost:1234/abc' // 349
|
||
|
if (url.indexOf('/') === 0) { // 350
|
||
|
url = baseUrl.protocol + '//' + baseUrl.host + url; // 351
|
||
|
} // 352
|
||
|
// </METEOR> // 353
|
||
|
// strip trailing slashes // 354
|
||
|
url = url.replace(/[/]+$/,''); // 355
|
||
|
// 356
|
||
|
// We have a full url here, with proto and host. For some browsers // 357
|
||
|
// http://localhost:80/ is not in the same origin as http://localhost/ // 358
|
||
|
// Remove explicit :80 or :443 in such cases. See #74 // 359
|
||
|
var parts = url.split("/"); // 360
|
||
|
if ((parts[0] === "http:" && /:80$/.test(parts[2])) || // 361
|
||
|
(parts[0] === "https:" && /:443$/.test(parts[2]))) { // 362
|
||
|
parts[2] = parts[2].replace(/:(80|443)$/, ""); // 363
|
||
|
} // 364
|
||
|
url = parts.join("/"); // 365
|
||
|
return url; // 366
|
||
|
}; // 367
|
||
|
// 368
|
||
|
// IE doesn't support [].indexOf. // 369
|
||
|
utils.arrIndexOf = function(arr, obj){ // 370
|
||
|
for(var i=0; i < arr.length; i++){ // 371
|
||
|
if(arr[i] === obj){ // 372
|
||
|
return i; // 373
|
||
|
} // 374
|
||
|
} // 375
|
||
|
return -1; // 376
|
||
|
}; // 377
|
||
|
// 378
|
||
|
utils.arrSkip = function(arr, obj) { // 379
|
||
|
var idx = utils.arrIndexOf(arr, obj); // 380
|
||
|
if (idx === -1) { // 381
|
||
|
return arr.slice(); // 382
|
||
|
} else { // 383
|
||
|
var dst = arr.slice(0, idx); // 384
|
||
|
return dst.concat(arr.slice(idx+1)); // 385
|
||
|
} // 386
|
||
|
}; // 387
|
||
|
// 388
|
||
|
// Via: https://gist.github.com/1133122/2121c601c5549155483f50be3da5305e83b8c5df // 389
|
||
|
utils.isArray = Array.isArray || function(value) { // 390
|
||
|
return {}.toString.call(value).indexOf('Array') >= 0 // 391
|
||
|
}; // 392
|
||
|
// 393
|
||
|
utils.delay = function(t, fun) { // 394
|
||
|
if(typeof t === 'function') { // 395
|
||
|
fun = t; // 396
|
||
|
t = 0; // 397
|
||
|
} // 398
|
||
|
return setTimeout(fun, t); // 399
|
||
|
}; // 400
|
||
|
// 401
|
||
|
// 402
|
||
|
// Chars worth escaping, as defined by Douglas Crockford: // 403
|
||
|
// https://github.com/douglascrockford/JSON-js/blob/47a9882cddeb1e8529e07af9736218075372b8ac/json2.js#L196 // 404
|
||
|
var json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||
|
json_lookup = { // 406
|
||
|
"\u0000":"\\u0000","\u0001":"\\u0001","\u0002":"\\u0002","\u0003":"\\u0003", // 407
|
||
|
"\u0004":"\\u0004","\u0005":"\\u0005","\u0006":"\\u0006","\u0007":"\\u0007", // 408
|
||
|
"\b":"\\b","\t":"\\t","\n":"\\n","\u000b":"\\u000b","\f":"\\f","\r":"\\r", // 409
|
||
|
"\u000e":"\\u000e","\u000f":"\\u000f","\u0010":"\\u0010","\u0011":"\\u0011", // 410
|
||
|
"\u0012":"\\u0012","\u0013":"\\u0013","\u0014":"\\u0014","\u0015":"\\u0015", // 411
|
||
|
"\u0016":"\\u0016","\u0017":"\\u0017","\u0018":"\\u0018","\u0019":"\\u0019", // 412
|
||
|
"\u001a":"\\u001a","\u001b":"\\u001b","\u001c":"\\u001c","\u001d":"\\u001d", // 413
|
||
|
"\u001e":"\\u001e","\u001f":"\\u001f","\"":"\\\"","\\":"\\\\", // 414
|
||
|
"\u007f":"\\u007f","\u0080":"\\u0080","\u0081":"\\u0081","\u0082":"\\u0082", // 415
|
||
|
"\u0083":"\\u0083","\u0084":"\\u0084","\u0085":"\\u0085","\u0086":"\\u0086", // 416
|
||
|
"\u0087":"\\u0087","\u0088":"\\u0088","\u0089":"\\u0089","\u008a":"\\u008a", // 417
|
||
|
"\u008b":"\\u008b","\u008c":"\\u008c","\u008d":"\\u008d","\u008e":"\\u008e", // 418
|
||
|
"\u008f":"\\u008f","\u0090":"\\u0090","\u0091":"\\u0091","\u0092":"\\u0092", // 419
|
||
|
"\u0093":"\\u0093","\u0094":"\\u0094","\u0095":"\\u0095","\u0096":"\\u0096", // 420
|
||
|
"\u0097":"\\u0097","\u0098":"\\u0098","\u0099":"\\u0099","\u009a":"\\u009a", // 421
|
||
|
"\u009b":"\\u009b","\u009c":"\\u009c","\u009d":"\\u009d","\u009e":"\\u009e", // 422
|
||
|
"\u009f":"\\u009f","\u00ad":"\\u00ad","\u0600":"\\u0600","\u0601":"\\u0601", // 423
|
||
|
"\u0602":"\\u0602","\u0603":"\\u0603","\u0604":"\\u0604","\u070f":"\\u070f", // 424
|
||
|
"\u17b4":"\\u17b4","\u17b5":"\\u17b5","\u200c":"\\u200c","\u200d":"\\u200d", // 425
|
||
|
"\u200e":"\\u200e","\u200f":"\\u200f","\u2028":"\\u2028","\u2029":"\\u2029", // 426
|
||
|
"\u202a":"\\u202a","\u202b":"\\u202b","\u202c":"\\u202c","\u202d":"\\u202d", // 427
|
||
|
"\u202e":"\\u202e","\u202f":"\\u202f","\u2060":"\\u2060","\u2061":"\\u2061", // 428
|
||
|
"\u2062":"\\u2062","\u2063":"\\u2063","\u2064":"\\u2064","\u2065":"\\u2065", // 429
|
||
|
"\u2066":"\\u2066","\u2067":"\\u2067","\u2068":"\\u2068","\u2069":"\\u2069", // 430
|
||
|
"\u206a":"\\u206a","\u206b":"\\u206b","\u206c":"\\u206c","\u206d":"\\u206d", // 431
|
||
|
"\u206e":"\\u206e","\u206f":"\\u206f","\ufeff":"\\ufeff","\ufff0":"\\ufff0", // 432
|
||
|
"\ufff1":"\\ufff1","\ufff2":"\\ufff2","\ufff3":"\\ufff3","\ufff4":"\\ufff4", // 433
|
||
|
"\ufff5":"\\ufff5","\ufff6":"\\ufff6","\ufff7":"\\ufff7","\ufff8":"\\ufff8", // 434
|
||
|
"\ufff9":"\\ufff9","\ufffa":"\\ufffa","\ufffb":"\\ufffb","\ufffc":"\\ufffc", // 435
|
||
|
"\ufffd":"\\ufffd","\ufffe":"\\ufffe","\uffff":"\\uffff"}; // 436
|
||
|
// 437
|
||
|
// Some extra characters that Chrome gets wrong, and substitutes with // 438
|
||
|
// something else on the wire. // 439
|
||
|
var extra_escapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g,
|
||
|
extra_lookup; // 441
|
||
|
// 442
|
||
|
// JSON Quote string. Use native implementation when possible. // 443
|
||
|
var JSONQuote = (JSON && JSON.stringify) || function(string) { // 444
|
||
|
json_escapable.lastIndex = 0; // 445
|
||
|
if (json_escapable.test(string)) { // 446
|
||
|
string = string.replace(json_escapable, function(a) { // 447
|
||
|
return json_lookup[a]; // 448
|
||
|
}); // 449
|
||
|
} // 450
|
||
|
return '"' + string + '"'; // 451
|
||
|
}; // 452
|
||
|
// 453
|
||
|
// This may be quite slow, so let's delay until user actually uses bad // 454
|
||
|
// characters. // 455
|
||
|
var unroll_lookup = function(escapable) { // 456
|
||
|
var i; // 457
|
||
|
var unrolled = {} // 458
|
||
|
var c = [] // 459
|
||
|
for(i=0; i<65536; i++) { // 460
|
||
|
c.push( String.fromCharCode(i) ); // 461
|
||
|
} // 462
|
||
|
escapable.lastIndex = 0; // 463
|
||
|
c.join('').replace(escapable, function (a) { // 464
|
||
|
unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); // 465
|
||
|
return ''; // 466
|
||
|
}); // 467
|
||
|
escapable.lastIndex = 0; // 468
|
||
|
return unrolled; // 469
|
||
|
}; // 470
|
||
|
// 471
|
||
|
// Quote string, also taking care of unicode characters that browsers // 472
|
||
|
// often break. Especially, take care of unicode surrogates: // 473
|
||
|
// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates // 474
|
||
|
utils.quote = function(string) { // 475
|
||
|
var quoted = JSONQuote(string); // 476
|
||
|
// 477
|
||
|
// In most cases this should be very fast and good enough. // 478
|
||
|
extra_escapable.lastIndex = 0; // 479
|
||
|
if(!extra_escapable.test(quoted)) { // 480
|
||
|
return quoted; // 481
|
||
|
} // 482
|
||
|
// 483
|
||
|
if(!extra_lookup) extra_lookup = unroll_lookup(extra_escapable); // 484
|
||
|
// 485
|
||
|
return quoted.replace(extra_escapable, function(a) { // 486
|
||
|
return extra_lookup[a]; // 487
|
||
|
}); // 488
|
||
|
} // 489
|
||
|
// 490
|
||
|
var _all_protocols = ['websocket', // 491
|
||
|
'xdr-streaming', // 492
|
||
|
'xhr-streaming', // 493
|
||
|
'iframe-eventsource', // 494
|
||
|
'iframe-htmlfile', // 495
|
||
|
'xdr-polling', // 496
|
||
|
'xhr-polling', // 497
|
||
|
'iframe-xhr-polling', // 498
|
||
|
'jsonp-polling']; // 499
|
||
|
// 500
|
||
|
utils.probeProtocols = function() { // 501
|
||
|
var probed = {}; // 502
|
||
|
for(var i=0; i<_all_protocols.length; i++) { // 503
|
||
|
var protocol = _all_protocols[i]; // 504
|
||
|
// User can have a typo in protocol name. // 505
|
||
|
probed[protocol] = SockJS[protocol] && // 506
|
||
|
SockJS[protocol].enabled(); // 507
|
||
|
} // 508
|
||
|
return probed; // 509
|
||
|
}; // 510
|
||
|
// 511
|
||
|
utils.detectProtocols = function(probed, protocols_whitelist, info) { // 512
|
||
|
var pe = {}, // 513
|
||
|
protocols = []; // 514
|
||
|
if (!protocols_whitelist) protocols_whitelist = _all_protocols; // 515
|
||
|
for(var i=0; i<protocols_whitelist.length; i++) { // 516
|
||
|
var protocol = protocols_whitelist[i]; // 517
|
||
|
pe[protocol] = probed[protocol]; // 518
|
||
|
} // 519
|
||
|
var maybe_push = function(protos) { // 520
|
||
|
var proto = protos.shift(); // 521
|
||
|
if (pe[proto]) { // 522
|
||
|
protocols.push(proto); // 523
|
||
|
} else { // 524
|
||
|
if (protos.length > 0) { // 525
|
||
|
maybe_push(protos); // 526
|
||
|
} // 527
|
||
|
} // 528
|
||
|
} // 529
|
||
|
// 530
|
||
|
// 1. Websocket // 531
|
||
|
if (info.websocket !== false) { // 532
|
||
|
maybe_push(['websocket']); // 533
|
||
|
} // 534
|
||
|
// 535
|
||
|
// 2. Streaming // 536
|
||
|
if (pe['xhr-streaming'] && !info.null_origin) { // 537
|
||
|
protocols.push('xhr-streaming'); // 538
|
||
|
} else { // 539
|
||
|
if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) { // 540
|
||
|
protocols.push('xdr-streaming'); // 541
|
||
|
} else { // 542
|
||
|
maybe_push(['iframe-eventsource', // 543
|
||
|
'iframe-htmlfile']); // 544
|
||
|
} // 545
|
||
|
} // 546
|
||
|
// 547
|
||
|
// 3. Polling // 548
|
||
|
if (pe['xhr-polling'] && !info.null_origin) { // 549
|
||
|
protocols.push('xhr-polling'); // 550
|
||
|
} else { // 551
|
||
|
if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) { // 552
|
||
|
protocols.push('xdr-polling'); // 553
|
||
|
} else { // 554
|
||
|
maybe_push(['iframe-xhr-polling', // 555
|
||
|
'jsonp-polling']); // 556
|
||
|
} // 557
|
||
|
} // 558
|
||
|
return protocols; // 559
|
||
|
} // 560
|
||
|
// [*] End of lib/utils.js // 561
|
||
|
// 562
|
||
|
// 563
|
||
|
// [*] Including lib/dom.js // 564
|
||
|
/* // 565
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 566
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 567
|
||
|
* // 568
|
||
|
* For the license see COPYING. // 569
|
||
|
* ***** END LICENSE BLOCK ***** // 570
|
||
|
*/ // 571
|
||
|
// 572
|
||
|
// May be used by htmlfile jsonp and transports. // 573
|
||
|
var MPrefix = '_sockjs_global'; // 574
|
||
|
utils.createHook = function() { // 575
|
||
|
var window_id = 'a' + utils.random_string(8); // 576
|
||
|
if (!(MPrefix in _window)) { // 577
|
||
|
var map = {}; // 578
|
||
|
_window[MPrefix] = function(window_id) { // 579
|
||
|
if (!(window_id in map)) { // 580
|
||
|
map[window_id] = { // 581
|
||
|
id: window_id, // 582
|
||
|
del: function() {delete map[window_id];} // 583
|
||
|
}; // 584
|
||
|
} // 585
|
||
|
return map[window_id]; // 586
|
||
|
} // 587
|
||
|
} // 588
|
||
|
return _window[MPrefix](window_id); // 589
|
||
|
}; // 590
|
||
|
// 591
|
||
|
// 592
|
||
|
// 593
|
||
|
utils.attachMessage = function(listener) { // 594
|
||
|
utils.attachEvent('message', listener); // 595
|
||
|
}; // 596
|
||
|
utils.attachEvent = function(event, listener) { // 597
|
||
|
if (typeof _window.addEventListener !== 'undefined') { // 598
|
||
|
_window.addEventListener(event, listener, false); // 599
|
||
|
} else { // 600
|
||
|
// IE quirks. // 601
|
||
|
// According to: http://stevesouders.com/misc/test-postmessage.php // 602
|
||
|
// the message gets delivered only to 'document', not 'window'. // 603
|
||
|
_document.attachEvent("on" + event, listener); // 604
|
||
|
// I get 'window' for ie8. // 605
|
||
|
_window.attachEvent("on" + event, listener); // 606
|
||
|
} // 607
|
||
|
}; // 608
|
||
|
// 609
|
||
|
utils.detachMessage = function(listener) { // 610
|
||
|
utils.detachEvent('message', listener); // 611
|
||
|
}; // 612
|
||
|
utils.detachEvent = function(event, listener) { // 613
|
||
|
if (typeof _window.addEventListener !== 'undefined') { // 614
|
||
|
_window.removeEventListener(event, listener, false); // 615
|
||
|
} else { // 616
|
||
|
_document.detachEvent("on" + event, listener); // 617
|
||
|
_window.detachEvent("on" + event, listener); // 618
|
||
|
} // 619
|
||
|
}; // 620
|
||
|
// 621
|
||
|
// 622
|
||
|
var on_unload = {}; // 623
|
||
|
// Things registered after beforeunload are to be called immediately. // 624
|
||
|
var after_unload = false; // 625
|
||
|
// 626
|
||
|
var trigger_unload_callbacks = function() { // 627
|
||
|
for(var ref in on_unload) { // 628
|
||
|
on_unload[ref](); // 629
|
||
|
delete on_unload[ref]; // 630
|
||
|
}; // 631
|
||
|
}; // 632
|
||
|
// 633
|
||
|
var unload_triggered = function() { // 634
|
||
|
if(after_unload) return; // 635
|
||
|
after_unload = true; // 636
|
||
|
trigger_unload_callbacks(); // 637
|
||
|
}; // 638
|
||
|
// 639
|
||
|
// 'unload' alone is not reliable in opera within an iframe, but we // 640
|
||
|
// can't use `beforeunload` as IE fires it on javascript: links. // 641
|
||
|
utils.attachEvent('unload', unload_triggered); // 642
|
||
|
// 643
|
||
|
utils.unload_add = function(listener) { // 644
|
||
|
var ref = utils.random_string(8); // 645
|
||
|
on_unload[ref] = listener; // 646
|
||
|
if (after_unload) { // 647
|
||
|
utils.delay(trigger_unload_callbacks); // 648
|
||
|
} // 649
|
||
|
return ref; // 650
|
||
|
}; // 651
|
||
|
utils.unload_del = function(ref) { // 652
|
||
|
if (ref in on_unload) // 653
|
||
|
delete on_unload[ref]; // 654
|
||
|
}; // 655
|
||
|
// 656
|
||
|
// 657
|
||
|
utils.createIframe = function (iframe_url, error_callback) { // 658
|
||
|
var iframe = _document.createElement('iframe'); // 659
|
||
|
var tref, unload_ref; // 660
|
||
|
var unattach = function() { // 661
|
||
|
clearTimeout(tref); // 662
|
||
|
// Explorer had problems with that. // 663
|
||
|
try {iframe.onload = null;} catch (x) {} // 664
|
||
|
iframe.onerror = null; // 665
|
||
|
}; // 666
|
||
|
var cleanup = function() { // 667
|
||
|
if (iframe) { // 668
|
||
|
unattach(); // 669
|
||
|
// This timeout makes chrome fire onbeforeunload event // 670
|
||
|
// within iframe. Without the timeout it goes straight to // 671
|
||
|
// onunload. // 672
|
||
|
setTimeout(function() { // 673
|
||
|
if(iframe) { // 674
|
||
|
iframe.parentNode.removeChild(iframe); // 675
|
||
|
} // 676
|
||
|
iframe = null; // 677
|
||
|
}, 0); // 678
|
||
|
utils.unload_del(unload_ref); // 679
|
||
|
} // 680
|
||
|
}; // 681
|
||
|
var onerror = function(r) { // 682
|
||
|
if (iframe) { // 683
|
||
|
cleanup(); // 684
|
||
|
error_callback(r); // 685
|
||
|
} // 686
|
||
|
}; // 687
|
||
|
var post = function(msg, origin) { // 688
|
||
|
try { // 689
|
||
|
// When the iframe is not loaded, IE raises an exception // 690
|
||
|
// on 'contentWindow'. // 691
|
||
|
if (iframe && iframe.contentWindow) { // 692
|
||
|
iframe.contentWindow.postMessage(msg, origin); // 693
|
||
|
} // 694
|
||
|
} catch (x) {}; // 695
|
||
|
}; // 696
|
||
|
// 697
|
||
|
iframe.src = iframe_url; // 698
|
||
|
iframe.style.display = 'none'; // 699
|
||
|
iframe.style.position = 'absolute'; // 700
|
||
|
iframe.onerror = function(){onerror('onerror');}; // 701
|
||
|
iframe.onload = function() { // 702
|
||
|
// `onload` is triggered before scripts on the iframe are // 703
|
||
|
// executed. Give it few seconds to actually load stuff. // 704
|
||
|
clearTimeout(tref); // 705
|
||
|
tref = setTimeout(function(){onerror('onload timeout');}, 2000); // 706
|
||
|
}; // 707
|
||
|
_document.body.appendChild(iframe); // 708
|
||
|
tref = setTimeout(function(){onerror('timeout');}, 15000); // 709
|
||
|
unload_ref = utils.unload_add(cleanup); // 710
|
||
|
return { // 711
|
||
|
post: post, // 712
|
||
|
cleanup: cleanup, // 713
|
||
|
loaded: unattach // 714
|
||
|
}; // 715
|
||
|
}; // 716
|
||
|
// 717
|
||
|
utils.createHtmlfile = function (iframe_url, error_callback) { // 718
|
||
|
var doc = new ActiveXObject('htmlfile'); // 719
|
||
|
var tref, unload_ref; // 720
|
||
|
var iframe; // 721
|
||
|
var unattach = function() { // 722
|
||
|
clearTimeout(tref); // 723
|
||
|
}; // 724
|
||
|
var cleanup = function() { // 725
|
||
|
if (doc) { // 726
|
||
|
unattach(); // 727
|
||
|
utils.unload_del(unload_ref); // 728
|
||
|
iframe.parentNode.removeChild(iframe); // 729
|
||
|
iframe = doc = null; // 730
|
||
|
CollectGarbage(); // 731
|
||
|
} // 732
|
||
|
}; // 733
|
||
|
var onerror = function(r) { // 734
|
||
|
if (doc) { // 735
|
||
|
cleanup(); // 736
|
||
|
error_callback(r); // 737
|
||
|
} // 738
|
||
|
}; // 739
|
||
|
var post = function(msg, origin) { // 740
|
||
|
try { // 741
|
||
|
// When the iframe is not loaded, IE raises an exception // 742
|
||
|
// on 'contentWindow'. // 743
|
||
|
if (iframe && iframe.contentWindow) { // 744
|
||
|
iframe.contentWindow.postMessage(msg, origin); // 745
|
||
|
} // 746
|
||
|
} catch (x) {}; // 747
|
||
|
}; // 748
|
||
|
// 749
|
||
|
doc.open(); // 750
|
||
|
doc.write('<html><s' + 'cript>' + // 751
|
||
|
'document.domain="' + document.domain + '";' + // 752
|
||
|
'</s' + 'cript></html>'); // 753
|
||
|
doc.close(); // 754
|
||
|
doc.parentWindow[WPrefix] = _window[WPrefix]; // 755
|
||
|
var c = doc.createElement('div'); // 756
|
||
|
doc.body.appendChild(c); // 757
|
||
|
iframe = doc.createElement('iframe'); // 758
|
||
|
c.appendChild(iframe); // 759
|
||
|
iframe.src = iframe_url; // 760
|
||
|
tref = setTimeout(function(){onerror('timeout');}, 15000); // 761
|
||
|
unload_ref = utils.unload_add(cleanup); // 762
|
||
|
return { // 763
|
||
|
post: post, // 764
|
||
|
cleanup: cleanup, // 765
|
||
|
loaded: unattach // 766
|
||
|
}; // 767
|
||
|
}; // 768
|
||
|
// [*] End of lib/dom.js // 769
|
||
|
// 770
|
||
|
// 771
|
||
|
// [*] Including lib/dom2.js // 772
|
||
|
/* // 773
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 774
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 775
|
||
|
* // 776
|
||
|
* For the license see COPYING. // 777
|
||
|
* ***** END LICENSE BLOCK ***** // 778
|
||
|
*/ // 779
|
||
|
// 780
|
||
|
var AbstractXHRObject = function(){}; // 781
|
||
|
AbstractXHRObject.prototype = new EventEmitter(['chunk', 'finish']); // 782
|
||
|
// 783
|
||
|
AbstractXHRObject.prototype._start = function(method, url, payload, opts) { // 784
|
||
|
var that = this; // 785
|
||
|
// 786
|
||
|
try { // 787
|
||
|
that.xhr = new XMLHttpRequest(); // 788
|
||
|
} catch(x) {}; // 789
|
||
|
// 790
|
||
|
if (!that.xhr) { // 791
|
||
|
try { // 792
|
||
|
that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP'); // 793
|
||
|
} catch(x) {}; // 794
|
||
|
} // 795
|
||
|
if (_window.ActiveXObject || _window.XDomainRequest) { // 796
|
||
|
// IE8 caches even POSTs // 797
|
||
|
url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); // 798
|
||
|
} // 799
|
||
|
// 800
|
||
|
// Explorer tends to keep connection open, even after the // 801
|
||
|
// tab gets closed: http://bugs.jquery.com/ticket/5280 // 802
|
||
|
that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); // 803
|
||
|
try { // 804
|
||
|
that.xhr.open(method, url, true); // 805
|
||
|
} catch(e) { // 806
|
||
|
// IE raises an exception on wrong port. // 807
|
||
|
that.emit('finish', 0, ''); // 808
|
||
|
that._cleanup(); // 809
|
||
|
return; // 810
|
||
|
}; // 811
|
||
|
// 812
|
||
|
if (!opts || !opts.no_credentials) { // 813
|
||
|
// Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest : // 814
|
||
|
// "This never affects same-site requests." // 815
|
||
|
that.xhr.withCredentials = 'true'; // 816
|
||
|
} // 817
|
||
|
if (opts && opts.headers) { // 818
|
||
|
for(var key in opts.headers) { // 819
|
||
|
that.xhr.setRequestHeader(key, opts.headers[key]); // 820
|
||
|
} // 821
|
||
|
} // 822
|
||
|
// 823
|
||
|
that.xhr.onreadystatechange = function() { // 824
|
||
|
if (that.xhr) { // 825
|
||
|
var x = that.xhr; // 826
|
||
|
switch (x.readyState) { // 827
|
||
|
case 3: // 828
|
||
|
// IE doesn't like peeking into responseText or status // 829
|
||
|
// on Microsoft.XMLHTTP and readystate=3 // 830
|
||
|
try { // 831
|
||
|
var status = x.status; // 832
|
||
|
var text = x.responseText; // 833
|
||
|
} catch (x) {}; // 834
|
||
|
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 // 835
|
||
|
if (status === 1223) status = 204; // 836
|
||
|
// 837
|
||
|
// IE does return readystate == 3 for 404 answers. // 838
|
||
|
if (text && text.length > 0) { // 839
|
||
|
that.emit('chunk', status, text); // 840
|
||
|
} // 841
|
||
|
break; // 842
|
||
|
case 4: // 843
|
||
|
var status = x.status; // 844
|
||
|
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450 // 845
|
||
|
if (status === 1223) status = 204; // 846
|
||
|
// 847
|
||
|
that.emit('finish', status, x.responseText); // 848
|
||
|
that._cleanup(false); // 849
|
||
|
break; // 850
|
||
|
} // 851
|
||
|
} // 852
|
||
|
}; // 853
|
||
|
that.xhr.send(payload); // 854
|
||
|
}; // 855
|
||
|
// 856
|
||
|
AbstractXHRObject.prototype._cleanup = function(abort) { // 857
|
||
|
var that = this; // 858
|
||
|
if (!that.xhr) return; // 859
|
||
|
utils.unload_del(that.unload_ref); // 860
|
||
|
// 861
|
||
|
// IE needs this field to be a function // 862
|
||
|
that.xhr.onreadystatechange = function(){}; // 863
|
||
|
// 864
|
||
|
if (abort) { // 865
|
||
|
try { // 866
|
||
|
that.xhr.abort(); // 867
|
||
|
} catch(x) {}; // 868
|
||
|
} // 869
|
||
|
that.unload_ref = that.xhr = null; // 870
|
||
|
}; // 871
|
||
|
// 872
|
||
|
AbstractXHRObject.prototype.close = function() { // 873
|
||
|
var that = this; // 874
|
||
|
that.nuke(); // 875
|
||
|
that._cleanup(true); // 876
|
||
|
}; // 877
|
||
|
// 878
|
||
|
var XHRCorsObject = utils.XHRCorsObject = function() { // 879
|
||
|
var that = this, args = arguments; // 880
|
||
|
utils.delay(function(){that._start.apply(that, args);}); // 881
|
||
|
}; // 882
|
||
|
XHRCorsObject.prototype = new AbstractXHRObject(); // 883
|
||
|
// 884
|
||
|
var XHRLocalObject = utils.XHRLocalObject = function(method, url, payload) { // 885
|
||
|
var that = this; // 886
|
||
|
utils.delay(function(){ // 887
|
||
|
that._start(method, url, payload, { // 888
|
||
|
no_credentials: true // 889
|
||
|
}); // 890
|
||
|
}); // 891
|
||
|
}; // 892
|
||
|
XHRLocalObject.prototype = new AbstractXHRObject(); // 893
|
||
|
// 894
|
||
|
// 895
|
||
|
// 896
|
||
|
// References: // 897
|
||
|
// http://ajaxian.com/archives/100-line-ajax-wrapper // 898
|
||
|
// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx // 899
|
||
|
var XDRObject = utils.XDRObject = function(method, url, payload) { // 900
|
||
|
var that = this; // 901
|
||
|
utils.delay(function(){that._start(method, url, payload);}); // 902
|
||
|
}; // 903
|
||
|
XDRObject.prototype = new EventEmitter(['chunk', 'finish']); // 904
|
||
|
XDRObject.prototype._start = function(method, url, payload) { // 905
|
||
|
var that = this; // 906
|
||
|
var xdr = new XDomainRequest(); // 907
|
||
|
// IE caches even POSTs // 908
|
||
|
url += ((url.indexOf('?') === -1) ? '?' : '&') + 't='+(+new Date); // 909
|
||
|
// 910
|
||
|
var onerror = xdr.ontimeout = xdr.onerror = function() { // 911
|
||
|
that.emit('finish', 0, ''); // 912
|
||
|
that._cleanup(false); // 913
|
||
|
}; // 914
|
||
|
xdr.onprogress = function() { // 915
|
||
|
that.emit('chunk', 200, xdr.responseText); // 916
|
||
|
}; // 917
|
||
|
xdr.onload = function() { // 918
|
||
|
that.emit('finish', 200, xdr.responseText); // 919
|
||
|
that._cleanup(false); // 920
|
||
|
}; // 921
|
||
|
that.xdr = xdr; // 922
|
||
|
that.unload_ref = utils.unload_add(function(){that._cleanup(true);}); // 923
|
||
|
try { // 924
|
||
|
// Fails with AccessDenied if port number is bogus // 925
|
||
|
that.xdr.open(method, url); // 926
|
||
|
that.xdr.send(payload); // 927
|
||
|
} catch(x) { // 928
|
||
|
onerror(); // 929
|
||
|
} // 930
|
||
|
}; // 931
|
||
|
// 932
|
||
|
XDRObject.prototype._cleanup = function(abort) { // 933
|
||
|
var that = this; // 934
|
||
|
if (!that.xdr) return; // 935
|
||
|
utils.unload_del(that.unload_ref); // 936
|
||
|
// 937
|
||
|
that.xdr.ontimeout = that.xdr.onerror = that.xdr.onprogress = // 938
|
||
|
that.xdr.onload = null; // 939
|
||
|
if (abort) { // 940
|
||
|
try { // 941
|
||
|
that.xdr.abort(); // 942
|
||
|
} catch(x) {}; // 943
|
||
|
} // 944
|
||
|
that.unload_ref = that.xdr = null; // 945
|
||
|
}; // 946
|
||
|
// 947
|
||
|
XDRObject.prototype.close = function() { // 948
|
||
|
var that = this; // 949
|
||
|
that.nuke(); // 950
|
||
|
that._cleanup(true); // 951
|
||
|
}; // 952
|
||
|
// 953
|
||
|
// 1. Is natively via XHR // 954
|
||
|
// 2. Is natively via XDR // 955
|
||
|
// 3. Nope, but postMessage is there so it should work via the Iframe. // 956
|
||
|
// 4. Nope, sorry. // 957
|
||
|
utils.isXHRCorsCapable = function() { // 958
|
||
|
if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) { // 959
|
||
|
return 1; // 960
|
||
|
} // 961
|
||
|
// XDomainRequest doesn't work if page is served from file:// // 962
|
||
|
if (_window.XDomainRequest && _document.domain) { // 963
|
||
|
return 2; // 964
|
||
|
} // 965
|
||
|
if (IframeTransport.enabled()) { // 966
|
||
|
return 3; // 967
|
||
|
} // 968
|
||
|
return 4; // 969
|
||
|
}; // 970
|
||
|
// [*] End of lib/dom2.js // 971
|
||
|
// 972
|
||
|
// 973
|
||
|
// [*] Including lib/sockjs.js // 974
|
||
|
/* // 975
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 976
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 977
|
||
|
* // 978
|
||
|
* For the license see COPYING. // 979
|
||
|
* ***** END LICENSE BLOCK ***** // 980
|
||
|
*/ // 981
|
||
|
// 982
|
||
|
var SockJS = function(url, dep_protocols_whitelist, options) { // 983
|
||
|
if (!(this instanceof SockJS)) { // 984
|
||
|
// makes `new` optional // 985
|
||
|
return new SockJS(url, dep_protocols_whitelist, options); // 986
|
||
|
} // 987
|
||
|
// 988
|
||
|
var that = this, protocols_whitelist; // 989
|
||
|
that._options = {devel: false, debug: false, protocols_whitelist: [], // 990
|
||
|
info: undefined, rtt: undefined}; // 991
|
||
|
if (options) { // 992
|
||
|
utils.objectExtend(that._options, options); // 993
|
||
|
} // 994
|
||
|
that._base_url = utils.amendUrl(url); // 995
|
||
|
that._server = that._options.server || utils.random_number_string(1000); // 996
|
||
|
if (that._options.protocols_whitelist && // 997
|
||
|
that._options.protocols_whitelist.length) { // 998
|
||
|
protocols_whitelist = that._options.protocols_whitelist; // 999
|
||
|
} else { // 1000
|
||
|
// Deprecated API // 1001
|
||
|
if (typeof dep_protocols_whitelist === 'string' && // 1002
|
||
|
dep_protocols_whitelist.length > 0) { // 1003
|
||
|
protocols_whitelist = [dep_protocols_whitelist]; // 1004
|
||
|
} else if (utils.isArray(dep_protocols_whitelist)) { // 1005
|
||
|
protocols_whitelist = dep_protocols_whitelist // 1006
|
||
|
} else { // 1007
|
||
|
protocols_whitelist = null; // 1008
|
||
|
} // 1009
|
||
|
if (protocols_whitelist) { // 1010
|
||
|
that._debug('Deprecated API: Use "protocols_whitelist" option ' + // 1011
|
||
|
'instead of supplying protocol list as a second ' + // 1012
|
||
|
'parameter to SockJS constructor.'); // 1013
|
||
|
} // 1014
|
||
|
} // 1015
|
||
|
that._protocols = []; // 1016
|
||
|
that.protocol = null; // 1017
|
||
|
that.readyState = SockJS.CONNECTING; // 1018
|
||
|
that._ir = createInfoReceiver(that._base_url); // 1019
|
||
|
that._ir.onfinish = function(info, rtt) { // 1020
|
||
|
that._ir = null; // 1021
|
||
|
if (info) { // 1022
|
||
|
if (that._options.info) { // 1023
|
||
|
// Override if user supplies the option // 1024
|
||
|
info = utils.objectExtend(info, that._options.info); // 1025
|
||
|
} // 1026
|
||
|
if (that._options.rtt) { // 1027
|
||
|
rtt = that._options.rtt; // 1028
|
||
|
} // 1029
|
||
|
that._applyInfo(info, rtt, protocols_whitelist); // 1030
|
||
|
that._didClose(); // 1031
|
||
|
} else { // 1032
|
||
|
that._didClose(1002, 'Can\'t connect to server', true); // 1033
|
||
|
} // 1034
|
||
|
}; // 1035
|
||
|
}; // 1036
|
||
|
// Inheritance // 1037
|
||
|
SockJS.prototype = new REventTarget(); // 1038
|
||
|
// 1039
|
||
|
SockJS.version = "0.3.4"; // 1040
|
||
|
// 1041
|
||
|
SockJS.CONNECTING = 0; // 1042
|
||
|
SockJS.OPEN = 1; // 1043
|
||
|
SockJS.CLOSING = 2; // 1044
|
||
|
SockJS.CLOSED = 3; // 1045
|
||
|
// 1046
|
||
|
SockJS.prototype._debug = function() { // 1047
|
||
|
if (this._options.debug) // 1048
|
||
|
utils.log.apply(utils, arguments); // 1049
|
||
|
}; // 1050
|
||
|
// 1051
|
||
|
SockJS.prototype._dispatchOpen = function() { // 1052
|
||
|
var that = this; // 1053
|
||
|
if (that.readyState === SockJS.CONNECTING) { // 1054
|
||
|
if (that._transport_tref) { // 1055
|
||
|
clearTimeout(that._transport_tref); // 1056
|
||
|
that._transport_tref = null; // 1057
|
||
|
} // 1058
|
||
|
that.readyState = SockJS.OPEN; // 1059
|
||
|
that.dispatchEvent(new SimpleEvent("open")); // 1060
|
||
|
} else { // 1061
|
||
|
// The server might have been restarted, and lost track of our // 1062
|
||
|
// connection. // 1063
|
||
|
that._didClose(1006, "Server lost session"); // 1064
|
||
|
} // 1065
|
||
|
}; // 1066
|
||
|
// 1067
|
||
|
SockJS.prototype._dispatchMessage = function(data) { // 1068
|
||
|
var that = this; // 1069
|
||
|
if (that.readyState !== SockJS.OPEN) // 1070
|
||
|
return; // 1071
|
||
|
that.dispatchEvent(new SimpleEvent("message", {data: data})); // 1072
|
||
|
}; // 1073
|
||
|
// 1074
|
||
|
SockJS.prototype._dispatchHeartbeat = function(data) { // 1075
|
||
|
var that = this; // 1076
|
||
|
if (that.readyState !== SockJS.OPEN) // 1077
|
||
|
return; // 1078
|
||
|
that.dispatchEvent(new SimpleEvent('heartbeat', {})); // 1079
|
||
|
}; // 1080
|
||
|
// 1081
|
||
|
SockJS.prototype._didClose = function(code, reason, force) { // 1082
|
||
|
var that = this; // 1083
|
||
|
if (that.readyState !== SockJS.CONNECTING && // 1084
|
||
|
that.readyState !== SockJS.OPEN && // 1085
|
||
|
that.readyState !== SockJS.CLOSING) // 1086
|
||
|
throw new Error('INVALID_STATE_ERR'); // 1087
|
||
|
if (that._ir) { // 1088
|
||
|
that._ir.nuke(); // 1089
|
||
|
that._ir = null; // 1090
|
||
|
} // 1091
|
||
|
// 1092
|
||
|
if (that._transport) { // 1093
|
||
|
that._transport.doCleanup(); // 1094
|
||
|
that._transport = null; // 1095
|
||
|
} // 1096
|
||
|
// 1097
|
||
|
var close_event = new SimpleEvent("close", { // 1098
|
||
|
code: code, // 1099
|
||
|
reason: reason, // 1100
|
||
|
wasClean: utils.userSetCode(code)}); // 1101
|
||
|
// 1102
|
||
|
if (!utils.userSetCode(code) && // 1103
|
||
|
that.readyState === SockJS.CONNECTING && !force) { // 1104
|
||
|
if (that._try_next_protocol(close_event)) { // 1105
|
||
|
return; // 1106
|
||
|
} // 1107
|
||
|
close_event = new SimpleEvent("close", {code: 2000, // 1108
|
||
|
reason: "All transports failed", // 1109
|
||
|
wasClean: false, // 1110
|
||
|
last_event: close_event}); // 1111
|
||
|
} // 1112
|
||
|
that.readyState = SockJS.CLOSED; // 1113
|
||
|
// 1114
|
||
|
utils.delay(function() { // 1115
|
||
|
that.dispatchEvent(close_event); // 1116
|
||
|
}); // 1117
|
||
|
}; // 1118
|
||
|
// 1119
|
||
|
SockJS.prototype._didMessage = function(data) { // 1120
|
||
|
var that = this; // 1121
|
||
|
var type = data.slice(0, 1); // 1122
|
||
|
switch(type) { // 1123
|
||
|
case 'o': // 1124
|
||
|
that._dispatchOpen(); // 1125
|
||
|
break; // 1126
|
||
|
case 'a': // 1127
|
||
|
var payload = JSON.parse(data.slice(1) || '[]'); // 1128
|
||
|
for(var i=0; i < payload.length; i++){ // 1129
|
||
|
that._dispatchMessage(payload[i]); // 1130
|
||
|
} // 1131
|
||
|
break; // 1132
|
||
|
case 'm': // 1133
|
||
|
var payload = JSON.parse(data.slice(1) || 'null'); // 1134
|
||
|
that._dispatchMessage(payload); // 1135
|
||
|
break; // 1136
|
||
|
case 'c': // 1137
|
||
|
var payload = JSON.parse(data.slice(1) || '[]'); // 1138
|
||
|
that._didClose(payload[0], payload[1]); // 1139
|
||
|
break; // 1140
|
||
|
case 'h': // 1141
|
||
|
that._dispatchHeartbeat(); // 1142
|
||
|
break; // 1143
|
||
|
} // 1144
|
||
|
}; // 1145
|
||
|
// 1146
|
||
|
SockJS.prototype._try_next_protocol = function(close_event) { // 1147
|
||
|
var that = this; // 1148
|
||
|
if (that.protocol) { // 1149
|
||
|
that._debug('Closed transport:', that.protocol, ''+close_event); // 1150
|
||
|
that.protocol = null; // 1151
|
||
|
} // 1152
|
||
|
if (that._transport_tref) { // 1153
|
||
|
clearTimeout(that._transport_tref); // 1154
|
||
|
that._transport_tref = null; // 1155
|
||
|
} // 1156
|
||
|
// 1157
|
||
|
while(1) { // 1158
|
||
|
var protocol = that.protocol = that._protocols.shift(); // 1159
|
||
|
if (!protocol) { // 1160
|
||
|
return false; // 1161
|
||
|
} // 1162
|
||
|
// Some protocols require access to `body`, what if were in // 1163
|
||
|
// the `head`? // 1164
|
||
|
if (SockJS[protocol] && // 1165
|
||
|
SockJS[protocol].need_body === true && // 1166
|
||
|
(!_document.body || // 1167
|
||
|
(typeof _document.readyState !== 'undefined' // 1168
|
||
|
&& _document.readyState !== 'complete'))) { // 1169
|
||
|
that._protocols.unshift(protocol); // 1170
|
||
|
that.protocol = 'waiting-for-load'; // 1171
|
||
|
utils.attachEvent('load', function(){ // 1172
|
||
|
that._try_next_protocol(); // 1173
|
||
|
}); // 1174
|
||
|
return true; // 1175
|
||
|
} // 1176
|
||
|
// 1177
|
||
|
if (!SockJS[protocol] || // 1178
|
||
|
!SockJS[protocol].enabled(that._options)) { // 1179
|
||
|
that._debug('Skipping transport:', protocol); // 1180
|
||
|
} else { // 1181
|
||
|
var roundTrips = SockJS[protocol].roundTrips || 1; // 1182
|
||
|
var to = ((that._options.rto || 0) * roundTrips) || 5000; // 1183
|
||
|
that._transport_tref = utils.delay(to, function() { // 1184
|
||
|
if (that.readyState === SockJS.CONNECTING) { // 1185
|
||
|
// I can't understand how it is possible to run // 1186
|
||
|
// this timer, when the state is CLOSED, but // 1187
|
||
|
// apparently in IE everythin is possible. // 1188
|
||
|
that._didClose(2007, "Transport timeouted"); // 1189
|
||
|
} // 1190
|
||
|
}); // 1191
|
||
|
// 1192
|
||
|
var connid = utils.random_string(8); // 1193
|
||
|
var trans_url = that._base_url + '/' + that._server + '/' + connid; // 1194
|
||
|
that._debug('Opening transport:', protocol, ' url:'+trans_url, // 1195
|
||
|
' RTO:'+that._options.rto); // 1196
|
||
|
that._transport = new SockJS[protocol](that, trans_url, // 1197
|
||
|
that._base_url); // 1198
|
||
|
return true; // 1199
|
||
|
} // 1200
|
||
|
} // 1201
|
||
|
}; // 1202
|
||
|
// 1203
|
||
|
SockJS.prototype.close = function(code, reason) { // 1204
|
||
|
var that = this; // 1205
|
||
|
if (code && !utils.userSetCode(code)) // 1206
|
||
|
throw new Error("INVALID_ACCESS_ERR"); // 1207
|
||
|
if(that.readyState !== SockJS.CONNECTING && // 1208
|
||
|
that.readyState !== SockJS.OPEN) { // 1209
|
||
|
return false; // 1210
|
||
|
} // 1211
|
||
|
that.readyState = SockJS.CLOSING; // 1212
|
||
|
that._didClose(code || 1000, reason || "Normal closure"); // 1213
|
||
|
return true; // 1214
|
||
|
}; // 1215
|
||
|
// 1216
|
||
|
SockJS.prototype.send = function(data) { // 1217
|
||
|
var that = this; // 1218
|
||
|
if (that.readyState === SockJS.CONNECTING) // 1219
|
||
|
throw new Error('INVALID_STATE_ERR'); // 1220
|
||
|
if (that.readyState === SockJS.OPEN) { // 1221
|
||
|
that._transport.doSend(utils.quote('' + data)); // 1222
|
||
|
} // 1223
|
||
|
return true; // 1224
|
||
|
}; // 1225
|
||
|
// 1226
|
||
|
SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) { // 1227
|
||
|
var that = this; // 1228
|
||
|
that._options.info = info; // 1229
|
||
|
that._options.rtt = rtt; // 1230
|
||
|
that._options.rto = utils.countRTO(rtt); // 1231
|
||
|
that._options.info.null_origin = !_document.domain; // 1232
|
||
|
// Servers can override base_url, eg to provide a randomized domain name and // 1233
|
||
|
// avoid browser per-domain connection limits. // 1234
|
||
|
if (info.base_url) // 1235
|
||
|
// <METEOR> // 1236
|
||
|
that._base_url = utils.amendUrl(info.base_url, that._base_url); // 1237
|
||
|
// </METEOR> // 1238
|
||
|
var probed = utils.probeProtocols(); // 1239
|
||
|
that._protocols = utils.detectProtocols(probed, protocols_whitelist, info); // 1240
|
||
|
// <METEOR> // 1241
|
||
|
// https://github.com/sockjs/sockjs-client/issues/79 // 1242
|
||
|
// Hack to avoid XDR when using different protocols // 1243
|
||
|
// We're on IE trying to do cross-protocol. jsonp only. // 1244
|
||
|
if (!utils.isSameOriginScheme(that._base_url) && // 1245
|
||
|
2 === utils.isXHRCorsCapable()) { // 1246
|
||
|
that._protocols = ['jsonp-polling']; // 1247
|
||
|
} // 1248
|
||
|
// </METEOR> // 1249
|
||
|
}; // 1250
|
||
|
// [*] End of lib/sockjs.js // 1251
|
||
|
// 1252
|
||
|
// 1253
|
||
|
// [*] Including lib/trans-websocket.js // 1254
|
||
|
/* // 1255
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1256
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1257
|
||
|
* // 1258
|
||
|
* For the license see COPYING. // 1259
|
||
|
* ***** END LICENSE BLOCK ***** // 1260
|
||
|
*/ // 1261
|
||
|
// 1262
|
||
|
var WebSocketTransport = SockJS.websocket = function(ri, trans_url) { // 1263
|
||
|
var that = this; // 1264
|
||
|
var url = trans_url + '/websocket'; // 1265
|
||
|
if (url.slice(0, 5) === 'https') { // 1266
|
||
|
url = 'wss' + url.slice(5); // 1267
|
||
|
} else { // 1268
|
||
|
url = 'ws' + url.slice(4); // 1269
|
||
|
} // 1270
|
||
|
that.ri = ri; // 1271
|
||
|
that.url = url; // 1272
|
||
|
var Constructor = _window.WebSocket || _window.MozWebSocket; // 1273
|
||
|
// 1274
|
||
|
that.ws = new Constructor(that.url); // 1275
|
||
|
that.ws.onmessage = function(e) { // 1276
|
||
|
that.ri._didMessage(e.data); // 1277
|
||
|
}; // 1278
|
||
|
// Firefox has an interesting bug. If a websocket connection is // 1279
|
||
|
// created after onunload, it stays alive even when user // 1280
|
||
|
// navigates away from the page. In such situation let's lie - // 1281
|
||
|
// let's not open the ws connection at all. See: // 1282
|
||
|
// https://github.com/sockjs/sockjs-client/issues/28 // 1283
|
||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=696085 // 1284
|
||
|
that.unload_ref = utils.unload_add(function(){that.ws.close()}); // 1285
|
||
|
that.ws.onclose = function() { // 1286
|
||
|
that.ri._didMessage(utils.closeFrame(1006, "WebSocket connection broken")); // 1287
|
||
|
}; // 1288
|
||
|
}; // 1289
|
||
|
// 1290
|
||
|
WebSocketTransport.prototype.doSend = function(data) { // 1291
|
||
|
this.ws.send('[' + data + ']'); // 1292
|
||
|
}; // 1293
|
||
|
// 1294
|
||
|
WebSocketTransport.prototype.doCleanup = function() { // 1295
|
||
|
var that = this; // 1296
|
||
|
var ws = that.ws; // 1297
|
||
|
if (ws) { // 1298
|
||
|
ws.onmessage = ws.onclose = null; // 1299
|
||
|
ws.close(); // 1300
|
||
|
utils.unload_del(that.unload_ref); // 1301
|
||
|
that.unload_ref = that.ri = that.ws = null; // 1302
|
||
|
} // 1303
|
||
|
}; // 1304
|
||
|
// 1305
|
||
|
WebSocketTransport.enabled = function() { // 1306
|
||
|
return !!(_window.WebSocket || _window.MozWebSocket); // 1307
|
||
|
}; // 1308
|
||
|
// 1309
|
||
|
// In theory, ws should require 1 round trip. But in chrome, this is // 1310
|
||
|
// not very stable over SSL. Most likely a ws connection requires a // 1311
|
||
|
// separate SSL connection, in which case 2 round trips are an // 1312
|
||
|
// absolute minumum. // 1313
|
||
|
WebSocketTransport.roundTrips = 2; // 1314
|
||
|
// [*] End of lib/trans-websocket.js // 1315
|
||
|
// 1316
|
||
|
// 1317
|
||
|
// [*] Including lib/trans-sender.js // 1318
|
||
|
/* // 1319
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1320
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1321
|
||
|
* // 1322
|
||
|
* For the license see COPYING. // 1323
|
||
|
* ***** END LICENSE BLOCK ***** // 1324
|
||
|
*/ // 1325
|
||
|
// 1326
|
||
|
var BufferedSender = function() {}; // 1327
|
||
|
BufferedSender.prototype.send_constructor = function(sender) { // 1328
|
||
|
var that = this; // 1329
|
||
|
that.send_buffer = []; // 1330
|
||
|
that.sender = sender; // 1331
|
||
|
}; // 1332
|
||
|
BufferedSender.prototype.doSend = function(message) { // 1333
|
||
|
var that = this; // 1334
|
||
|
that.send_buffer.push(message); // 1335
|
||
|
if (!that.send_stop) { // 1336
|
||
|
that.send_schedule(); // 1337
|
||
|
} // 1338
|
||
|
}; // 1339
|
||
|
// 1340
|
||
|
// For polling transports in a situation when in the message callback, // 1341
|
||
|
// new message is being send. If the sending connection was started // 1342
|
||
|
// before receiving one, it is possible to saturate the network and // 1343
|
||
|
// timeout due to the lack of receiving socket. To avoid that we delay // 1344
|
||
|
// sending messages by some small time, in order to let receiving // 1345
|
||
|
// connection be started beforehand. This is only a halfmeasure and // 1346
|
||
|
// does not fix the big problem, but it does make the tests go more // 1347
|
||
|
// stable on slow networks. // 1348
|
||
|
BufferedSender.prototype.send_schedule_wait = function() { // 1349
|
||
|
var that = this; // 1350
|
||
|
var tref; // 1351
|
||
|
that.send_stop = function() { // 1352
|
||
|
that.send_stop = null; // 1353
|
||
|
clearTimeout(tref); // 1354
|
||
|
}; // 1355
|
||
|
tref = utils.delay(25, function() { // 1356
|
||
|
that.send_stop = null; // 1357
|
||
|
that.send_schedule(); // 1358
|
||
|
}); // 1359
|
||
|
}; // 1360
|
||
|
// 1361
|
||
|
BufferedSender.prototype.send_schedule = function() { // 1362
|
||
|
var that = this; // 1363
|
||
|
if (that.send_buffer.length > 0) { // 1364
|
||
|
var payload = '[' + that.send_buffer.join(',') + ']'; // 1365
|
||
|
that.send_stop = that.sender(that.trans_url, payload, function(success, abort_reason) { // 1366
|
||
|
that.send_stop = null; // 1367
|
||
|
if (success === false) { // 1368
|
||
|
that.ri._didClose(1006, 'Sending error ' + abort_reason); // 1369
|
||
|
} else { // 1370
|
||
|
that.send_schedule_wait(); // 1371
|
||
|
} // 1372
|
||
|
}); // 1373
|
||
|
that.send_buffer = []; // 1374
|
||
|
} // 1375
|
||
|
}; // 1376
|
||
|
// 1377
|
||
|
BufferedSender.prototype.send_destructor = function() { // 1378
|
||
|
var that = this; // 1379
|
||
|
if (that._send_stop) { // 1380
|
||
|
that._send_stop(); // 1381
|
||
|
} // 1382
|
||
|
that._send_stop = null; // 1383
|
||
|
}; // 1384
|
||
|
// 1385
|
||
|
var jsonPGenericSender = function(url, payload, callback) { // 1386
|
||
|
var that = this; // 1387
|
||
|
// 1388
|
||
|
if (!('_send_form' in that)) { // 1389
|
||
|
var form = that._send_form = _document.createElement('form'); // 1390
|
||
|
var area = that._send_area = _document.createElement('textarea'); // 1391
|
||
|
area.name = 'd'; // 1392
|
||
|
form.style.display = 'none'; // 1393
|
||
|
form.style.position = 'absolute'; // 1394
|
||
|
form.method = 'POST'; // 1395
|
||
|
form.enctype = 'application/x-www-form-urlencoded'; // 1396
|
||
|
form.acceptCharset = "UTF-8"; // 1397
|
||
|
form.appendChild(area); // 1398
|
||
|
_document.body.appendChild(form); // 1399
|
||
|
} // 1400
|
||
|
var form = that._send_form; // 1401
|
||
|
var area = that._send_area; // 1402
|
||
|
var id = 'a' + utils.random_string(8); // 1403
|
||
|
form.target = id; // 1404
|
||
|
form.action = url + '/jsonp_send?i=' + id; // 1405
|
||
|
// 1406
|
||
|
var iframe; // 1407
|
||
|
try { // 1408
|
||
|
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher) // 1409
|
||
|
iframe = _document.createElement('<iframe name="'+ id +'">'); // 1410
|
||
|
} catch(x) { // 1411
|
||
|
iframe = _document.createElement('iframe'); // 1412
|
||
|
iframe.name = id; // 1413
|
||
|
} // 1414
|
||
|
iframe.id = id; // 1415
|
||
|
form.appendChild(iframe); // 1416
|
||
|
iframe.style.display = 'none'; // 1417
|
||
|
// 1418
|
||
|
try { // 1419
|
||
|
area.value = payload; // 1420
|
||
|
} catch(e) { // 1421
|
||
|
utils.log('Your browser is seriously broken. Go home! ' + e.message); // 1422
|
||
|
} // 1423
|
||
|
form.submit(); // 1424
|
||
|
// 1425
|
||
|
var completed = function(e) { // 1426
|
||
|
if (!iframe.onerror) return; // 1427
|
||
|
iframe.onreadystatechange = iframe.onerror = iframe.onload = null; // 1428
|
||
|
// Opera mini doesn't like if we GC iframe // 1429
|
||
|
// immediately, thus this timeout. // 1430
|
||
|
utils.delay(500, function() { // 1431
|
||
|
iframe.parentNode.removeChild(iframe); // 1432
|
||
|
iframe = null; // 1433
|
||
|
}); // 1434
|
||
|
area.value = ''; // 1435
|
||
|
// It is not possible to detect if the iframe succeeded or // 1436
|
||
|
// failed to submit our form. // 1437
|
||
|
callback(true); // 1438
|
||
|
}; // 1439
|
||
|
iframe.onerror = iframe.onload = completed; // 1440
|
||
|
iframe.onreadystatechange = function(e) { // 1441
|
||
|
if (iframe.readyState == 'complete') completed(); // 1442
|
||
|
}; // 1443
|
||
|
return completed; // 1444
|
||
|
}; // 1445
|
||
|
// 1446
|
||
|
var createAjaxSender = function(AjaxObject) { // 1447
|
||
|
return function(url, payload, callback) { // 1448
|
||
|
var xo = new AjaxObject('POST', url + '/xhr_send', payload); // 1449
|
||
|
xo.onfinish = function(status, text) { // 1450
|
||
|
callback(status === 200 || status === 204, // 1451
|
||
|
'http status ' + status); // 1452
|
||
|
}; // 1453
|
||
|
return function(abort_reason) { // 1454
|
||
|
callback(false, abort_reason); // 1455
|
||
|
}; // 1456
|
||
|
}; // 1457
|
||
|
}; // 1458
|
||
|
// [*] End of lib/trans-sender.js // 1459
|
||
|
// 1460
|
||
|
// 1461
|
||
|
// [*] Including lib/trans-jsonp-receiver.js // 1462
|
||
|
/* // 1463
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1464
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1465
|
||
|
* // 1466
|
||
|
* For the license see COPYING. // 1467
|
||
|
* ***** END LICENSE BLOCK ***** // 1468
|
||
|
*/ // 1469
|
||
|
// 1470
|
||
|
// Parts derived from Socket.io: // 1471
|
||
|
// https://github.com/LearnBoost/socket.io/blob/0.6.17/lib/socket.io/transports/jsonp-polling.js // 1472
|
||
|
// and jQuery-JSONP: // 1473
|
||
|
// https://code.google.com/p/jquery-jsonp/source/browse/trunk/core/jquery.jsonp.js // 1474
|
||
|
var jsonPGenericReceiver = function(url, callback) { // 1475
|
||
|
var tref; // 1476
|
||
|
var script = _document.createElement('script'); // 1477
|
||
|
var script2; // Opera synchronous load trick. // 1478
|
||
|
var close_script = function(frame) { // 1479
|
||
|
if (script2) { // 1480
|
||
|
script2.parentNode.removeChild(script2); // 1481
|
||
|
script2 = null; // 1482
|
||
|
} // 1483
|
||
|
if (script) { // 1484
|
||
|
clearTimeout(tref); // 1485
|
||
|
// Unfortunately, you can't really abort script loading of // 1486
|
||
|
// the script. // 1487
|
||
|
script.parentNode.removeChild(script); // 1488
|
||
|
script.onreadystatechange = script.onerror = // 1489
|
||
|
script.onload = script.onclick = null; // 1490
|
||
|
script = null; // 1491
|
||
|
callback(frame); // 1492
|
||
|
callback = null; // 1493
|
||
|
} // 1494
|
||
|
}; // 1495
|
||
|
// 1496
|
||
|
// IE9 fires 'error' event after orsc or before, in random order. // 1497
|
||
|
var loaded_okay = false; // 1498
|
||
|
var error_timer = null; // 1499
|
||
|
// 1500
|
||
|
script.id = 'a' + utils.random_string(8); // 1501
|
||
|
script.src = url; // 1502
|
||
|
script.type = 'text/javascript'; // 1503
|
||
|
script.charset = 'UTF-8'; // 1504
|
||
|
script.onerror = function(e) { // 1505
|
||
|
if (!error_timer) { // 1506
|
||
|
// Delay firing close_script. // 1507
|
||
|
error_timer = setTimeout(function() { // 1508
|
||
|
if (!loaded_okay) { // 1509
|
||
|
close_script(utils.closeFrame( // 1510
|
||
|
1006, // 1511
|
||
|
"JSONP script loaded abnormally (onerror)")); // 1512
|
||
|
} // 1513
|
||
|
}, 1000); // 1514
|
||
|
} // 1515
|
||
|
}; // 1516
|
||
|
script.onload = function(e) { // 1517
|
||
|
close_script(utils.closeFrame(1006, "JSONP script loaded abnormally (onload)")); // 1518
|
||
|
}; // 1519
|
||
|
// 1520
|
||
|
script.onreadystatechange = function(e) { // 1521
|
||
|
if (/loaded|closed/.test(script.readyState)) { // 1522
|
||
|
if (script && script.htmlFor && script.onclick) { // 1523
|
||
|
loaded_okay = true; // 1524
|
||
|
try { // 1525
|
||
|
// In IE, actually execute the script. // 1526
|
||
|
script.onclick(); // 1527
|
||
|
} catch (x) {} // 1528
|
||
|
} // 1529
|
||
|
if (script) { // 1530
|
||
|
close_script(utils.closeFrame(1006, "JSONP script loaded abnormally (onreadystatechange)")); // 1531
|
||
|
} // 1532
|
||
|
} // 1533
|
||
|
}; // 1534
|
||
|
// IE: event/htmlFor/onclick trick. // 1535
|
||
|
// One can't rely on proper order for onreadystatechange. In order to // 1536
|
||
|
// make sure, set a 'htmlFor' and 'event' properties, so that // 1537
|
||
|
// script code will be installed as 'onclick' handler for the // 1538
|
||
|
// script object. Later, onreadystatechange, manually execute this // 1539
|
||
|
// code. FF and Chrome doesn't work with 'event' and 'htmlFor' // 1540
|
||
|
// set. For reference see: // 1541
|
||
|
// http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html // 1542
|
||
|
// Also, read on that about script ordering: // 1543
|
||
|
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order // 1544
|
||
|
if (typeof script.async === 'undefined' && _document.attachEvent) { // 1545
|
||
|
// According to mozilla docs, in recent browsers script.async defaults // 1546
|
||
|
// to 'true', so we may use it to detect a good browser: // 1547
|
||
|
// https://developer.mozilla.org/en/HTML/Element/script // 1548
|
||
|
if (!/opera/i.test(navigator.userAgent)) { // 1549
|
||
|
// Naively assume we're in IE // 1550
|
||
|
try { // 1551
|
||
|
script.htmlFor = script.id; // 1552
|
||
|
script.event = "onclick"; // 1553
|
||
|
} catch (x) {} // 1554
|
||
|
script.async = true; // 1555
|
||
|
} else { // 1556
|
||
|
// Opera, second sync script hack // 1557
|
||
|
script2 = _document.createElement('script'); // 1558
|
||
|
script2.text = "try{var a = document.getElementById('"+script.id+"'); if(a)a.onerror();}catch(x){};"; // 1559
|
||
|
script.async = script2.async = false; // 1560
|
||
|
} // 1561
|
||
|
} // 1562
|
||
|
if (typeof script.async !== 'undefined') { // 1563
|
||
|
script.async = true; // 1564
|
||
|
} // 1565
|
||
|
// 1566
|
||
|
// Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty. // 1567
|
||
|
tref = setTimeout(function() { // 1568
|
||
|
close_script(utils.closeFrame(1006, "JSONP script loaded abnormally (timeout)")); // 1569
|
||
|
}, 35000); // 1570
|
||
|
// 1571
|
||
|
var head = _document.getElementsByTagName('head')[0]; // 1572
|
||
|
head.insertBefore(script, head.firstChild); // 1573
|
||
|
if (script2) { // 1574
|
||
|
head.insertBefore(script2, head.firstChild); // 1575
|
||
|
} // 1576
|
||
|
return close_script; // 1577
|
||
|
}; // 1578
|
||
|
// [*] End of lib/trans-jsonp-receiver.js // 1579
|
||
|
// 1580
|
||
|
// 1581
|
||
|
// [*] Including lib/trans-jsonp-polling.js // 1582
|
||
|
/* // 1583
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1584
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1585
|
||
|
* // 1586
|
||
|
* For the license see COPYING. // 1587
|
||
|
* ***** END LICENSE BLOCK ***** // 1588
|
||
|
*/ // 1589
|
||
|
// 1590
|
||
|
// The simplest and most robust transport, using the well-know cross // 1591
|
||
|
// domain hack - JSONP. This transport is quite inefficient - one // 1592
|
||
|
// mssage could use up to one http request. But at least it works almost // 1593
|
||
|
// everywhere. // 1594
|
||
|
// Known limitations: // 1595
|
||
|
// o you will get a spinning cursor // 1596
|
||
|
// o for Konqueror a dumb timer is needed to detect errors // 1597
|
||
|
// 1598
|
||
|
// 1599
|
||
|
var JsonPTransport = SockJS['jsonp-polling'] = function(ri, trans_url) { // 1600
|
||
|
utils.polluteGlobalNamespace(); // 1601
|
||
|
var that = this; // 1602
|
||
|
that.ri = ri; // 1603
|
||
|
that.trans_url = trans_url; // 1604
|
||
|
that.send_constructor(jsonPGenericSender); // 1605
|
||
|
that._schedule_recv(); // 1606
|
||
|
}; // 1607
|
||
|
// 1608
|
||
|
// Inheritnace // 1609
|
||
|
JsonPTransport.prototype = new BufferedSender(); // 1610
|
||
|
// 1611
|
||
|
JsonPTransport.prototype._schedule_recv = function() { // 1612
|
||
|
var that = this; // 1613
|
||
|
var callback = function(data) { // 1614
|
||
|
that._recv_stop = null; // 1615
|
||
|
if (data) { // 1616
|
||
|
// no data - heartbeat; // 1617
|
||
|
if (!that._is_closing) { // 1618
|
||
|
that.ri._didMessage(data); // 1619
|
||
|
} // 1620
|
||
|
} // 1621
|
||
|
// The message can be a close message, and change is_closing state. // 1622
|
||
|
if (!that._is_closing) { // 1623
|
||
|
that._schedule_recv(); // 1624
|
||
|
} // 1625
|
||
|
}; // 1626
|
||
|
that._recv_stop = jsonPReceiverWrapper(that.trans_url + '/jsonp', // 1627
|
||
|
jsonPGenericReceiver, callback); // 1628
|
||
|
}; // 1629
|
||
|
// 1630
|
||
|
JsonPTransport.enabled = function() { // 1631
|
||
|
return true; // 1632
|
||
|
}; // 1633
|
||
|
// 1634
|
||
|
JsonPTransport.need_body = true; // 1635
|
||
|
// 1636
|
||
|
// 1637
|
||
|
JsonPTransport.prototype.doCleanup = function() { // 1638
|
||
|
var that = this; // 1639
|
||
|
that._is_closing = true; // 1640
|
||
|
if (that._recv_stop) { // 1641
|
||
|
that._recv_stop(); // 1642
|
||
|
} // 1643
|
||
|
that.ri = that._recv_stop = null; // 1644
|
||
|
that.send_destructor(); // 1645
|
||
|
}; // 1646
|
||
|
// 1647
|
||
|
// 1648
|
||
|
// Abstract away code that handles global namespace pollution. // 1649
|
||
|
var jsonPReceiverWrapper = function(url, constructReceiver, user_callback) { // 1650
|
||
|
var id = 'a' + utils.random_string(6); // 1651
|
||
|
var url_id = url + '?c=' + escape(WPrefix + '.' + id); // 1652
|
||
|
// 1653
|
||
|
// Unfortunately it is not possible to abort loading of the // 1654
|
||
|
// script. We need to keep track of frake close frames. // 1655
|
||
|
var aborting = 0; // 1656
|
||
|
// 1657
|
||
|
// Callback will be called exactly once. // 1658
|
||
|
var callback = function(frame) { // 1659
|
||
|
switch(aborting) { // 1660
|
||
|
case 0: // 1661
|
||
|
// Normal behaviour - delete hook _and_ emit message. // 1662
|
||
|
delete _window[WPrefix][id]; // 1663
|
||
|
user_callback(frame); // 1664
|
||
|
break; // 1665
|
||
|
case 1: // 1666
|
||
|
// Fake close frame - emit but don't delete hook. // 1667
|
||
|
user_callback(frame); // 1668
|
||
|
aborting = 2; // 1669
|
||
|
break; // 1670
|
||
|
case 2: // 1671
|
||
|
// Got frame after connection was closed, delete hook, don't emit. // 1672
|
||
|
delete _window[WPrefix][id]; // 1673
|
||
|
break; // 1674
|
||
|
} // 1675
|
||
|
}; // 1676
|
||
|
// 1677
|
||
|
var close_script = constructReceiver(url_id, callback); // 1678
|
||
|
_window[WPrefix][id] = close_script; // 1679
|
||
|
var stop = function() { // 1680
|
||
|
if (_window[WPrefix][id]) { // 1681
|
||
|
aborting = 1; // 1682
|
||
|
_window[WPrefix][id](utils.closeFrame(1000, "JSONP user aborted read")); // 1683
|
||
|
} // 1684
|
||
|
}; // 1685
|
||
|
return stop; // 1686
|
||
|
}; // 1687
|
||
|
// [*] End of lib/trans-jsonp-polling.js // 1688
|
||
|
// 1689
|
||
|
// 1690
|
||
|
// [*] Including lib/trans-xhr.js // 1691
|
||
|
/* // 1692
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1693
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1694
|
||
|
* // 1695
|
||
|
* For the license see COPYING. // 1696
|
||
|
* ***** END LICENSE BLOCK ***** // 1697
|
||
|
*/ // 1698
|
||
|
// 1699
|
||
|
var AjaxBasedTransport = function() {}; // 1700
|
||
|
AjaxBasedTransport.prototype = new BufferedSender(); // 1701
|
||
|
// 1702
|
||
|
AjaxBasedTransport.prototype.run = function(ri, trans_url, // 1703
|
||
|
url_suffix, Receiver, AjaxObject) { // 1704
|
||
|
var that = this; // 1705
|
||
|
that.ri = ri; // 1706
|
||
|
that.trans_url = trans_url; // 1707
|
||
|
that.send_constructor(createAjaxSender(AjaxObject)); // 1708
|
||
|
that.poll = new Polling(ri, Receiver, // 1709
|
||
|
trans_url + url_suffix, AjaxObject); // 1710
|
||
|
}; // 1711
|
||
|
// 1712
|
||
|
AjaxBasedTransport.prototype.doCleanup = function() { // 1713
|
||
|
var that = this; // 1714
|
||
|
if (that.poll) { // 1715
|
||
|
that.poll.abort(); // 1716
|
||
|
that.poll = null; // 1717
|
||
|
} // 1718
|
||
|
}; // 1719
|
||
|
// 1720
|
||
|
// xhr-streaming // 1721
|
||
|
var XhrStreamingTransport = SockJS['xhr-streaming'] = function(ri, trans_url) { // 1722
|
||
|
this.run(ri, trans_url, '/xhr_streaming', XhrReceiver, utils.XHRCorsObject); // 1723
|
||
|
}; // 1724
|
||
|
// 1725
|
||
|
XhrStreamingTransport.prototype = new AjaxBasedTransport(); // 1726
|
||
|
// 1727
|
||
|
XhrStreamingTransport.enabled = function() { // 1728
|
||
|
// Support for CORS Ajax aka Ajax2? Opera 12 claims CORS but // 1729
|
||
|
// doesn't do streaming. // 1730
|
||
|
return (_window.XMLHttpRequest && // 1731
|
||
|
'withCredentials' in new XMLHttpRequest() && // 1732
|
||
|
(!/opera/i.test(navigator.userAgent))); // 1733
|
||
|
}; // 1734
|
||
|
XhrStreamingTransport.roundTrips = 2; // preflight, ajax // 1735
|
||
|
// 1736
|
||
|
// Safari gets confused when a streaming ajax request is started // 1737
|
||
|
// before onload. This causes the load indicator to spin indefinetely. // 1738
|
||
|
XhrStreamingTransport.need_body = true; // 1739
|
||
|
// 1740
|
||
|
// 1741
|
||
|
// According to: // 1742
|
||
|
// http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests // 1743
|
||
|
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ // 1744
|
||
|
// 1745
|
||
|
// 1746
|
||
|
// xdr-streaming // 1747
|
||
|
var XdrStreamingTransport = SockJS['xdr-streaming'] = function(ri, trans_url) { // 1748
|
||
|
this.run(ri, trans_url, '/xhr_streaming', XhrReceiver, utils.XDRObject); // 1749
|
||
|
}; // 1750
|
||
|
// 1751
|
||
|
XdrStreamingTransport.prototype = new AjaxBasedTransport(); // 1752
|
||
|
// 1753
|
||
|
XdrStreamingTransport.enabled = function() { // 1754
|
||
|
return !!_window.XDomainRequest; // 1755
|
||
|
}; // 1756
|
||
|
XdrStreamingTransport.roundTrips = 2; // preflight, ajax // 1757
|
||
|
// 1758
|
||
|
// 1759
|
||
|
// 1760
|
||
|
// xhr-polling // 1761
|
||
|
var XhrPollingTransport = SockJS['xhr-polling'] = function(ri, trans_url) { // 1762
|
||
|
this.run(ri, trans_url, '/xhr', XhrReceiver, utils.XHRCorsObject); // 1763
|
||
|
}; // 1764
|
||
|
// 1765
|
||
|
XhrPollingTransport.prototype = new AjaxBasedTransport(); // 1766
|
||
|
// 1767
|
||
|
XhrPollingTransport.enabled = XhrStreamingTransport.enabled; // 1768
|
||
|
XhrPollingTransport.roundTrips = 2; // preflight, ajax // 1769
|
||
|
// 1770
|
||
|
// 1771
|
||
|
// xdr-polling // 1772
|
||
|
var XdrPollingTransport = SockJS['xdr-polling'] = function(ri, trans_url) { // 1773
|
||
|
this.run(ri, trans_url, '/xhr', XhrReceiver, utils.XDRObject); // 1774
|
||
|
}; // 1775
|
||
|
// 1776
|
||
|
XdrPollingTransport.prototype = new AjaxBasedTransport(); // 1777
|
||
|
// 1778
|
||
|
XdrPollingTransport.enabled = XdrStreamingTransport.enabled; // 1779
|
||
|
XdrPollingTransport.roundTrips = 2; // preflight, ajax // 1780
|
||
|
// [*] End of lib/trans-xhr.js // 1781
|
||
|
// 1782
|
||
|
// 1783
|
||
|
// [*] Including lib/trans-iframe.js // 1784
|
||
|
/* // 1785
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1786
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1787
|
||
|
* // 1788
|
||
|
* For the license see COPYING. // 1789
|
||
|
* ***** END LICENSE BLOCK ***** // 1790
|
||
|
*/ // 1791
|
||
|
// 1792
|
||
|
// Few cool transports do work only for same-origin. In order to make // 1793
|
||
|
// them working cross-domain we shall use iframe, served form the // 1794
|
||
|
// remote domain. New browsers, have capabilities to communicate with // 1795
|
||
|
// cross domain iframe, using postMessage(). In IE it was implemented // 1796
|
||
|
// from IE 8+, but of course, IE got some details wrong: // 1797
|
||
|
// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx // 1798
|
||
|
// http://stevesouders.com/misc/test-postmessage.php // 1799
|
||
|
// 1800
|
||
|
var IframeTransport = function() {}; // 1801
|
||
|
// 1802
|
||
|
IframeTransport.prototype.i_constructor = function(ri, trans_url, base_url) { // 1803
|
||
|
var that = this; // 1804
|
||
|
that.ri = ri; // 1805
|
||
|
that.origin = utils.getOrigin(base_url); // 1806
|
||
|
that.base_url = base_url; // 1807
|
||
|
that.trans_url = trans_url; // 1808
|
||
|
// 1809
|
||
|
var iframe_url = base_url + '/iframe.html'; // 1810
|
||
|
if (that.ri._options.devel) { // 1811
|
||
|
iframe_url += '?t=' + (+new Date); // 1812
|
||
|
} // 1813
|
||
|
that.window_id = utils.random_string(8); // 1814
|
||
|
iframe_url += '#' + that.window_id; // 1815
|
||
|
// 1816
|
||
|
that.iframeObj = utils.createIframe(iframe_url, function(r) { // 1817
|
||
|
that.ri._didClose(1006, "Unable to load an iframe (" + r + ")"); // 1818
|
||
|
}); // 1819
|
||
|
// 1820
|
||
|
that.onmessage_cb = utils.bind(that.onmessage, that); // 1821
|
||
|
utils.attachMessage(that.onmessage_cb); // 1822
|
||
|
}; // 1823
|
||
|
// 1824
|
||
|
IframeTransport.prototype.doCleanup = function() { // 1825
|
||
|
var that = this; // 1826
|
||
|
if (that.iframeObj) { // 1827
|
||
|
utils.detachMessage(that.onmessage_cb); // 1828
|
||
|
try { // 1829
|
||
|
// When the iframe is not loaded, IE raises an exception // 1830
|
||
|
// on 'contentWindow'. // 1831
|
||
|
if (that.iframeObj.iframe.contentWindow) { // 1832
|
||
|
that.postMessage('c'); // 1833
|
||
|
} // 1834
|
||
|
} catch (x) {} // 1835
|
||
|
that.iframeObj.cleanup(); // 1836
|
||
|
that.iframeObj = null; // 1837
|
||
|
that.onmessage_cb = that.iframeObj = null; // 1838
|
||
|
} // 1839
|
||
|
}; // 1840
|
||
|
// 1841
|
||
|
IframeTransport.prototype.onmessage = function(e) { // 1842
|
||
|
var that = this; // 1843
|
||
|
if (e.origin !== that.origin) return; // 1844
|
||
|
var window_id = e.data.slice(0, 8); // 1845
|
||
|
var type = e.data.slice(8, 9); // 1846
|
||
|
var data = e.data.slice(9); // 1847
|
||
|
// 1848
|
||
|
if (window_id !== that.window_id) return; // 1849
|
||
|
// 1850
|
||
|
switch(type) { // 1851
|
||
|
case 's': // 1852
|
||
|
that.iframeObj.loaded(); // 1853
|
||
|
that.postMessage('s', JSON.stringify([SockJS.version, that.protocol, that.trans_url, that.base_url])); // 1854
|
||
|
break; // 1855
|
||
|
case 't': // 1856
|
||
|
that.ri._didMessage(data); // 1857
|
||
|
break; // 1858
|
||
|
} // 1859
|
||
|
}; // 1860
|
||
|
// 1861
|
||
|
IframeTransport.prototype.postMessage = function(type, data) { // 1862
|
||
|
var that = this; // 1863
|
||
|
that.iframeObj.post(that.window_id + type + (data || ''), that.origin); // 1864
|
||
|
}; // 1865
|
||
|
// 1866
|
||
|
IframeTransport.prototype.doSend = function (message) { // 1867
|
||
|
this.postMessage('m', message); // 1868
|
||
|
}; // 1869
|
||
|
// 1870
|
||
|
IframeTransport.enabled = function() { // 1871
|
||
|
// postMessage misbehaves in konqueror 4.6.5 - the messages are delivered with // 1872
|
||
|
// huge delay, or not at all. // 1873
|
||
|
var konqueror = navigator && navigator.userAgent && navigator.userAgent.indexOf('Konqueror') !== -1; // 1874
|
||
|
return ((typeof _window.postMessage === 'function' || // 1875
|
||
|
typeof _window.postMessage === 'object') && (!konqueror)); // 1876
|
||
|
}; // 1877
|
||
|
// [*] End of lib/trans-iframe.js // 1878
|
||
|
// 1879
|
||
|
// 1880
|
||
|
// [*] Including lib/trans-iframe-within.js // 1881
|
||
|
/* // 1882
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1883
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1884
|
||
|
* // 1885
|
||
|
* For the license see COPYING. // 1886
|
||
|
* ***** END LICENSE BLOCK ***** // 1887
|
||
|
*/ // 1888
|
||
|
// 1889
|
||
|
var curr_window_id; // 1890
|
||
|
// 1891
|
||
|
var postMessage = function (type, data) { // 1892
|
||
|
if(parent !== _window) { // 1893
|
||
|
parent.postMessage(curr_window_id + type + (data || ''), '*'); // 1894
|
||
|
} else { // 1895
|
||
|
utils.log("Can't postMessage, no parent window.", type, data); // 1896
|
||
|
} // 1897
|
||
|
}; // 1898
|
||
|
// 1899
|
||
|
var FacadeJS = function() {}; // 1900
|
||
|
FacadeJS.prototype._didClose = function (code, reason) { // 1901
|
||
|
postMessage('t', utils.closeFrame(code, reason)); // 1902
|
||
|
}; // 1903
|
||
|
FacadeJS.prototype._didMessage = function (frame) { // 1904
|
||
|
postMessage('t', frame); // 1905
|
||
|
}; // 1906
|
||
|
FacadeJS.prototype._doSend = function (data) { // 1907
|
||
|
this._transport.doSend(data); // 1908
|
||
|
}; // 1909
|
||
|
FacadeJS.prototype._doCleanup = function () { // 1910
|
||
|
this._transport.doCleanup(); // 1911
|
||
|
}; // 1912
|
||
|
// 1913
|
||
|
utils.parent_origin = undefined; // 1914
|
||
|
// 1915
|
||
|
SockJS.bootstrap_iframe = function() { // 1916
|
||
|
var facade; // 1917
|
||
|
curr_window_id = _document.location.hash.slice(1); // 1918
|
||
|
var onMessage = function(e) { // 1919
|
||
|
if(e.source !== parent) return; // 1920
|
||
|
if(typeof utils.parent_origin === 'undefined') // 1921
|
||
|
utils.parent_origin = e.origin; // 1922
|
||
|
if (e.origin !== utils.parent_origin) return; // 1923
|
||
|
// 1924
|
||
|
var window_id = e.data.slice(0, 8); // 1925
|
||
|
var type = e.data.slice(8, 9); // 1926
|
||
|
var data = e.data.slice(9); // 1927
|
||
|
if (window_id !== curr_window_id) return; // 1928
|
||
|
switch(type) { // 1929
|
||
|
case 's': // 1930
|
||
|
var p = JSON.parse(data); // 1931
|
||
|
var version = p[0]; // 1932
|
||
|
var protocol = p[1]; // 1933
|
||
|
var trans_url = p[2]; // 1934
|
||
|
var base_url = p[3]; // 1935
|
||
|
if (version !== SockJS.version) { // 1936
|
||
|
utils.log("Incompatibile SockJS! Main site uses:" + // 1937
|
||
|
" \"" + version + "\", the iframe:" + // 1938
|
||
|
" \"" + SockJS.version + "\"."); // 1939
|
||
|
} // 1940
|
||
|
if (!utils.flatUrl(trans_url) || !utils.flatUrl(base_url)) { // 1941
|
||
|
utils.log("Only basic urls are supported in SockJS"); // 1942
|
||
|
return; // 1943
|
||
|
} // 1944
|
||
|
// 1945
|
||
|
if (!utils.isSameOriginUrl(trans_url) || // 1946
|
||
|
!utils.isSameOriginUrl(base_url)) { // 1947
|
||
|
utils.log("Can't connect to different domain from within an " + // 1948
|
||
|
"iframe. (" + JSON.stringify([_window.location.href, trans_url, base_url]) + // 1949
|
||
|
")"); // 1950
|
||
|
return; // 1951
|
||
|
} // 1952
|
||
|
facade = new FacadeJS(); // 1953
|
||
|
facade._transport = new FacadeJS[protocol](facade, trans_url, base_url); // 1954
|
||
|
break; // 1955
|
||
|
case 'm': // 1956
|
||
|
facade._doSend(data); // 1957
|
||
|
break; // 1958
|
||
|
case 'c': // 1959
|
||
|
if (facade) // 1960
|
||
|
facade._doCleanup(); // 1961
|
||
|
facade = null; // 1962
|
||
|
break; // 1963
|
||
|
} // 1964
|
||
|
}; // 1965
|
||
|
// 1966
|
||
|
// alert('test ticker'); // 1967
|
||
|
// facade = new FacadeJS(); // 1968
|
||
|
// facade._transport = new FacadeJS['w-iframe-xhr-polling'](facade, 'http://host.com:9999/ticker/12/basd'); // 1969
|
||
|
// 1970
|
||
|
utils.attachMessage(onMessage); // 1971
|
||
|
// 1972
|
||
|
// Start // 1973
|
||
|
postMessage('s'); // 1974
|
||
|
}; // 1975
|
||
|
// [*] End of lib/trans-iframe-within.js // 1976
|
||
|
// 1977
|
||
|
// 1978
|
||
|
// [*] Including lib/info.js // 1979
|
||
|
/* // 1980
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 1981
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 1982
|
||
|
* // 1983
|
||
|
* For the license see COPYING. // 1984
|
||
|
* ***** END LICENSE BLOCK ***** // 1985
|
||
|
*/ // 1986
|
||
|
// 1987
|
||
|
var InfoReceiver = function(base_url, AjaxObject) { // 1988
|
||
|
var that = this; // 1989
|
||
|
utils.delay(function(){that.doXhr(base_url, AjaxObject);}); // 1990
|
||
|
}; // 1991
|
||
|
// 1992
|
||
|
InfoReceiver.prototype = new EventEmitter(['finish']); // 1993
|
||
|
// 1994
|
||
|
InfoReceiver.prototype.doXhr = function(base_url, AjaxObject) { // 1995
|
||
|
var that = this; // 1996
|
||
|
var t0 = (new Date()).getTime(); // 1997
|
||
|
// 1998
|
||
|
// <METEOR> // 1999
|
||
|
// https://github.com/sockjs/sockjs-client/pull/129 // 2000
|
||
|
// var xo = new AjaxObject('GET', base_url + '/info'); // 2001
|
||
|
// 2002
|
||
|
var xo = new AjaxObject( // 2003
|
||
|
// add cachebusting parameter to url to work around a chrome bug: // 2004
|
||
|
// https://code.google.com/p/chromium/issues/detail?id=263981 // 2005
|
||
|
// or misbehaving proxies. // 2006
|
||
|
'GET', base_url + '/info?cb=' + utils.random_string(10)) // 2007
|
||
|
// </METEOR> // 2008
|
||
|
// 2009
|
||
|
var tref = utils.delay(8000, // 2010
|
||
|
function(){xo.ontimeout();}); // 2011
|
||
|
// 2012
|
||
|
xo.onfinish = function(status, text) { // 2013
|
||
|
clearTimeout(tref); // 2014
|
||
|
tref = null; // 2015
|
||
|
if (status === 200) { // 2016
|
||
|
var rtt = (new Date()).getTime() - t0; // 2017
|
||
|
var info = JSON.parse(text); // 2018
|
||
|
if (typeof info !== 'object') info = {}; // 2019
|
||
|
that.emit('finish', info, rtt); // 2020
|
||
|
} else { // 2021
|
||
|
that.emit('finish'); // 2022
|
||
|
} // 2023
|
||
|
}; // 2024
|
||
|
xo.ontimeout = function() { // 2025
|
||
|
xo.close(); // 2026
|
||
|
that.emit('finish'); // 2027
|
||
|
}; // 2028
|
||
|
}; // 2029
|
||
|
// 2030
|
||
|
var InfoReceiverIframe = function(base_url) { // 2031
|
||
|
var that = this; // 2032
|
||
|
var go = function() { // 2033
|
||
|
var ifr = new IframeTransport(); // 2034
|
||
|
ifr.protocol = 'w-iframe-info-receiver'; // 2035
|
||
|
var fun = function(r) { // 2036
|
||
|
if (typeof r === 'string' && r.substr(0,1) === 'm') { // 2037
|
||
|
var d = JSON.parse(r.substr(1)); // 2038
|
||
|
var info = d[0], rtt = d[1]; // 2039
|
||
|
that.emit('finish', info, rtt); // 2040
|
||
|
} else { // 2041
|
||
|
that.emit('finish'); // 2042
|
||
|
} // 2043
|
||
|
ifr.doCleanup(); // 2044
|
||
|
ifr = null; // 2045
|
||
|
}; // 2046
|
||
|
var mock_ri = { // 2047
|
||
|
_options: {}, // 2048
|
||
|
_didClose: fun, // 2049
|
||
|
_didMessage: fun // 2050
|
||
|
}; // 2051
|
||
|
ifr.i_constructor(mock_ri, base_url, base_url); // 2052
|
||
|
} // 2053
|
||
|
if(!_document.body) { // 2054
|
||
|
utils.attachEvent('load', go); // 2055
|
||
|
} else { // 2056
|
||
|
go(); // 2057
|
||
|
} // 2058
|
||
|
}; // 2059
|
||
|
InfoReceiverIframe.prototype = new EventEmitter(['finish']); // 2060
|
||
|
// 2061
|
||
|
// 2062
|
||
|
var InfoReceiverFake = function() { // 2063
|
||
|
// It may not be possible to do cross domain AJAX to get the info // 2064
|
||
|
// data, for example for IE7. But we want to run JSONP, so let's // 2065
|
||
|
// fake the response, with rtt=2s (rto=6s). // 2066
|
||
|
var that = this; // 2067
|
||
|
utils.delay(function() { // 2068
|
||
|
that.emit('finish', {}, 2000); // 2069
|
||
|
}); // 2070
|
||
|
}; // 2071
|
||
|
InfoReceiverFake.prototype = new EventEmitter(['finish']); // 2072
|
||
|
// 2073
|
||
|
var createInfoReceiver = function(base_url) { // 2074
|
||
|
if (utils.isSameOriginUrl(base_url)) { // 2075
|
||
|
// If, for some reason, we have SockJS locally - there's no // 2076
|
||
|
// need to start up the complex machinery. Just use ajax. // 2077
|
||
|
return new InfoReceiver(base_url, utils.XHRLocalObject); // 2078
|
||
|
} // 2079
|
||
|
switch (utils.isXHRCorsCapable()) { // 2080
|
||
|
case 1: // 2081
|
||
|
// XHRLocalObject -> no_credentials=true // 2082
|
||
|
return new InfoReceiver(base_url, utils.XHRLocalObject); // 2083
|
||
|
case 2: // 2084
|
||
|
// <METEOR> // 2085
|
||
|
// https://github.com/sockjs/sockjs-client/issues/79 // 2086
|
||
|
// XDR doesn't work across different schemes // 2087
|
||
|
// http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
|
||
|
if (utils.isSameOriginScheme(base_url)) // 2089
|
||
|
return new InfoReceiver(base_url, utils.XDRObject); // 2090
|
||
|
else // 2091
|
||
|
return new InfoReceiverFake(); // 2092
|
||
|
// </METEOR> // 2093
|
||
|
case 3: // 2094
|
||
|
// Opera // 2095
|
||
|
return new InfoReceiverIframe(base_url); // 2096
|
||
|
default: // 2097
|
||
|
// IE 7 // 2098
|
||
|
return new InfoReceiverFake(); // 2099
|
||
|
}; // 2100
|
||
|
}; // 2101
|
||
|
// 2102
|
||
|
// 2103
|
||
|
var WInfoReceiverIframe = FacadeJS['w-iframe-info-receiver'] = function(ri, _trans_url, base_url) { // 2104
|
||
|
var ir = new InfoReceiver(base_url, utils.XHRLocalObject); // 2105
|
||
|
ir.onfinish = function(info, rtt) { // 2106
|
||
|
ri._didMessage('m'+JSON.stringify([info, rtt])); // 2107
|
||
|
ri._didClose(); // 2108
|
||
|
} // 2109
|
||
|
}; // 2110
|
||
|
WInfoReceiverIframe.prototype.doCleanup = function() {}; // 2111
|
||
|
// [*] End of lib/info.js // 2112
|
||
|
// 2113
|
||
|
// 2114
|
||
|
// [*] Including lib/trans-iframe-eventsource.js // 2115
|
||
|
/* // 2116
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2117
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2118
|
||
|
* // 2119
|
||
|
* For the license see COPYING. // 2120
|
||
|
* ***** END LICENSE BLOCK ***** // 2121
|
||
|
*/ // 2122
|
||
|
// 2123
|
||
|
var EventSourceIframeTransport = SockJS['iframe-eventsource'] = function () { // 2124
|
||
|
var that = this; // 2125
|
||
|
that.protocol = 'w-iframe-eventsource'; // 2126
|
||
|
that.i_constructor.apply(that, arguments); // 2127
|
||
|
}; // 2128
|
||
|
// 2129
|
||
|
EventSourceIframeTransport.prototype = new IframeTransport(); // 2130
|
||
|
// 2131
|
||
|
EventSourceIframeTransport.enabled = function () { // 2132
|
||
|
return ('EventSource' in _window) && IframeTransport.enabled(); // 2133
|
||
|
}; // 2134
|
||
|
// 2135
|
||
|
EventSourceIframeTransport.need_body = true; // 2136
|
||
|
EventSourceIframeTransport.roundTrips = 3; // html, javascript, eventsource // 2137
|
||
|
// 2138
|
||
|
// 2139
|
||
|
// w-iframe-eventsource // 2140
|
||
|
var EventSourceTransport = FacadeJS['w-iframe-eventsource'] = function(ri, trans_url) { // 2141
|
||
|
this.run(ri, trans_url, '/eventsource', EventSourceReceiver, utils.XHRLocalObject); // 2142
|
||
|
} // 2143
|
||
|
EventSourceTransport.prototype = new AjaxBasedTransport(); // 2144
|
||
|
// [*] End of lib/trans-iframe-eventsource.js // 2145
|
||
|
// 2146
|
||
|
// 2147
|
||
|
// [*] Including lib/trans-iframe-xhr-polling.js // 2148
|
||
|
/* // 2149
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2150
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2151
|
||
|
* // 2152
|
||
|
* For the license see COPYING. // 2153
|
||
|
* ***** END LICENSE BLOCK ***** // 2154
|
||
|
*/ // 2155
|
||
|
// 2156
|
||
|
var XhrPollingIframeTransport = SockJS['iframe-xhr-polling'] = function () { // 2157
|
||
|
var that = this; // 2158
|
||
|
that.protocol = 'w-iframe-xhr-polling'; // 2159
|
||
|
that.i_constructor.apply(that, arguments); // 2160
|
||
|
}; // 2161
|
||
|
// 2162
|
||
|
XhrPollingIframeTransport.prototype = new IframeTransport(); // 2163
|
||
|
// 2164
|
||
|
XhrPollingIframeTransport.enabled = function () { // 2165
|
||
|
return _window.XMLHttpRequest && IframeTransport.enabled(); // 2166
|
||
|
}; // 2167
|
||
|
// 2168
|
||
|
XhrPollingIframeTransport.need_body = true; // 2169
|
||
|
XhrPollingIframeTransport.roundTrips = 3; // html, javascript, xhr // 2170
|
||
|
// 2171
|
||
|
// 2172
|
||
|
// w-iframe-xhr-polling // 2173
|
||
|
var XhrPollingITransport = FacadeJS['w-iframe-xhr-polling'] = function(ri, trans_url) { // 2174
|
||
|
this.run(ri, trans_url, '/xhr', XhrReceiver, utils.XHRLocalObject); // 2175
|
||
|
}; // 2176
|
||
|
// 2177
|
||
|
XhrPollingITransport.prototype = new AjaxBasedTransport(); // 2178
|
||
|
// [*] End of lib/trans-iframe-xhr-polling.js // 2179
|
||
|
// 2180
|
||
|
// 2181
|
||
|
// [*] Including lib/trans-iframe-htmlfile.js // 2182
|
||
|
/* // 2183
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2184
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2185
|
||
|
* // 2186
|
||
|
* For the license see COPYING. // 2187
|
||
|
* ***** END LICENSE BLOCK ***** // 2188
|
||
|
*/ // 2189
|
||
|
// 2190
|
||
|
// This transport generally works in any browser, but will cause a // 2191
|
||
|
// spinning cursor to appear in any browser other than IE. // 2192
|
||
|
// We may test this transport in all browsers - why not, but in // 2193
|
||
|
// production it should be only run in IE. // 2194
|
||
|
// 2195
|
||
|
var HtmlFileIframeTransport = SockJS['iframe-htmlfile'] = function () { // 2196
|
||
|
var that = this; // 2197
|
||
|
that.protocol = 'w-iframe-htmlfile'; // 2198
|
||
|
that.i_constructor.apply(that, arguments); // 2199
|
||
|
}; // 2200
|
||
|
// 2201
|
||
|
// Inheritance. // 2202
|
||
|
HtmlFileIframeTransport.prototype = new IframeTransport(); // 2203
|
||
|
// 2204
|
||
|
HtmlFileIframeTransport.enabled = function() { // 2205
|
||
|
return IframeTransport.enabled(); // 2206
|
||
|
}; // 2207
|
||
|
// 2208
|
||
|
HtmlFileIframeTransport.need_body = true; // 2209
|
||
|
HtmlFileIframeTransport.roundTrips = 3; // html, javascript, htmlfile // 2210
|
||
|
// 2211
|
||
|
// 2212
|
||
|
// w-iframe-htmlfile // 2213
|
||
|
var HtmlFileTransport = FacadeJS['w-iframe-htmlfile'] = function(ri, trans_url) { // 2214
|
||
|
this.run(ri, trans_url, '/htmlfile', HtmlfileReceiver, utils.XHRLocalObject); // 2215
|
||
|
}; // 2216
|
||
|
HtmlFileTransport.prototype = new AjaxBasedTransport(); // 2217
|
||
|
// [*] End of lib/trans-iframe-htmlfile.js // 2218
|
||
|
// 2219
|
||
|
// 2220
|
||
|
// [*] Including lib/trans-polling.js // 2221
|
||
|
/* // 2222
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2223
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2224
|
||
|
* // 2225
|
||
|
* For the license see COPYING. // 2226
|
||
|
* ***** END LICENSE BLOCK ***** // 2227
|
||
|
*/ // 2228
|
||
|
// 2229
|
||
|
var Polling = function(ri, Receiver, recv_url, AjaxObject) { // 2230
|
||
|
var that = this; // 2231
|
||
|
that.ri = ri; // 2232
|
||
|
that.Receiver = Receiver; // 2233
|
||
|
that.recv_url = recv_url; // 2234
|
||
|
that.AjaxObject = AjaxObject; // 2235
|
||
|
that._scheduleRecv(); // 2236
|
||
|
}; // 2237
|
||
|
// 2238
|
||
|
Polling.prototype._scheduleRecv = function() { // 2239
|
||
|
var that = this; // 2240
|
||
|
var poll = that.poll = new that.Receiver(that.recv_url, that.AjaxObject); // 2241
|
||
|
var msg_counter = 0; // 2242
|
||
|
poll.onmessage = function(e) { // 2243
|
||
|
msg_counter += 1; // 2244
|
||
|
that.ri._didMessage(e.data); // 2245
|
||
|
}; // 2246
|
||
|
poll.onclose = function(e) { // 2247
|
||
|
that.poll = poll = poll.onmessage = poll.onclose = null; // 2248
|
||
|
if (!that.poll_is_closing) { // 2249
|
||
|
if (e.reason === 'permanent') { // 2250
|
||
|
that.ri._didClose(1006, 'Polling error (' + e.reason + ')'); // 2251
|
||
|
} else { // 2252
|
||
|
that._scheduleRecv(); // 2253
|
||
|
} // 2254
|
||
|
} // 2255
|
||
|
}; // 2256
|
||
|
}; // 2257
|
||
|
// 2258
|
||
|
Polling.prototype.abort = function() { // 2259
|
||
|
var that = this; // 2260
|
||
|
that.poll_is_closing = true; // 2261
|
||
|
if (that.poll) { // 2262
|
||
|
that.poll.abort(); // 2263
|
||
|
} // 2264
|
||
|
}; // 2265
|
||
|
// [*] End of lib/trans-polling.js // 2266
|
||
|
// 2267
|
||
|
// 2268
|
||
|
// [*] Including lib/trans-receiver-eventsource.js // 2269
|
||
|
/* // 2270
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2271
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2272
|
||
|
* // 2273
|
||
|
* For the license see COPYING. // 2274
|
||
|
* ***** END LICENSE BLOCK ***** // 2275
|
||
|
*/ // 2276
|
||
|
// 2277
|
||
|
var EventSourceReceiver = function(url) { // 2278
|
||
|
var that = this; // 2279
|
||
|
var es = new EventSource(url); // 2280
|
||
|
es.onmessage = function(e) { // 2281
|
||
|
that.dispatchEvent(new SimpleEvent('message', // 2282
|
||
|
{'data': unescape(e.data)})); // 2283
|
||
|
}; // 2284
|
||
|
that.es_close = es.onerror = function(e, abort_reason) { // 2285
|
||
|
// ES on reconnection has readyState = 0 or 1. // 2286
|
||
|
// on network error it's CLOSED = 2 // 2287
|
||
|
var reason = abort_reason ? 'user' : // 2288
|
||
|
(es.readyState !== 2 ? 'network' : 'permanent'); // 2289
|
||
|
that.es_close = es.onmessage = es.onerror = null; // 2290
|
||
|
// EventSource reconnects automatically. // 2291
|
||
|
es.close(); // 2292
|
||
|
es = null; // 2293
|
||
|
// Safari and chrome < 15 crash if we close window before // 2294
|
||
|
// waiting for ES cleanup. See: // 2295
|
||
|
// https://code.google.com/p/chromium/issues/detail?id=89155 // 2296
|
||
|
utils.delay(200, function() { // 2297
|
||
|
that.dispatchEvent(new SimpleEvent('close', {reason: reason})); // 2298
|
||
|
}); // 2299
|
||
|
}; // 2300
|
||
|
}; // 2301
|
||
|
// 2302
|
||
|
EventSourceReceiver.prototype = new REventTarget(); // 2303
|
||
|
// 2304
|
||
|
EventSourceReceiver.prototype.abort = function() { // 2305
|
||
|
var that = this; // 2306
|
||
|
if (that.es_close) { // 2307
|
||
|
that.es_close({}, true); // 2308
|
||
|
} // 2309
|
||
|
}; // 2310
|
||
|
// [*] End of lib/trans-receiver-eventsource.js // 2311
|
||
|
// 2312
|
||
|
// 2313
|
||
|
// [*] Including lib/trans-receiver-htmlfile.js // 2314
|
||
|
/* // 2315
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2316
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2317
|
||
|
* // 2318
|
||
|
* For the license see COPYING. // 2319
|
||
|
* ***** END LICENSE BLOCK ***** // 2320
|
||
|
*/ // 2321
|
||
|
// 2322
|
||
|
var _is_ie_htmlfile_capable; // 2323
|
||
|
var isIeHtmlfileCapable = function() { // 2324
|
||
|
if (_is_ie_htmlfile_capable === undefined) { // 2325
|
||
|
if ('ActiveXObject' in _window) { // 2326
|
||
|
try { // 2327
|
||
|
_is_ie_htmlfile_capable = !!new ActiveXObject('htmlfile'); // 2328
|
||
|
} catch (x) {} // 2329
|
||
|
} else { // 2330
|
||
|
_is_ie_htmlfile_capable = false; // 2331
|
||
|
} // 2332
|
||
|
} // 2333
|
||
|
return _is_ie_htmlfile_capable; // 2334
|
||
|
}; // 2335
|
||
|
// 2336
|
||
|
// 2337
|
||
|
var HtmlfileReceiver = function(url) { // 2338
|
||
|
var that = this; // 2339
|
||
|
utils.polluteGlobalNamespace(); // 2340
|
||
|
// 2341
|
||
|
that.id = 'a' + utils.random_string(6, 26); // 2342
|
||
|
url += ((url.indexOf('?') === -1) ? '?' : '&') + // 2343
|
||
|
'c=' + escape(WPrefix + '.' + that.id); // 2344
|
||
|
// 2345
|
||
|
var constructor = isIeHtmlfileCapable() ? // 2346
|
||
|
utils.createHtmlfile : utils.createIframe; // 2347
|
||
|
// 2348
|
||
|
var iframeObj; // 2349
|
||
|
_window[WPrefix][that.id] = { // 2350
|
||
|
start: function () { // 2351
|
||
|
iframeObj.loaded(); // 2352
|
||
|
}, // 2353
|
||
|
message: function (data) { // 2354
|
||
|
that.dispatchEvent(new SimpleEvent('message', {'data': data})); // 2355
|
||
|
}, // 2356
|
||
|
stop: function () { // 2357
|
||
|
that.iframe_close({}, 'network'); // 2358
|
||
|
} // 2359
|
||
|
}; // 2360
|
||
|
that.iframe_close = function(e, abort_reason) { // 2361
|
||
|
iframeObj.cleanup(); // 2362
|
||
|
that.iframe_close = iframeObj = null; // 2363
|
||
|
delete _window[WPrefix][that.id]; // 2364
|
||
|
that.dispatchEvent(new SimpleEvent('close', {reason: abort_reason})); // 2365
|
||
|
}; // 2366
|
||
|
iframeObj = constructor(url, function(e) { // 2367
|
||
|
that.iframe_close({}, 'permanent'); // 2368
|
||
|
}); // 2369
|
||
|
}; // 2370
|
||
|
// 2371
|
||
|
HtmlfileReceiver.prototype = new REventTarget(); // 2372
|
||
|
// 2373
|
||
|
HtmlfileReceiver.prototype.abort = function() { // 2374
|
||
|
var that = this; // 2375
|
||
|
if (that.iframe_close) { // 2376
|
||
|
that.iframe_close({}, 'user'); // 2377
|
||
|
} // 2378
|
||
|
}; // 2379
|
||
|
// [*] End of lib/trans-receiver-htmlfile.js // 2380
|
||
|
// 2381
|
||
|
// 2382
|
||
|
// [*] Including lib/trans-receiver-xhr.js // 2383
|
||
|
/* // 2384
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2385
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2386
|
||
|
* // 2387
|
||
|
* For the license see COPYING. // 2388
|
||
|
* ***** END LICENSE BLOCK ***** // 2389
|
||
|
*/ // 2390
|
||
|
// 2391
|
||
|
var XhrReceiver = function(url, AjaxObject) { // 2392
|
||
|
var that = this; // 2393
|
||
|
var buf_pos = 0; // 2394
|
||
|
// 2395
|
||
|
that.xo = new AjaxObject('POST', url, null); // 2396
|
||
|
that.xo.onchunk = function(status, text) { // 2397
|
||
|
if (status !== 200) return; // 2398
|
||
|
while (1) { // 2399
|
||
|
var buf = text.slice(buf_pos); // 2400
|
||
|
var p = buf.indexOf('\n'); // 2401
|
||
|
if (p === -1) break; // 2402
|
||
|
buf_pos += p+1; // 2403
|
||
|
var msg = buf.slice(0, p); // 2404
|
||
|
that.dispatchEvent(new SimpleEvent('message', {data: msg})); // 2405
|
||
|
} // 2406
|
||
|
}; // 2407
|
||
|
that.xo.onfinish = function(status, text) { // 2408
|
||
|
that.xo.onchunk(status, text); // 2409
|
||
|
that.xo = null; // 2410
|
||
|
var reason = status === 200 ? 'network' : 'permanent'; // 2411
|
||
|
that.dispatchEvent(new SimpleEvent('close', {reason: reason})); // 2412
|
||
|
} // 2413
|
||
|
}; // 2414
|
||
|
// 2415
|
||
|
XhrReceiver.prototype = new REventTarget(); // 2416
|
||
|
// 2417
|
||
|
XhrReceiver.prototype.abort = function() { // 2418
|
||
|
var that = this; // 2419
|
||
|
if (that.xo) { // 2420
|
||
|
that.xo.close(); // 2421
|
||
|
that.dispatchEvent(new SimpleEvent('close', {reason: 'user'})); // 2422
|
||
|
that.xo = null; // 2423
|
||
|
} // 2424
|
||
|
}; // 2425
|
||
|
// [*] End of lib/trans-receiver-xhr.js // 2426
|
||
|
// 2427
|
||
|
// 2428
|
||
|
// [*] Including lib/test-hooks.js // 2429
|
||
|
/* // 2430
|
||
|
* ***** BEGIN LICENSE BLOCK ***** // 2431
|
||
|
* Copyright (c) 2011-2012 VMware, Inc. // 2432
|
||
|
* // 2433
|
||
|
* For the license see COPYING. // 2434
|
||
|
* ***** END LICENSE BLOCK ***** // 2435
|
||
|
*/ // 2436
|
||
|
// 2437
|
||
|
// For testing // 2438
|
||
|
SockJS.getUtils = function(){ // 2439
|
||
|
return utils; // 2440
|
||
|
}; // 2441
|
||
|
// 2442
|
||
|
SockJS.getIframeTransport = function(){ // 2443
|
||
|
return IframeTransport; // 2444
|
||
|
}; // 2445
|
||
|
// [*] End of lib/test-hooks.js // 2446
|
||
|
// 2447
|
||
|
return SockJS; // 2448
|
||
|
})(); // 2449
|
||
|
if ('_sockjs_onload' in window) setTimeout(_sockjs_onload, 1); // 2450
|
||
|
// 2451
|
||
|
// AMD compliance // 2452
|
||
|
if (typeof define === 'function' && define.amd) { // 2453
|
||
|
define('sockjs', [], function(){return SockJS;}); // 2454
|
||
|
} // 2455
|
||
|
// [*] End of lib/index.js // 2456
|
||
|
// 2457
|
||
|
// [*] End of lib/all.js // 2458
|
||
|
// 2459
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/stream_client_sockjs.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// @param url {String} URL to Meteor app // 1
|
||
|
// "http://subdomain.meteor.com/" or "/" or // 2
|
||
|
// "ddp+sockjs://foo-**.meteor.com/sockjs" // 3
|
||
|
LivedataTest.ClientStream = function (url, options) { // 4
|
||
|
var self = this; // 5
|
||
|
self.options = _.extend({ // 6
|
||
|
retry: true // 7
|
||
|
}, options); // 8
|
||
|
self._initCommon(self.options); // 9
|
||
|
// 10
|
||
|
//// Constants // 11
|
||
|
// 12
|
||
|
// 13
|
||
|
// how long between hearing heartbeat from the server until we declare // 14
|
||
|
// the connection dead. heartbeats come every 45s (stream_server.js) // 15
|
||
|
// // 16
|
||
|
// NOTE: this is a older timeout mechanism. We now send heartbeats at // 17
|
||
|
// the DDP level (https://github.com/meteor/meteor/pull/1865), and // 18
|
||
|
// expect those timeouts to kill a non-responsive connection before // 19
|
||
|
// this timeout fires. This is kept around for compatibility (when // 20
|
||
|
// talking to a server that doesn't support DDP heartbeats) and can be // 21
|
||
|
// removed later. // 22
|
||
|
self.HEARTBEAT_TIMEOUT = 100*1000; // 23
|
||
|
// 24
|
||
|
self.rawUrl = url; // 25
|
||
|
self.socket = null; // 26
|
||
|
// 27
|
||
|
self.heartbeatTimer = null; // 28
|
||
|
// 29
|
||
|
// Listen to global 'online' event if we are running in a browser. // 30
|
||
|
// (IE8 does not support addEventListener) // 31
|
||
|
if (typeof window !== 'undefined' && window.addEventListener) // 32
|
||
|
window.addEventListener("online", _.bind(self._online, self), // 33
|
||
|
false /* useCapture. make FF3.6 happy. */); // 34
|
||
|
// 35
|
||
|
//// Kickoff! // 36
|
||
|
self._launchConnection(); // 37
|
||
|
}; // 38
|
||
|
// 39
|
||
|
_.extend(LivedataTest.ClientStream.prototype, { // 40
|
||
|
// 41
|
||
|
// data is a utf8 string. Data sent while not connected is dropped on // 42
|
||
|
// the floor, and it is up the user of this API to retransmit lost // 43
|
||
|
// messages on 'reset' // 44
|
||
|
send: function (data) { // 45
|
||
|
var self = this; // 46
|
||
|
if (self.currentStatus.connected) { // 47
|
||
|
self.socket.send(data); // 48
|
||
|
} // 49
|
||
|
}, // 50
|
||
|
// 51
|
||
|
// Changes where this connection points // 52
|
||
|
_changeUrl: function (url) { // 53
|
||
|
var self = this; // 54
|
||
|
self.rawUrl = url; // 55
|
||
|
}, // 56
|
||
|
// 57
|
||
|
_connected: function () { // 58
|
||
|
var self = this; // 59
|
||
|
// 60
|
||
|
if (self.connectionTimer) { // 61
|
||
|
clearTimeout(self.connectionTimer); // 62
|
||
|
self.connectionTimer = null; // 63
|
||
|
} // 64
|
||
|
// 65
|
||
|
if (self.currentStatus.connected) { // 66
|
||
|
// already connected. do nothing. this probably shouldn't happen. // 67
|
||
|
return; // 68
|
||
|
} // 69
|
||
|
// 70
|
||
|
// update status // 71
|
||
|
self.currentStatus.status = "connected"; // 72
|
||
|
self.currentStatus.connected = true; // 73
|
||
|
self.currentStatus.retryCount = 0; // 74
|
||
|
self.statusChanged(); // 75
|
||
|
// 76
|
||
|
// fire resets. This must come after status change so that clients // 77
|
||
|
// can call send from within a reset callback. // 78
|
||
|
_.each(self.eventCallbacks.reset, function (callback) { callback(); }); // 79
|
||
|
// 80
|
||
|
}, // 81
|
||
|
// 82
|
||
|
_cleanup: function (maybeError) { // 83
|
||
|
var self = this; // 84
|
||
|
// 85
|
||
|
self._clearConnectionAndHeartbeatTimers(); // 86
|
||
|
if (self.socket) { // 87
|
||
|
self.socket.onmessage = self.socket.onclose // 88
|
||
|
= self.socket.onerror = self.socket.onheartbeat = function () {}; // 89
|
||
|
self.socket.close(); // 90
|
||
|
self.socket = null; // 91
|
||
|
} // 92
|
||
|
// 93
|
||
|
_.each(self.eventCallbacks.disconnect, function (callback) { // 94
|
||
|
callback(maybeError); // 95
|
||
|
}); // 96
|
||
|
}, // 97
|
||
|
// 98
|
||
|
_clearConnectionAndHeartbeatTimers: function () { // 99
|
||
|
var self = this; // 100
|
||
|
if (self.connectionTimer) { // 101
|
||
|
clearTimeout(self.connectionTimer); // 102
|
||
|
self.connectionTimer = null; // 103
|
||
|
} // 104
|
||
|
if (self.heartbeatTimer) { // 105
|
||
|
clearTimeout(self.heartbeatTimer); // 106
|
||
|
self.heartbeatTimer = null; // 107
|
||
|
} // 108
|
||
|
}, // 109
|
||
|
// 110
|
||
|
_heartbeat_timeout: function () { // 111
|
||
|
var self = this; // 112
|
||
|
Meteor._debug("Connection timeout. No sockjs heartbeat received."); // 113
|
||
|
self._lostConnection(new DDP.ConnectionError("Heartbeat timed out")); // 114
|
||
|
}, // 115
|
||
|
// 116
|
||
|
_heartbeat_received: function () { // 117
|
||
|
var self = this; // 118
|
||
|
// If we've already permanently shut down this stream, the timeout is // 119
|
||
|
// already cleared, and we don't need to set it again. // 120
|
||
|
if (self._forcedToDisconnect) // 121
|
||
|
return; // 122
|
||
|
if (self.heartbeatTimer) // 123
|
||
|
clearTimeout(self.heartbeatTimer); // 124
|
||
|
self.heartbeatTimer = setTimeout( // 125
|
||
|
_.bind(self._heartbeat_timeout, self), // 126
|
||
|
self.HEARTBEAT_TIMEOUT); // 127
|
||
|
}, // 128
|
||
|
// 129
|
||
|
_sockjsProtocolsWhitelist: function () { // 130
|
||
|
// only allow polling protocols. no streaming. streaming // 131
|
||
|
// makes safari spin. // 132
|
||
|
var protocolsWhitelist = [ // 133
|
||
|
'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling']; // 134
|
||
|
// 135
|
||
|
// iOS 4 and 5 and below crash when using websockets over certain // 136
|
||
|
// proxies. this seems to be resolved with iOS 6. eg // 137
|
||
|
// https://github.com/LearnBoost/socket.io/issues/193#issuecomment-7308865. // 138
|
||
|
// // 139
|
||
|
// iOS <4 doesn't support websockets at all so sockjs will just // 140
|
||
|
// immediately fall back to http // 141
|
||
|
var noWebsockets = navigator && // 142
|
||
|
/iPhone|iPad|iPod/.test(navigator.userAgent) && // 143
|
||
|
/OS 4_|OS 5_/.test(navigator.userAgent); // 144
|
||
|
// 145
|
||
|
if (!noWebsockets) // 146
|
||
|
protocolsWhitelist = ['websocket'].concat(protocolsWhitelist); // 147
|
||
|
// 148
|
||
|
return protocolsWhitelist; // 149
|
||
|
}, // 150
|
||
|
// 151
|
||
|
_launchConnection: function () { // 152
|
||
|
var self = this; // 153
|
||
|
self._cleanup(); // cleanup the old socket, if there was one. // 154
|
||
|
// 155
|
||
|
var options = _.extend({ // 156
|
||
|
protocols_whitelist:self._sockjsProtocolsWhitelist() // 157
|
||
|
}, self.options._sockjsOptions); // 158
|
||
|
// 159
|
||
|
// Convert raw URL to SockJS URL each time we open a connection, so that we // 160
|
||
|
// can connect to random hostnames and get around browser per-host // 161
|
||
|
// connection limits. // 162
|
||
|
self.socket = new SockJS(toSockjsUrl(self.rawUrl), undefined, options); // 163
|
||
|
self.socket.onopen = function (data) { // 164
|
||
|
self._connected(); // 165
|
||
|
}; // 166
|
||
|
self.socket.onmessage = function (data) { // 167
|
||
|
self._heartbeat_received(); // 168
|
||
|
// 169
|
||
|
if (self.currentStatus.connected) // 170
|
||
|
_.each(self.eventCallbacks.message, function (callback) { // 171
|
||
|
callback(data.data); // 172
|
||
|
}); // 173
|
||
|
}; // 174
|
||
|
self.socket.onclose = function () { // 175
|
||
|
self._lostConnection(); // 176
|
||
|
}; // 177
|
||
|
self.socket.onerror = function () { // 178
|
||
|
// XXX is this ever called? // 179
|
||
|
Meteor._debug("stream error", _.toArray(arguments), (new Date()).toDateString()); // 180
|
||
|
}; // 181
|
||
|
// 182
|
||
|
self.socket.onheartbeat = function () { // 183
|
||
|
self._heartbeat_received(); // 184
|
||
|
}; // 185
|
||
|
// 186
|
||
|
if (self.connectionTimer) // 187
|
||
|
clearTimeout(self.connectionTimer); // 188
|
||
|
self.connectionTimer = setTimeout(function () { // 189
|
||
|
self._lostConnection( // 190
|
||
|
new DDP.ConnectionError("DDP connection timed out")); // 191
|
||
|
}, self.CONNECT_TIMEOUT); // 192
|
||
|
} // 193
|
||
|
}); // 194
|
||
|
// 195
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/stream_client_common.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// XXX from Underscore.String (http://epeli.github.com/underscore.string/) // 1
|
||
|
var startsWith = function(str, starts) { // 2
|
||
|
return str.length >= starts.length && // 3
|
||
|
str.substring(0, starts.length) === starts; // 4
|
||
|
}; // 5
|
||
|
var endsWith = function(str, ends) { // 6
|
||
|
return str.length >= ends.length && // 7
|
||
|
str.substring(str.length - ends.length) === ends; // 8
|
||
|
}; // 9
|
||
|
// 10
|
||
|
// @param url {String} URL to Meteor app, eg: // 11
|
||
|
// "/" or "madewith.meteor.com" or "https://foo.meteor.com" // 12
|
||
|
// or "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" // 13
|
||
|
// @returns {String} URL to the endpoint with the specific scheme and subPath, e.g. // 14
|
||
|
// for scheme "http" and subPath "sockjs" // 15
|
||
|
// "http://subdomain.meteor.com/sockjs" or "/sockjs" // 16
|
||
|
// or "https://ddp--1234-foo.meteor.com/sockjs" // 17
|
||
|
var translateUrl = function(url, newSchemeBase, subPath) { // 18
|
||
|
if (! newSchemeBase) { // 19
|
||
|
newSchemeBase = "http"; // 20
|
||
|
} // 21
|
||
|
// 22
|
||
|
var ddpUrlMatch = url.match(/^ddp(i?)\+sockjs:\/\//); // 23
|
||
|
var httpUrlMatch = url.match(/^http(s?):\/\//); // 24
|
||
|
var newScheme; // 25
|
||
|
if (ddpUrlMatch) { // 26
|
||
|
// Remove scheme and split off the host. // 27
|
||
|
var urlAfterDDP = url.substr(ddpUrlMatch[0].length); // 28
|
||
|
newScheme = ddpUrlMatch[1] === "i" ? newSchemeBase : newSchemeBase + "s"; // 29
|
||
|
var slashPos = urlAfterDDP.indexOf('/'); // 30
|
||
|
var host = // 31
|
||
|
slashPos === -1 ? urlAfterDDP : urlAfterDDP.substr(0, slashPos); // 32
|
||
|
var rest = slashPos === -1 ? '' : urlAfterDDP.substr(slashPos); // 33
|
||
|
// 34
|
||
|
// In the host (ONLY!), change '*' characters into random digits. This // 35
|
||
|
// allows different stream connections to connect to different hostnames // 36
|
||
|
// and avoid browser per-hostname connection limits. // 37
|
||
|
host = host.replace(/\*/g, function () { // 38
|
||
|
return Math.floor(Random.fraction()*10); // 39
|
||
|
}); // 40
|
||
|
// 41
|
||
|
return newScheme + '://' + host + rest; // 42
|
||
|
} else if (httpUrlMatch) { // 43
|
||
|
newScheme = !httpUrlMatch[1] ? newSchemeBase : newSchemeBase + "s"; // 44
|
||
|
var urlAfterHttp = url.substr(httpUrlMatch[0].length); // 45
|
||
|
url = newScheme + "://" + urlAfterHttp; // 46
|
||
|
} // 47
|
||
|
// 48
|
||
|
// Prefix FQDNs but not relative URLs // 49
|
||
|
if (url.indexOf("://") === -1 && !startsWith(url, "/")) { // 50
|
||
|
url = newSchemeBase + "://" + url; // 51
|
||
|
} // 52
|
||
|
// 53
|
||
|
// XXX This is not what we should be doing: if I have a site // 54
|
||
|
// deployed at "/foo", then DDP.connect("/") should actually connect // 55
|
||
|
// to "/", not to "/foo". "/" is an absolute path. (Contrast: if // 56
|
||
|
// deployed at "/foo", it would be reasonable for DDP.connect("bar") // 57
|
||
|
// to connect to "/foo/bar"). // 58
|
||
|
// // 59
|
||
|
// We should make this properly honor absolute paths rather than // 60
|
||
|
// forcing the path to be relative to the site root. Simultaneously, // 61
|
||
|
// we should set DDP_DEFAULT_CONNECTION_URL to include the site // 62
|
||
|
// root. See also client_convenience.js #RationalizingRelativeDDPURLs // 63
|
||
|
url = Meteor._relativeToSiteRootUrl(url); // 64
|
||
|
// 65
|
||
|
if (endsWith(url, "/")) // 66
|
||
|
return url + subPath; // 67
|
||
|
else // 68
|
||
|
return url + "/" + subPath; // 69
|
||
|
}; // 70
|
||
|
// 71
|
||
|
toSockjsUrl = function (url) { // 72
|
||
|
return translateUrl(url, "http", "sockjs"); // 73
|
||
|
}; // 74
|
||
|
// 75
|
||
|
toWebsocketUrl = function (url) { // 76
|
||
|
var ret = translateUrl(url, "ws", "websocket"); // 77
|
||
|
return ret; // 78
|
||
|
}; // 79
|
||
|
// 80
|
||
|
LivedataTest.toSockjsUrl = toSockjsUrl; // 81
|
||
|
// 82
|
||
|
// 83
|
||
|
_.extend(LivedataTest.ClientStream.prototype, { // 84
|
||
|
// 85
|
||
|
// Register for callbacks. // 86
|
||
|
on: function (name, callback) { // 87
|
||
|
var self = this; // 88
|
||
|
// 89
|
||
|
if (name !== 'message' && name !== 'reset' && name !== 'disconnect') // 90
|
||
|
throw new Error("unknown event type: " + name); // 91
|
||
|
// 92
|
||
|
if (!self.eventCallbacks[name]) // 93
|
||
|
self.eventCallbacks[name] = []; // 94
|
||
|
self.eventCallbacks[name].push(callback); // 95
|
||
|
}, // 96
|
||
|
// 97
|
||
|
// 98
|
||
|
_initCommon: function (options) { // 99
|
||
|
var self = this; // 100
|
||
|
options = options || {}; // 101
|
||
|
// 102
|
||
|
//// Constants // 103
|
||
|
// 104
|
||
|
// how long to wait until we declare the connection attempt // 105
|
||
|
// failed. // 106
|
||
|
self.CONNECT_TIMEOUT = options.connectTimeoutMs || 10000; // 107
|
||
|
// 108
|
||
|
self.eventCallbacks = {}; // name -> [callback] // 109
|
||
|
// 110
|
||
|
self._forcedToDisconnect = false; // 111
|
||
|
// 112
|
||
|
//// Reactive status // 113
|
||
|
self.currentStatus = { // 114
|
||
|
status: "connecting", // 115
|
||
|
connected: false, // 116
|
||
|
retryCount: 0 // 117
|
||
|
}; // 118
|
||
|
// 119
|
||
|
// 120
|
||
|
self.statusListeners = typeof Tracker !== 'undefined' && new Tracker.Dependency; // 121
|
||
|
self.statusChanged = function () { // 122
|
||
|
if (self.statusListeners) // 123
|
||
|
self.statusListeners.changed(); // 124
|
||
|
}; // 125
|
||
|
// 126
|
||
|
//// Retry logic // 127
|
||
|
self._retry = new Retry; // 128
|
||
|
self.connectionTimer = null; // 129
|
||
|
// 130
|
||
|
}, // 131
|
||
|
// 132
|
||
|
// Trigger a reconnect. // 133
|
||
|
reconnect: function (options) { // 134
|
||
|
var self = this; // 135
|
||
|
options = options || {}; // 136
|
||
|
// 137
|
||
|
if (options.url) { // 138
|
||
|
self._changeUrl(options.url); // 139
|
||
|
} // 140
|
||
|
// 141
|
||
|
if (options._sockjsOptions) { // 142
|
||
|
self.options._sockjsOptions = options._sockjsOptions; // 143
|
||
|
} // 144
|
||
|
// 145
|
||
|
if (self.currentStatus.connected) { // 146
|
||
|
if (options._force || options.url) { // 147
|
||
|
// force reconnect. // 148
|
||
|
self._lostConnection(new DDP.ForcedReconnectError); // 149
|
||
|
} // else, noop. // 150
|
||
|
return; // 151
|
||
|
} // 152
|
||
|
// 153
|
||
|
// if we're mid-connection, stop it. // 154
|
||
|
if (self.currentStatus.status === "connecting") { // 155
|
||
|
// Pretend it's a clean close. // 156
|
||
|
self._lostConnection(); // 157
|
||
|
} // 158
|
||
|
// 159
|
||
|
self._retry.clear(); // 160
|
||
|
self.currentStatus.retryCount -= 1; // don't count manual retries // 161
|
||
|
self._retryNow(); // 162
|
||
|
}, // 163
|
||
|
// 164
|
||
|
disconnect: function (options) { // 165
|
||
|
var self = this; // 166
|
||
|
options = options || {}; // 167
|
||
|
// 168
|
||
|
// Failed is permanent. If we're failed, don't let people go back // 169
|
||
|
// online by calling 'disconnect' then 'reconnect'. // 170
|
||
|
if (self._forcedToDisconnect) // 171
|
||
|
return; // 172
|
||
|
// 173
|
||
|
// If _permanent is set, permanently disconnect a stream. Once a stream // 174
|
||
|
// is forced to disconnect, it can never reconnect. This is for // 175
|
||
|
// error cases such as ddp version mismatch, where trying again // 176
|
||
|
// won't fix the problem. // 177
|
||
|
if (options._permanent) { // 178
|
||
|
self._forcedToDisconnect = true; // 179
|
||
|
} // 180
|
||
|
// 181
|
||
|
self._cleanup(); // 182
|
||
|
self._retry.clear(); // 183
|
||
|
// 184
|
||
|
self.currentStatus = { // 185
|
||
|
status: (options._permanent ? "failed" : "offline"), // 186
|
||
|
connected: false, // 187
|
||
|
retryCount: 0 // 188
|
||
|
}; // 189
|
||
|
// 190
|
||
|
if (options._permanent && options._error) // 191
|
||
|
self.currentStatus.reason = options._error; // 192
|
||
|
// 193
|
||
|
self.statusChanged(); // 194
|
||
|
}, // 195
|
||
|
// 196
|
||
|
// maybeError is set unless it's a clean protocol-level close. // 197
|
||
|
_lostConnection: function (maybeError) { // 198
|
||
|
var self = this; // 199
|
||
|
// 200
|
||
|
self._cleanup(maybeError); // 201
|
||
|
self._retryLater(maybeError); // sets status. no need to do it here. // 202
|
||
|
}, // 203
|
||
|
// 204
|
||
|
// fired when we detect that we've gone online. try to reconnect // 205
|
||
|
// immediately. // 206
|
||
|
_online: function () { // 207
|
||
|
// if we've requested to be offline by disconnecting, don't reconnect. // 208
|
||
|
if (this.currentStatus.status != "offline") // 209
|
||
|
this.reconnect(); // 210
|
||
|
}, // 211
|
||
|
// 212
|
||
|
_retryLater: function (maybeError) { // 213
|
||
|
var self = this; // 214
|
||
|
// 215
|
||
|
var timeout = 0; // 216
|
||
|
if (self.options.retry || // 217
|
||
|
(maybeError && maybeError.errorType === "DDP.ForcedReconnectError")) { // 218
|
||
|
timeout = self._retry.retryLater( // 219
|
||
|
self.currentStatus.retryCount, // 220
|
||
|
_.bind(self._retryNow, self) // 221
|
||
|
); // 222
|
||
|
self.currentStatus.status = "waiting"; // 223
|
||
|
self.currentStatus.retryTime = (new Date()).getTime() + timeout; // 224
|
||
|
} else { // 225
|
||
|
self.currentStatus.status = "failed"; // 226
|
||
|
delete self.currentStatus.retryTime; // 227
|
||
|
} // 228
|
||
|
// 229
|
||
|
self.currentStatus.connected = false; // 230
|
||
|
self.statusChanged(); // 231
|
||
|
}, // 232
|
||
|
// 233
|
||
|
_retryNow: function () { // 234
|
||
|
var self = this; // 235
|
||
|
// 236
|
||
|
if (self._forcedToDisconnect) // 237
|
||
|
return; // 238
|
||
|
// 239
|
||
|
self.currentStatus.retryCount += 1; // 240
|
||
|
self.currentStatus.status = "connecting"; // 241
|
||
|
self.currentStatus.connected = false; // 242
|
||
|
delete self.currentStatus.retryTime; // 243
|
||
|
self.statusChanged(); // 244
|
||
|
// 245
|
||
|
self._launchConnection(); // 246
|
||
|
}, // 247
|
||
|
// 248
|
||
|
// 249
|
||
|
// Get current status. Reactive. // 250
|
||
|
status: function () { // 251
|
||
|
var self = this; // 252
|
||
|
if (self.statusListeners) // 253
|
||
|
self.statusListeners.depend(); // 254
|
||
|
return self.currentStatus; // 255
|
||
|
} // 256
|
||
|
}); // 257
|
||
|
// 258
|
||
|
DDP.ConnectionError = Meteor.makeErrorType( // 259
|
||
|
"DDP.ConnectionError", function (message) { // 260
|
||
|
var self = this; // 261
|
||
|
self.message = message; // 262
|
||
|
}); // 263
|
||
|
// 264
|
||
|
DDP.ForcedReconnectError = Meteor.makeErrorType( // 265
|
||
|
"DDP.ForcedReconnectError", function () {}); // 266
|
||
|
// 267
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/heartbeat.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Heartbeat options: // 1
|
||
|
// heartbeatInterval: interval to send pings, in milliseconds. // 2
|
||
|
// heartbeatTimeout: timeout to close the connection if a reply isn't // 3
|
||
|
// received, in milliseconds. // 4
|
||
|
// sendPing: function to call to send a ping on the connection. // 5
|
||
|
// onTimeout: function to call to close the connection. // 6
|
||
|
// 7
|
||
|
Heartbeat = function (options) { // 8
|
||
|
var self = this; // 9
|
||
|
// 10
|
||
|
self.heartbeatInterval = options.heartbeatInterval; // 11
|
||
|
self.heartbeatTimeout = options.heartbeatTimeout; // 12
|
||
|
self._sendPing = options.sendPing; // 13
|
||
|
self._onTimeout = options.onTimeout; // 14
|
||
|
// 15
|
||
|
self._heartbeatIntervalHandle = null; // 16
|
||
|
self._heartbeatTimeoutHandle = null; // 17
|
||
|
}; // 18
|
||
|
// 19
|
||
|
_.extend(Heartbeat.prototype, { // 20
|
||
|
stop: function () { // 21
|
||
|
var self = this; // 22
|
||
|
self._clearHeartbeatIntervalTimer(); // 23
|
||
|
self._clearHeartbeatTimeoutTimer(); // 24
|
||
|
}, // 25
|
||
|
// 26
|
||
|
start: function () { // 27
|
||
|
var self = this; // 28
|
||
|
self.stop(); // 29
|
||
|
self._startHeartbeatIntervalTimer(); // 30
|
||
|
}, // 31
|
||
|
// 32
|
||
|
_startHeartbeatIntervalTimer: function () { // 33
|
||
|
var self = this; // 34
|
||
|
self._heartbeatIntervalHandle = Meteor.setTimeout( // 35
|
||
|
_.bind(self._heartbeatIntervalFired, self), // 36
|
||
|
self.heartbeatInterval // 37
|
||
|
); // 38
|
||
|
}, // 39
|
||
|
// 40
|
||
|
_startHeartbeatTimeoutTimer: function () { // 41
|
||
|
var self = this; // 42
|
||
|
self._heartbeatTimeoutHandle = Meteor.setTimeout( // 43
|
||
|
_.bind(self._heartbeatTimeoutFired, self), // 44
|
||
|
self.heartbeatTimeout // 45
|
||
|
); // 46
|
||
|
}, // 47
|
||
|
// 48
|
||
|
_clearHeartbeatIntervalTimer: function () { // 49
|
||
|
var self = this; // 50
|
||
|
if (self._heartbeatIntervalHandle) { // 51
|
||
|
Meteor.clearTimeout(self._heartbeatIntervalHandle); // 52
|
||
|
self._heartbeatIntervalHandle = null; // 53
|
||
|
} // 54
|
||
|
}, // 55
|
||
|
// 56
|
||
|
_clearHeartbeatTimeoutTimer: function () { // 57
|
||
|
var self = this; // 58
|
||
|
if (self._heartbeatTimeoutHandle) { // 59
|
||
|
Meteor.clearTimeout(self._heartbeatTimeoutHandle); // 60
|
||
|
self._heartbeatTimeoutHandle = null; // 61
|
||
|
} // 62
|
||
|
}, // 63
|
||
|
// 64
|
||
|
// The heartbeat interval timer is fired when we should send a ping. // 65
|
||
|
_heartbeatIntervalFired: function () { // 66
|
||
|
var self = this; // 67
|
||
|
self._heartbeatIntervalHandle = null; // 68
|
||
|
self._sendPing(); // 69
|
||
|
// Wait for a pong. // 70
|
||
|
self._startHeartbeatTimeoutTimer(); // 71
|
||
|
}, // 72
|
||
|
// 73
|
||
|
// The heartbeat timeout timer is fired when we sent a ping, but we // 74
|
||
|
// timed out waiting for the pong. // 75
|
||
|
_heartbeatTimeoutFired: function () { // 76
|
||
|
var self = this; // 77
|
||
|
self._heartbeatTimeoutHandle = null; // 78
|
||
|
self._onTimeout(); // 79
|
||
|
}, // 80
|
||
|
// 81
|
||
|
pingReceived: function () { // 82
|
||
|
var self = this; // 83
|
||
|
// We know the connection is alive if we receive a ping, so we // 84
|
||
|
// don't need to send a ping ourselves. Reset the interval timer. // 85
|
||
|
if (self._heartbeatIntervalHandle) { // 86
|
||
|
self._clearHeartbeatIntervalTimer(); // 87
|
||
|
self._startHeartbeatIntervalTimer(); // 88
|
||
|
} // 89
|
||
|
}, // 90
|
||
|
// 91
|
||
|
pongReceived: function () { // 92
|
||
|
var self = this; // 93
|
||
|
// 94
|
||
|
// Receiving a pong means we won't timeout, so clear the timeout // 95
|
||
|
// timer and start the interval again. // 96
|
||
|
if (self._heartbeatTimeoutHandle) { // 97
|
||
|
self._clearHeartbeatTimeoutTimer(); // 98
|
||
|
self._startHeartbeatIntervalTimer(); // 99
|
||
|
} // 100
|
||
|
} // 101
|
||
|
}); // 102
|
||
|
// 103
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/livedata_common.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// All the supported versions (for both the client and server) // 1
|
||
|
// These must be in order of preference; most favored-first // 2
|
||
|
SUPPORTED_DDP_VERSIONS = [ '1', 'pre2', 'pre1' ]; // 3
|
||
|
// 4
|
||
|
LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS; // 5
|
||
|
// 6
|
||
|
// Instance name is this because it is usually referred to as this inside a // 7
|
||
|
// method definition // 8
|
||
|
/** // 9
|
||
|
* @summary The state for a single invocation of a method, referenced by this // 10
|
||
|
* inside a method definition. // 11
|
||
|
* @param {Object} options // 12
|
||
|
* @instanceName this // 13
|
||
|
*/ // 14
|
||
|
MethodInvocation = function (options) { // 15
|
||
|
var self = this; // 16
|
||
|
// 17
|
||
|
// true if we're running not the actual method, but a stub (that is, // 18
|
||
|
// if we're on a client (which may be a browser, or in the future a // 19
|
||
|
// server connecting to another server) and presently running a // 20
|
||
|
// simulation of a server-side method for latency compensation // 21
|
||
|
// purposes). not currently true except in a client such as a browser, // 22
|
||
|
// since there's usually no point in running stubs unless you have a // 23
|
||
|
// zero-latency connection to the user. // 24
|
||
|
// 25
|
||
|
/** // 26
|
||
|
* @summary Access inside a method invocation. Boolean value, true if this invocation is a stub. // 27
|
||
|
* @locus Anywhere // 28
|
||
|
* @name isSimulation // 29
|
||
|
* @memberOf MethodInvocation // 30
|
||
|
* @instance // 31
|
||
|
* @type {Boolean} // 32
|
||
|
*/ // 33
|
||
|
this.isSimulation = options.isSimulation; // 34
|
||
|
// 35
|
||
|
// call this function to allow other method invocations (from the // 36
|
||
|
// same client) to continue running without waiting for this one to // 37
|
||
|
// complete. // 38
|
||
|
this._unblock = options.unblock || function () {}; // 39
|
||
|
this._calledUnblock = false; // 40
|
||
|
// 41
|
||
|
// current user id // 42
|
||
|
// 43
|
||
|
/** // 44
|
||
|
* @summary The id of the user that made this method call, or `null` if no user was logged in. // 45
|
||
|
* @locus Anywhere // 46
|
||
|
* @name userId // 47
|
||
|
* @memberOf MethodInvocation // 48
|
||
|
* @instance // 49
|
||
|
*/ // 50
|
||
|
this.userId = options.userId; // 51
|
||
|
// 52
|
||
|
// sets current user id in all appropriate server contexts and // 53
|
||
|
// reruns subscriptions // 54
|
||
|
this._setUserId = options.setUserId || function () {}; // 55
|
||
|
// 56
|
||
|
// On the server, the connection this method call came in on. // 57
|
||
|
// 58
|
||
|
/** // 59
|
||
|
* @summary Access inside a method invocation. The [connection](#meteor_onconnection) that this method was received on. `null` if the method is not associated with a connection, eg. a server initiated method call.
|
||
|
* @locus Server // 61
|
||
|
* @name connection // 62
|
||
|
* @memberOf MethodInvocation // 63
|
||
|
* @instance // 64
|
||
|
*/ // 65
|
||
|
this.connection = options.connection; // 66
|
||
|
// 67
|
||
|
// The seed for randomStream value generation // 68
|
||
|
this.randomSeed = options.randomSeed; // 69
|
||
|
// 70
|
||
|
// This is set by RandomStream.get; and holds the random stream state // 71
|
||
|
this.randomStream = null; // 72
|
||
|
}; // 73
|
||
|
// 74
|
||
|
_.extend(MethodInvocation.prototype, { // 75
|
||
|
/** // 76
|
||
|
* @summary Call inside a method invocation. Allow subsequent method from this client to begin running in a new fiber.
|
||
|
* @locus Server // 78
|
||
|
* @memberOf MethodInvocation // 79
|
||
|
* @instance // 80
|
||
|
*/ // 81
|
||
|
unblock: function () { // 82
|
||
|
var self = this; // 83
|
||
|
self._calledUnblock = true; // 84
|
||
|
self._unblock(); // 85
|
||
|
}, // 86
|
||
|
// 87
|
||
|
/** // 88
|
||
|
* @summary Set the logged in user. // 89
|
||
|
* @locus Server // 90
|
||
|
* @memberOf MethodInvocation // 91
|
||
|
* @instance // 92
|
||
|
* @param {String | null} userId The value that should be returned by `userId` on this connection. // 93
|
||
|
*/ // 94
|
||
|
setUserId: function(userId) { // 95
|
||
|
var self = this; // 96
|
||
|
if (self._calledUnblock) // 97
|
||
|
throw new Error("Can't call setUserId in a method after calling unblock"); // 98
|
||
|
self.userId = userId; // 99
|
||
|
self._setUserId(userId); // 100
|
||
|
} // 101
|
||
|
}); // 102
|
||
|
// 103
|
||
|
parseDDP = function (stringMessage) { // 104
|
||
|
try { // 105
|
||
|
var msg = JSON.parse(stringMessage); // 106
|
||
|
} catch (e) { // 107
|
||
|
Meteor._debug("Discarding message with invalid JSON", stringMessage); // 108
|
||
|
return null; // 109
|
||
|
} // 110
|
||
|
// DDP messages must be objects. // 111
|
||
|
if (msg === null || typeof msg !== 'object') { // 112
|
||
|
Meteor._debug("Discarding non-object DDP message", stringMessage); // 113
|
||
|
return null; // 114
|
||
|
} // 115
|
||
|
// 116
|
||
|
// massage msg to get it into "abstract ddp" rather than "wire ddp" format. // 117
|
||
|
// 118
|
||
|
// switch between "cleared" rep of unsetting fields and "undefined" // 119
|
||
|
// rep of same // 120
|
||
|
if (_.has(msg, 'cleared')) { // 121
|
||
|
if (!_.has(msg, 'fields')) // 122
|
||
|
msg.fields = {}; // 123
|
||
|
_.each(msg.cleared, function (clearKey) { // 124
|
||
|
msg.fields[clearKey] = undefined; // 125
|
||
|
}); // 126
|
||
|
delete msg.cleared; // 127
|
||
|
} // 128
|
||
|
// 129
|
||
|
_.each(['fields', 'params', 'result'], function (field) { // 130
|
||
|
if (_.has(msg, field)) // 131
|
||
|
msg[field] = EJSON._adjustTypesFromJSONValue(msg[field]); // 132
|
||
|
}); // 133
|
||
|
// 134
|
||
|
return msg; // 135
|
||
|
}; // 136
|
||
|
// 137
|
||
|
stringifyDDP = function (msg) { // 138
|
||
|
var copy = EJSON.clone(msg); // 139
|
||
|
// swizzle 'changed' messages from 'fields undefined' rep to 'fields // 140
|
||
|
// and cleared' rep // 141
|
||
|
if (_.has(msg, 'fields')) { // 142
|
||
|
var cleared = []; // 143
|
||
|
_.each(msg.fields, function (value, key) { // 144
|
||
|
if (value === undefined) { // 145
|
||
|
cleared.push(key); // 146
|
||
|
delete copy.fields[key]; // 147
|
||
|
} // 148
|
||
|
}); // 149
|
||
|
if (!_.isEmpty(cleared)) // 150
|
||
|
copy.cleared = cleared; // 151
|
||
|
if (_.isEmpty(copy.fields)) // 152
|
||
|
delete copy.fields; // 153
|
||
|
} // 154
|
||
|
// adjust types to basic // 155
|
||
|
_.each(['fields', 'params', 'result'], function (field) { // 156
|
||
|
if (_.has(copy, field)) // 157
|
||
|
copy[field] = EJSON._adjustTypesToJSONValue(copy[field]); // 158
|
||
|
}); // 159
|
||
|
if (msg.id && typeof msg.id !== 'string') { // 160
|
||
|
throw new Error("Message id is not a string"); // 161
|
||
|
} // 162
|
||
|
return JSON.stringify(copy); // 163
|
||
|
}; // 164
|
||
|
// 165
|
||
|
// This is private but it's used in a few places. accounts-base uses // 166
|
||
|
// it to get the current user. accounts-password uses it to stash SRP // 167
|
||
|
// state in the DDP session. Meteor.setTimeout and friends clear // 168
|
||
|
// it. We can probably find a better way to factor this. // 169
|
||
|
DDP._CurrentInvocation = new Meteor.EnvironmentVariable; // 170
|
||
|
// 171
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/random_stream.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// RandomStream allows for generation of pseudo-random values, from a seed. // 1
|
||
|
// // 2
|
||
|
// We use this for consistent 'random' numbers across the client and server. // 3
|
||
|
// We want to generate probably-unique IDs on the client, and we ideally want // 4
|
||
|
// the server to generate the same IDs when it executes the method. // 5
|
||
|
// // 6
|
||
|
// For generated values to be the same, we must seed ourselves the same way, // 7
|
||
|
// and we must keep track of the current state of our pseudo-random generators. // 8
|
||
|
// We call this state the scope. By default, we use the current DDP method // 9
|
||
|
// invocation as our scope. DDP now allows the client to specify a randomSeed. // 10
|
||
|
// If a randomSeed is provided it will be used to seed our random sequences. // 11
|
||
|
// In this way, client and server method calls will generate the same values. // 12
|
||
|
// // 13
|
||
|
// We expose multiple named streams; each stream is independent // 14
|
||
|
// and is seeded differently (but predictably from the name). // 15
|
||
|
// By using multiple streams, we support reordering of requests, // 16
|
||
|
// as long as they occur on different streams. // 17
|
||
|
// // 18
|
||
|
// @param options {Optional Object} // 19
|
||
|
// seed: Array or value - Seed value(s) for the generator. // 20
|
||
|
// If an array, will be used as-is // 21
|
||
|
// If a value, will be converted to a single-value array // 22
|
||
|
// If omitted, a random array will be used as the seed. // 23
|
||
|
RandomStream = function (options) { // 24
|
||
|
var self = this; // 25
|
||
|
// 26
|
||
|
this.seed = [].concat(options.seed || randomToken()); // 27
|
||
|
// 28
|
||
|
this.sequences = {}; // 29
|
||
|
}; // 30
|
||
|
// 31
|
||
|
// Returns a random string of sufficient length for a random seed. // 32
|
||
|
// This is a placeholder function; a similar function is planned // 33
|
||
|
// for Random itself; when that is added we should remove this function, // 34
|
||
|
// and call Random's randomToken instead. // 35
|
||
|
function randomToken() { // 36
|
||
|
return Random.hexString(20); // 37
|
||
|
}; // 38
|
||
|
// 39
|
||
|
// Returns the random stream with the specified name, in the specified scope. // 40
|
||
|
// If scope is null (or otherwise falsey) then we will use Random, which will // 41
|
||
|
// give us as random numbers as possible, but won't produce the same // 42
|
||
|
// values across client and server. // 43
|
||
|
// However, scope will normally be the current DDP method invocation, so // 44
|
||
|
// we'll use the stream with the specified name, and we should get consistent // 45
|
||
|
// values on the client and server sides of a method call. // 46
|
||
|
RandomStream.get = function (scope, name) { // 47
|
||
|
if (!name) { // 48
|
||
|
name = "default"; // 49
|
||
|
} // 50
|
||
|
if (!scope) { // 51
|
||
|
// There was no scope passed in; // 52
|
||
|
// the sequence won't actually be reproducible. // 53
|
||
|
return Random; // 54
|
||
|
} // 55
|
||
|
var randomStream = scope.randomStream; // 56
|
||
|
if (!randomStream) { // 57
|
||
|
scope.randomStream = randomStream = new RandomStream({ // 58
|
||
|
seed: scope.randomSeed // 59
|
||
|
}); // 60
|
||
|
} // 61
|
||
|
return randomStream._sequence(name); // 62
|
||
|
}; // 63
|
||
|
// 64
|
||
|
// Returns the named sequence of pseudo-random values. // 65
|
||
|
// The scope will be DDP._CurrentInvocation.get(), so the stream will produce // 66
|
||
|
// consistent values for method calls on the client and server. // 67
|
||
|
DDP.randomStream = function (name) { // 68
|
||
|
var scope = DDP._CurrentInvocation.get(); // 69
|
||
|
return RandomStream.get(scope, name); // 70
|
||
|
}; // 71
|
||
|
// 72
|
||
|
// Creates a randomSeed for passing to a method call. // 73
|
||
|
// Note that we take enclosing as an argument, // 74
|
||
|
// though we expect it to be DDP._CurrentInvocation.get() // 75
|
||
|
// However, we often evaluate makeRpcSeed lazily, and thus the relevant // 76
|
||
|
// invocation may not be the one currently in scope. // 77
|
||
|
// If enclosing is null, we'll use Random and values won't be repeatable. // 78
|
||
|
makeRpcSeed = function (enclosing, methodName) { // 79
|
||
|
var stream = RandomStream.get(enclosing, '/rpc/' + methodName); // 80
|
||
|
return stream.hexString(20); // 81
|
||
|
}; // 82
|
||
|
// 83
|
||
|
_.extend(RandomStream.prototype, { // 84
|
||
|
// Get a random sequence with the specified name, creating it if does not exist. // 85
|
||
|
// New sequences are seeded with the seed concatenated with the name. // 86
|
||
|
// By passing a seed into Random.create, we use the Alea generator. // 87
|
||
|
_sequence: function (name) { // 88
|
||
|
var self = this; // 89
|
||
|
// 90
|
||
|
var sequence = self.sequences[name] || null; // 91
|
||
|
if (sequence === null) { // 92
|
||
|
var sequenceSeed = self.seed.concat(name); // 93
|
||
|
for (var i = 0; i < sequenceSeed.length; i++) { // 94
|
||
|
if (_.isFunction(sequenceSeed[i])) { // 95
|
||
|
sequenceSeed[i] = sequenceSeed[i](); // 96
|
||
|
} // 97
|
||
|
} // 98
|
||
|
self.sequences[name] = sequence = Random.createWithSeeds.apply(null, sequenceSeed); // 99
|
||
|
} // 100
|
||
|
return sequence; // 101
|
||
|
} // 102
|
||
|
}); // 103
|
||
|
// 104
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/livedata_connection.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
if (Meteor.isServer) { // 1
|
||
|
var path = Npm.require('path'); // 2
|
||
|
var Fiber = Npm.require('fibers'); // 3
|
||
|
var Future = Npm.require(path.join('fibers', 'future')); // 4
|
||
|
} // 5
|
||
|
// 6
|
||
|
// @param url {String|Object} URL to Meteor app, // 7
|
||
|
// or an object as a test hook (see code) // 8
|
||
|
// Options: // 9
|
||
|
// reloadWithOutstanding: is it OK to reload if there are outstanding methods? // 10
|
||
|
// headers: extra headers to send on the websockets connection, for // 11
|
||
|
// server-to-server DDP only // 12
|
||
|
// _sockjsOptions: Specifies options to pass through to the sockjs client // 13
|
||
|
// onDDPNegotiationVersionFailure: callback when version negotiation fails. // 14
|
||
|
// // 15
|
||
|
// XXX There should be a way to destroy a DDP connection, causing all // 16
|
||
|
// outstanding method calls to fail. // 17
|
||
|
// // 18
|
||
|
// XXX Our current way of handling failure and reconnection is great // 19
|
||
|
// for an app (where we want to tolerate being disconnected as an // 20
|
||
|
// expect state, and keep trying forever to reconnect) but cumbersome // 21
|
||
|
// for something like a command line tool that wants to make a // 22
|
||
|
// connection, call a method, and print an error if connection // 23
|
||
|
// fails. We should have better usability in the latter case (while // 24
|
||
|
// still transparently reconnecting if it's just a transient failure // 25
|
||
|
// or the server migrating us). // 26
|
||
|
var Connection = function (url, options) { // 27
|
||
|
var self = this; // 28
|
||
|
options = _.extend({ // 29
|
||
|
onConnected: function () {}, // 30
|
||
|
onDDPVersionNegotiationFailure: function (description) { // 31
|
||
|
Meteor._debug(description); // 32
|
||
|
}, // 33
|
||
|
heartbeatInterval: 35000, // 34
|
||
|
heartbeatTimeout: 15000, // 35
|
||
|
// These options are only for testing. // 36
|
||
|
reloadWithOutstanding: false, // 37
|
||
|
supportedDDPVersions: SUPPORTED_DDP_VERSIONS, // 38
|
||
|
retry: true, // 39
|
||
|
respondToPings: true // 40
|
||
|
}, options); // 41
|
||
|
// 42
|
||
|
// If set, called when we reconnect, queuing method calls _before_ the // 43
|
||
|
// existing outstanding ones. This is the only data member that is part of the // 44
|
||
|
// public API! // 45
|
||
|
self.onReconnect = null; // 46
|
||
|
// 47
|
||
|
// as a test hook, allow passing a stream instead of a url. // 48
|
||
|
if (typeof url === "object") { // 49
|
||
|
self._stream = url; // 50
|
||
|
} else { // 51
|
||
|
self._stream = new LivedataTest.ClientStream(url, { // 52
|
||
|
retry: options.retry, // 53
|
||
|
headers: options.headers, // 54
|
||
|
_sockjsOptions: options._sockjsOptions, // 55
|
||
|
// Used to keep some tests quiet, or for other cases in which // 56
|
||
|
// the right thing to do with connection errors is to silently // 57
|
||
|
// fail (e.g. sending package usage stats). At some point we // 58
|
||
|
// should have a real API for handling client-stream-level // 59
|
||
|
// errors. // 60
|
||
|
_dontPrintErrors: options._dontPrintErrors, // 61
|
||
|
connectTimeoutMs: options.connectTimeoutMs // 62
|
||
|
}); // 63
|
||
|
} // 64
|
||
|
// 65
|
||
|
self._lastSessionId = null; // 66
|
||
|
self._versionSuggestion = null; // The last proposed DDP version. // 67
|
||
|
self._version = null; // The DDP version agreed on by client and server. // 68
|
||
|
self._stores = {}; // name -> object with methods // 69
|
||
|
self._methodHandlers = {}; // name -> func // 70
|
||
|
self._nextMethodId = 1; // 71
|
||
|
self._supportedDDPVersions = options.supportedDDPVersions; // 72
|
||
|
// 73
|
||
|
self._heartbeatInterval = options.heartbeatInterval; // 74
|
||
|
self._heartbeatTimeout = options.heartbeatTimeout; // 75
|
||
|
// 76
|
||
|
// Tracks methods which the user has tried to call but which have not yet // 77
|
||
|
// called their user callback (ie, they are waiting on their result or for all // 78
|
||
|
// of their writes to be written to the local cache). Map from method ID to // 79
|
||
|
// MethodInvoker object. // 80
|
||
|
self._methodInvokers = {}; // 81
|
||
|
// 82
|
||
|
// Tracks methods which the user has called but whose result messages have not // 83
|
||
|
// arrived yet. // 84
|
||
|
// // 85
|
||
|
// _outstandingMethodBlocks is an array of blocks of methods. Each block // 86
|
||
|
// represents a set of methods that can run at the same time. The first block // 87
|
||
|
// represents the methods which are currently in flight; subsequent blocks // 88
|
||
|
// must wait for previous blocks to be fully finished before they can be sent // 89
|
||
|
// to the server. // 90
|
||
|
// // 91
|
||
|
// Each block is an object with the following fields: // 92
|
||
|
// - methods: a list of MethodInvoker objects // 93
|
||
|
// - wait: a boolean; if true, this block had a single method invoked with // 94
|
||
|
// the "wait" option // 95
|
||
|
// // 96
|
||
|
// There will never be adjacent blocks with wait=false, because the only thing // 97
|
||
|
// that makes methods need to be serialized is a wait method. // 98
|
||
|
// // 99
|
||
|
// Methods are removed from the first block when their "result" is // 100
|
||
|
// received. The entire first block is only removed when all of the in-flight // 101
|
||
|
// methods have received their results (so the "methods" list is empty) *AND* // 102
|
||
|
// all of the data written by those methods are visible in the local cache. So // 103
|
||
|
// it is possible for the first block's methods list to be empty, if we are // 104
|
||
|
// still waiting for some objects to quiesce. // 105
|
||
|
// // 106
|
||
|
// Example: // 107
|
||
|
// _outstandingMethodBlocks = [ // 108
|
||
|
// {wait: false, methods: []}, // 109
|
||
|
// {wait: true, methods: [<MethodInvoker for 'login'>]}, // 110
|
||
|
// {wait: false, methods: [<MethodInvoker for 'foo'>, // 111
|
||
|
// <MethodInvoker for 'bar'>]}] // 112
|
||
|
// This means that there were some methods which were sent to the server and // 113
|
||
|
// which have returned their results, but some of the data written by // 114
|
||
|
// the methods may not be visible in the local cache. Once all that data is // 115
|
||
|
// visible, we will send a 'login' method. Once the login method has returned // 116
|
||
|
// and all the data is visible (including re-running subs if userId changes), // 117
|
||
|
// we will send the 'foo' and 'bar' methods in parallel. // 118
|
||
|
self._outstandingMethodBlocks = []; // 119
|
||
|
// 120
|
||
|
// method ID -> array of objects with keys 'collection' and 'id', listing // 121
|
||
|
// documents written by a given method's stub. keys are associated with // 122
|
||
|
// methods whose stub wrote at least one document, and whose data-done message // 123
|
||
|
// has not yet been received. // 124
|
||
|
self._documentsWrittenByStub = {}; // 125
|
||
|
// collection -> IdMap of "server document" object. A "server document" has: // 126
|
||
|
// - "document": the version of the document according the // 127
|
||
|
// server (ie, the snapshot before a stub wrote it, amended by any changes // 128
|
||
|
// received from the server) // 129
|
||
|
// It is undefined if we think the document does not exist // 130
|
||
|
// - "writtenByStubs": a set of method IDs whose stubs wrote to the document // 131
|
||
|
// whose "data done" messages have not yet been processed // 132
|
||
|
self._serverDocuments = {}; // 133
|
||
|
// 134
|
||
|
// Array of callbacks to be called after the next update of the local // 135
|
||
|
// cache. Used for: // 136
|
||
|
// - Calling methodInvoker.dataVisible and sub ready callbacks after // 137
|
||
|
// the relevant data is flushed. // 138
|
||
|
// - Invoking the callbacks of "half-finished" methods after reconnect // 139
|
||
|
// quiescence. Specifically, methods whose result was received over the old // 140
|
||
|
// connection (so we don't re-send it) but whose data had not been made // 141
|
||
|
// visible. // 142
|
||
|
self._afterUpdateCallbacks = []; // 143
|
||
|
// 144
|
||
|
// In two contexts, we buffer all incoming data messages and then process them // 145
|
||
|
// all at once in a single update: // 146
|
||
|
// - During reconnect, we buffer all data messages until all subs that had // 147
|
||
|
// been ready before reconnect are ready again, and all methods that are // 148
|
||
|
// active have returned their "data done message"; then // 149
|
||
|
// - During the execution of a "wait" method, we buffer all data messages // 150
|
||
|
// until the wait method gets its "data done" message. (If the wait method // 151
|
||
|
// occurs during reconnect, it doesn't get any special handling.) // 152
|
||
|
// all data messages are processed in one update. // 153
|
||
|
// // 154
|
||
|
// The following fields are used for this "quiescence" process. // 155
|
||
|
// 156
|
||
|
// This buffers the messages that aren't being processed yet. // 157
|
||
|
self._messagesBufferedUntilQuiescence = []; // 158
|
||
|
// Map from method ID -> true. Methods are removed from this when their // 159
|
||
|
// "data done" message is received, and we will not quiesce until it is // 160
|
||
|
// empty. // 161
|
||
|
self._methodsBlockingQuiescence = {}; // 162
|
||
|
// map from sub ID -> true for subs that were ready (ie, called the sub // 163
|
||
|
// ready callback) before reconnect but haven't become ready again yet // 164
|
||
|
self._subsBeingRevived = {}; // map from sub._id -> true // 165
|
||
|
// if true, the next data update should reset all stores. (set during // 166
|
||
|
// reconnect.) // 167
|
||
|
self._resetStores = false; // 168
|
||
|
// 169
|
||
|
// name -> array of updates for (yet to be created) collections // 170
|
||
|
self._updatesForUnknownStores = {}; // 171
|
||
|
// if we're blocking a migration, the retry func // 172
|
||
|
self._retryMigrate = null; // 173
|
||
|
// 174
|
||
|
// metadata for subscriptions. Map from sub ID to object with keys: // 175
|
||
|
// - id // 176
|
||
|
// - name // 177
|
||
|
// - params // 178
|
||
|
// - inactive (if true, will be cleaned up if not reused in re-run) // 179
|
||
|
// - ready (has the 'ready' message been received?) // 180
|
||
|
// - readyCallback (an optional callback to call when ready) // 181
|
||
|
// - errorCallback (an optional callback to call if the sub terminates with // 182
|
||
|
// an error, XXX COMPAT WITH 1.0.3.1) // 183
|
||
|
// - stopCallback (an optional callback to call when the sub terminates // 184
|
||
|
// for any reason, with an error argument if an error triggered the stop) // 185
|
||
|
self._subscriptions = {}; // 186
|
||
|
// 187
|
||
|
// Reactive userId. // 188
|
||
|
self._userId = null; // 189
|
||
|
self._userIdDeps = new Tracker.Dependency; // 190
|
||
|
// 191
|
||
|
// Block auto-reload while we're waiting for method responses. // 192
|
||
|
if (Meteor.isClient && Package.reload && !options.reloadWithOutstanding) { // 193
|
||
|
Package.reload.Reload._onMigrate(function (retry) { // 194
|
||
|
if (!self._readyToMigrate()) { // 195
|
||
|
if (self._retryMigrate) // 196
|
||
|
throw new Error("Two migrations in progress?"); // 197
|
||
|
self._retryMigrate = retry; // 198
|
||
|
return false; // 199
|
||
|
} else { // 200
|
||
|
return [true]; // 201
|
||
|
} // 202
|
||
|
}); // 203
|
||
|
} // 204
|
||
|
// 205
|
||
|
var onMessage = function (raw_msg) { // 206
|
||
|
try { // 207
|
||
|
var msg = parseDDP(raw_msg); // 208
|
||
|
} catch (e) { // 209
|
||
|
Meteor._debug("Exception while parsing DDP", e); // 210
|
||
|
return; // 211
|
||
|
} // 212
|
||
|
// 213
|
||
|
if (msg === null || !msg.msg) { // 214
|
||
|
// XXX COMPAT WITH 0.6.6. ignore the old welcome message for back // 215
|
||
|
// compat. Remove this 'if' once the server stops sending welcome // 216
|
||
|
// messages (stream_server.js). // 217
|
||
|
if (! (msg && msg.server_id)) // 218
|
||
|
Meteor._debug("discarding invalid livedata message", msg); // 219
|
||
|
return; // 220
|
||
|
} // 221
|
||
|
// 222
|
||
|
if (msg.msg === 'connected') { // 223
|
||
|
self._version = self._versionSuggestion; // 224
|
||
|
self._livedata_connected(msg); // 225
|
||
|
options.onConnected(); // 226
|
||
|
} // 227
|
||
|
else if (msg.msg == 'failed') { // 228
|
||
|
if (_.contains(self._supportedDDPVersions, msg.version)) { // 229
|
||
|
self._versionSuggestion = msg.version; // 230
|
||
|
self._stream.reconnect({_force: true}); // 231
|
||
|
} else { // 232
|
||
|
var description = // 233
|
||
|
"DDP version negotiation failed; server requested version " + msg.version; // 234
|
||
|
self._stream.disconnect({_permanent: true, _error: description}); // 235
|
||
|
options.onDDPVersionNegotiationFailure(description); // 236
|
||
|
} // 237
|
||
|
} // 238
|
||
|
else if (msg.msg === 'ping') { // 239
|
||
|
if (options.respondToPings) // 240
|
||
|
self._send({msg: "pong", id: msg.id}); // 241
|
||
|
if (self._heartbeat) // 242
|
||
|
self._heartbeat.pingReceived(); // 243
|
||
|
} // 244
|
||
|
else if (msg.msg === 'pong') { // 245
|
||
|
if (self._heartbeat) { // 246
|
||
|
self._heartbeat.pongReceived(); // 247
|
||
|
} // 248
|
||
|
} // 249
|
||
|
else if (_.include(['added', 'changed', 'removed', 'ready', 'updated'], msg.msg)) // 250
|
||
|
self._livedata_data(msg); // 251
|
||
|
else if (msg.msg === 'nosub') // 252
|
||
|
self._livedata_nosub(msg); // 253
|
||
|
else if (msg.msg === 'result') // 254
|
||
|
self._livedata_result(msg); // 255
|
||
|
else if (msg.msg === 'error') // 256
|
||
|
self._livedata_error(msg); // 257
|
||
|
else // 258
|
||
|
Meteor._debug("discarding unknown livedata message type", msg); // 259
|
||
|
}; // 260
|
||
|
// 261
|
||
|
var onReset = function () { // 262
|
||
|
// Send a connect message at the beginning of the stream. // 263
|
||
|
// NOTE: reset is called even on the first connection, so this is // 264
|
||
|
// the only place we send this message. // 265
|
||
|
var msg = {msg: 'connect'}; // 266
|
||
|
if (self._lastSessionId) // 267
|
||
|
msg.session = self._lastSessionId; // 268
|
||
|
msg.version = self._versionSuggestion || self._supportedDDPVersions[0]; // 269
|
||
|
self._versionSuggestion = msg.version; // 270
|
||
|
msg.support = self._supportedDDPVersions; // 271
|
||
|
self._send(msg); // 272
|
||
|
// 273
|
||
|
// Now, to minimize setup latency, go ahead and blast out all of // 274
|
||
|
// our pending methods ands subscriptions before we've even taken // 275
|
||
|
// the necessary RTT to know if we successfully reconnected. (1) // 276
|
||
|
// They're supposed to be idempotent; (2) even if we did // 277
|
||
|
// reconnect, we're not sure what messages might have gotten lost // 278
|
||
|
// (in either direction) since we were disconnected (TCP being // 279
|
||
|
// sloppy about that.) // 280
|
||
|
// 281
|
||
|
// If the current block of methods all got their results (but didn't all get // 282
|
||
|
// their data visible), discard the empty block now. // 283
|
||
|
if (! _.isEmpty(self._outstandingMethodBlocks) && // 284
|
||
|
_.isEmpty(self._outstandingMethodBlocks[0].methods)) { // 285
|
||
|
self._outstandingMethodBlocks.shift(); // 286
|
||
|
} // 287
|
||
|
// 288
|
||
|
// Mark all messages as unsent, they have not yet been sent on this // 289
|
||
|
// connection. // 290
|
||
|
_.each(self._methodInvokers, function (m) { // 291
|
||
|
m.sentMessage = false; // 292
|
||
|
}); // 293
|
||
|
// 294
|
||
|
// If an `onReconnect` handler is set, call it first. Go through // 295
|
||
|
// some hoops to ensure that methods that are called from within // 296
|
||
|
// `onReconnect` get executed _before_ ones that were originally // 297
|
||
|
// outstanding (since `onReconnect` is used to re-establish auth // 298
|
||
|
// certificates) // 299
|
||
|
if (self.onReconnect) // 300
|
||
|
self._callOnReconnectAndSendAppropriateOutstandingMethods(); // 301
|
||
|
else // 302
|
||
|
self._sendOutstandingMethods(); // 303
|
||
|
// 304
|
||
|
// add new subscriptions at the end. this way they take effect after // 305
|
||
|
// the handlers and we don't see flicker. // 306
|
||
|
_.each(self._subscriptions, function (sub, id) { // 307
|
||
|
self._send({ // 308
|
||
|
msg: 'sub', // 309
|
||
|
id: id, // 310
|
||
|
name: sub.name, // 311
|
||
|
params: sub.params // 312
|
||
|
}); // 313
|
||
|
}); // 314
|
||
|
}; // 315
|
||
|
// 316
|
||
|
var onDisconnect = function () { // 317
|
||
|
if (self._heartbeat) { // 318
|
||
|
self._heartbeat.stop(); // 319
|
||
|
self._heartbeat = null; // 320
|
||
|
} // 321
|
||
|
}; // 322
|
||
|
// 323
|
||
|
if (Meteor.isServer) { // 324
|
||
|
self._stream.on('message', Meteor.bindEnvironment(onMessage, Meteor._debug)); // 325
|
||
|
self._stream.on('reset', Meteor.bindEnvironment(onReset, Meteor._debug)); // 326
|
||
|
self._stream.on('disconnect', Meteor.bindEnvironment(onDisconnect, Meteor._debug)); // 327
|
||
|
} else { // 328
|
||
|
self._stream.on('message', onMessage); // 329
|
||
|
self._stream.on('reset', onReset); // 330
|
||
|
self._stream.on('disconnect', onDisconnect); // 331
|
||
|
} // 332
|
||
|
}; // 333
|
||
|
// 334
|
||
|
// A MethodInvoker manages sending a method to the server and calling the user's // 335
|
||
|
// callbacks. On construction, it registers itself in the connection's // 336
|
||
|
// _methodInvokers map; it removes itself once the method is fully finished and // 337
|
||
|
// the callback is invoked. This occurs when it has both received a result, // 338
|
||
|
// and the data written by it is fully visible. // 339
|
||
|
var MethodInvoker = function (options) { // 340
|
||
|
var self = this; // 341
|
||
|
// 342
|
||
|
// Public (within this file) fields. // 343
|
||
|
self.methodId = options.methodId; // 344
|
||
|
self.sentMessage = false; // 345
|
||
|
// 346
|
||
|
self._callback = options.callback; // 347
|
||
|
self._connection = options.connection; // 348
|
||
|
self._message = options.message; // 349
|
||
|
self._onResultReceived = options.onResultReceived || function () {}; // 350
|
||
|
self._wait = options.wait; // 351
|
||
|
self._methodResult = null; // 352
|
||
|
self._dataVisible = false; // 353
|
||
|
// 354
|
||
|
// Register with the connection. // 355
|
||
|
self._connection._methodInvokers[self.methodId] = self; // 356
|
||
|
}; // 357
|
||
|
_.extend(MethodInvoker.prototype, { // 358
|
||
|
// Sends the method message to the server. May be called additional times if // 359
|
||
|
// we lose the connection and reconnect before receiving a result. // 360
|
||
|
sendMessage: function () { // 361
|
||
|
var self = this; // 362
|
||
|
// This function is called before sending a method (including resending on // 363
|
||
|
// reconnect). We should only (re)send methods where we don't already have a // 364
|
||
|
// result! // 365
|
||
|
if (self.gotResult()) // 366
|
||
|
throw new Error("sendingMethod is called on method with result"); // 367
|
||
|
// 368
|
||
|
// If we're re-sending it, it doesn't matter if data was written the first // 369
|
||
|
// time. // 370
|
||
|
self._dataVisible = false; // 371
|
||
|
// 372
|
||
|
self.sentMessage = true; // 373
|
||
|
// 374
|
||
|
// If this is a wait method, make all data messages be buffered until it is // 375
|
||
|
// done. // 376
|
||
|
if (self._wait) // 377
|
||
|
self._connection._methodsBlockingQuiescence[self.methodId] = true; // 378
|
||
|
// 379
|
||
|
// Actually send the message. // 380
|
||
|
self._connection._send(self._message); // 381
|
||
|
}, // 382
|
||
|
// Invoke the callback, if we have both a result and know that all data has // 383
|
||
|
// been written to the local cache. // 384
|
||
|
_maybeInvokeCallback: function () { // 385
|
||
|
var self = this; // 386
|
||
|
if (self._methodResult && self._dataVisible) { // 387
|
||
|
// Call the callback. (This won't throw: the callback was wrapped with // 388
|
||
|
// bindEnvironment.) // 389
|
||
|
self._callback(self._methodResult[0], self._methodResult[1]); // 390
|
||
|
// 391
|
||
|
// Forget about this method. // 392
|
||
|
delete self._connection._methodInvokers[self.methodId]; // 393
|
||
|
// 394
|
||
|
// Let the connection know that this method is finished, so it can try to // 395
|
||
|
// move on to the next block of methods. // 396
|
||
|
self._connection._outstandingMethodFinished(); // 397
|
||
|
} // 398
|
||
|
}, // 399
|
||
|
// Call with the result of the method from the server. Only may be called // 400
|
||
|
// once; once it is called, you should not call sendMessage again. // 401
|
||
|
// If the user provided an onResultReceived callback, call it immediately. // 402
|
||
|
// Then invoke the main callback if data is also visible. // 403
|
||
|
receiveResult: function (err, result) { // 404
|
||
|
var self = this; // 405
|
||
|
if (self.gotResult()) // 406
|
||
|
throw new Error("Methods should only receive results once"); // 407
|
||
|
self._methodResult = [err, result]; // 408
|
||
|
self._onResultReceived(err, result); // 409
|
||
|
self._maybeInvokeCallback(); // 410
|
||
|
}, // 411
|
||
|
// Call this when all data written by the method is visible. This means that // 412
|
||
|
// the method has returns its "data is done" message *AND* all server // 413
|
||
|
// documents that are buffered at that time have been written to the local // 414
|
||
|
// cache. Invokes the main callback if the result has been received. // 415
|
||
|
dataVisible: function () { // 416
|
||
|
var self = this; // 417
|
||
|
self._dataVisible = true; // 418
|
||
|
self._maybeInvokeCallback(); // 419
|
||
|
}, // 420
|
||
|
// True if receiveResult has been called. // 421
|
||
|
gotResult: function () { // 422
|
||
|
var self = this; // 423
|
||
|
return !!self._methodResult; // 424
|
||
|
} // 425
|
||
|
}); // 426
|
||
|
// 427
|
||
|
_.extend(Connection.prototype, { // 428
|
||
|
// 'name' is the name of the data on the wire that should go in the // 429
|
||
|
// store. 'wrappedStore' should be an object with methods beginUpdate, update, // 430
|
||
|
// endUpdate, saveOriginals, retrieveOriginals. see Collection for an example. // 431
|
||
|
registerStore: function (name, wrappedStore) { // 432
|
||
|
var self = this; // 433
|
||
|
// 434
|
||
|
if (name in self._stores) // 435
|
||
|
return false; // 436
|
||
|
// 437
|
||
|
// Wrap the input object in an object which makes any store method not // 438
|
||
|
// implemented by 'store' into a no-op. // 439
|
||
|
var store = {}; // 440
|
||
|
_.each(['update', 'beginUpdate', 'endUpdate', 'saveOriginals', // 441
|
||
|
'retrieveOriginals'], function (method) { // 442
|
||
|
store[method] = function () { // 443
|
||
|
return (wrappedStore[method] // 444
|
||
|
? wrappedStore[method].apply(wrappedStore, arguments) // 445
|
||
|
: undefined); // 446
|
||
|
}; // 447
|
||
|
}); // 448
|
||
|
// 449
|
||
|
self._stores[name] = store; // 450
|
||
|
// 451
|
||
|
var queued = self._updatesForUnknownStores[name]; // 452
|
||
|
if (queued) { // 453
|
||
|
store.beginUpdate(queued.length, false); // 454
|
||
|
_.each(queued, function (msg) { // 455
|
||
|
store.update(msg); // 456
|
||
|
}); // 457
|
||
|
store.endUpdate(); // 458
|
||
|
delete self._updatesForUnknownStores[name]; // 459
|
||
|
} // 460
|
||
|
// 461
|
||
|
return true; // 462
|
||
|
}, // 463
|
||
|
// 464
|
||
|
/** // 465
|
||
|
* @memberOf Meteor // 466
|
||
|
* @summary Subscribe to a record set. Returns a handle that provides // 467
|
||
|
* `stop()` and `ready()` methods. // 468
|
||
|
* @locus Client // 469
|
||
|
* @param {String} name Name of the subscription. Matches the name of the // 470
|
||
|
* server's `publish()` call. // 471
|
||
|
* @param {Any} [arg1,arg2...] Optional arguments passed to publisher // 472
|
||
|
* function on server. // 473
|
||
|
* @param {Function|Object} [callbacks] Optional. May include `onStop` // 474
|
||
|
* and `onReady` callbacks. If there is an error, it is passed as an // 475
|
||
|
* argument to `onStop`. If a function is passed instead of an object, it // 476
|
||
|
* is interpreted as an `onReady` callback. // 477
|
||
|
*/ // 478
|
||
|
subscribe: function (name /* .. [arguments] .. (callback|callbacks) */) { // 479
|
||
|
var self = this; // 480
|
||
|
// 481
|
||
|
var params = Array.prototype.slice.call(arguments, 1); // 482
|
||
|
var callbacks = {}; // 483
|
||
|
if (params.length) { // 484
|
||
|
var lastParam = params[params.length - 1]; // 485
|
||
|
if (_.isFunction(lastParam)) { // 486
|
||
|
callbacks.onReady = params.pop(); // 487
|
||
|
} else if (lastParam && // 488
|
||
|
// XXX COMPAT WITH 1.0.3.1 onError used to exist, but now we use // 489
|
||
|
// onStop with an error callback instead. // 490
|
||
|
_.any([lastParam.onReady, lastParam.onError, lastParam.onStop], // 491
|
||
|
_.isFunction)) { // 492
|
||
|
callbacks = params.pop(); // 493
|
||
|
} // 494
|
||
|
} // 495
|
||
|
// 496
|
||
|
// Is there an existing sub with the same name and param, run in an // 497
|
||
|
// invalidated Computation? This will happen if we are rerunning an // 498
|
||
|
// existing computation. // 499
|
||
|
// // 500
|
||
|
// For example, consider a rerun of: // 501
|
||
|
// // 502
|
||
|
// Tracker.autorun(function () { // 503
|
||
|
// Meteor.subscribe("foo", Session.get("foo")); // 504
|
||
|
// Meteor.subscribe("bar", Session.get("bar")); // 505
|
||
|
// }); // 506
|
||
|
// // 507
|
||
|
// If "foo" has changed but "bar" has not, we will match the "bar" // 508
|
||
|
// subcribe to an existing inactive subscription in order to not // 509
|
||
|
// unsub and resub the subscription unnecessarily. // 510
|
||
|
// // 511
|
||
|
// We only look for one such sub; if there are N apparently-identical subs // 512
|
||
|
// being invalidated, we will require N matching subscribe calls to keep // 513
|
||
|
// them all active. // 514
|
||
|
var existing = _.find(self._subscriptions, function (sub) { // 515
|
||
|
return sub.inactive && sub.name === name && // 516
|
||
|
EJSON.equals(sub.params, params); // 517
|
||
|
}); // 518
|
||
|
// 519
|
||
|
var id; // 520
|
||
|
if (existing) { // 521
|
||
|
id = existing.id; // 522
|
||
|
existing.inactive = false; // reactivate // 523
|
||
|
// 524
|
||
|
if (callbacks.onReady) { // 525
|
||
|
// If the sub is not already ready, replace any ready callback with the // 526
|
||
|
// one provided now. (It's not really clear what users would expect for // 527
|
||
|
// an onReady callback inside an autorun; the semantics we provide is // 528
|
||
|
// that at the time the sub first becomes ready, we call the last // 529
|
||
|
// onReady callback provided, if any.) // 530
|
||
|
if (!existing.ready) // 531
|
||
|
existing.readyCallback = callbacks.onReady; // 532
|
||
|
} // 533
|
||
|
// 534
|
||
|
// XXX COMPAT WITH 1.0.3.1 we used to have onError but now we call // 535
|
||
|
// onStop with an optional error argument // 536
|
||
|
if (callbacks.onError) { // 537
|
||
|
// Replace existing callback if any, so that errors aren't // 538
|
||
|
// double-reported. // 539
|
||
|
existing.errorCallback = callbacks.onError; // 540
|
||
|
} // 541
|
||
|
// 542
|
||
|
if (callbacks.onStop) { // 543
|
||
|
existing.stopCallback = callbacks.onStop; // 544
|
||
|
} // 545
|
||
|
} else { // 546
|
||
|
// New sub! Generate an id, save it locally, and send message. // 547
|
||
|
id = Random.id(); // 548
|
||
|
self._subscriptions[id] = { // 549
|
||
|
id: id, // 550
|
||
|
name: name, // 551
|
||
|
params: EJSON.clone(params), // 552
|
||
|
inactive: false, // 553
|
||
|
ready: false, // 554
|
||
|
readyDeps: new Tracker.Dependency, // 555
|
||
|
readyCallback: callbacks.onReady, // 556
|
||
|
// XXX COMPAT WITH 1.0.3.1 #errorCallback // 557
|
||
|
errorCallback: callbacks.onError, // 558
|
||
|
stopCallback: callbacks.onStop, // 559
|
||
|
connection: self, // 560
|
||
|
remove: function() { // 561
|
||
|
delete this.connection._subscriptions[this.id]; // 562
|
||
|
this.ready && this.readyDeps.changed(); // 563
|
||
|
}, // 564
|
||
|
stop: function() { // 565
|
||
|
this.connection._send({msg: 'unsub', id: id}); // 566
|
||
|
this.remove(); // 567
|
||
|
// 568
|
||
|
if (callbacks.onStop) { // 569
|
||
|
callbacks.onStop(); // 570
|
||
|
} // 571
|
||
|
} // 572
|
||
|
}; // 573
|
||
|
self._send({msg: 'sub', id: id, name: name, params: params}); // 574
|
||
|
} // 575
|
||
|
// 576
|
||
|
// return a handle to the application. // 577
|
||
|
var handle = { // 578
|
||
|
stop: function () { // 579
|
||
|
if (!_.has(self._subscriptions, id)) // 580
|
||
|
return; // 581
|
||
|
// 582
|
||
|
self._subscriptions[id].stop(); // 583
|
||
|
}, // 584
|
||
|
ready: function () { // 585
|
||
|
// return false if we've unsubscribed. // 586
|
||
|
if (!_.has(self._subscriptions, id)) // 587
|
||
|
return false; // 588
|
||
|
var record = self._subscriptions[id]; // 589
|
||
|
record.readyDeps.depend(); // 590
|
||
|
return record.ready; // 591
|
||
|
}, // 592
|
||
|
subscriptionId: id // 593
|
||
|
}; // 594
|
||
|
// 595
|
||
|
if (Tracker.active) { // 596
|
||
|
// We're in a reactive computation, so we'd like to unsubscribe when the // 597
|
||
|
// computation is invalidated... but not if the rerun just re-subscribes // 598
|
||
|
// to the same subscription! When a rerun happens, we use onInvalidate // 599
|
||
|
// as a change to mark the subscription "inactive" so that it can // 600
|
||
|
// be reused from the rerun. If it isn't reused, it's killed from // 601
|
||
|
// an afterFlush. // 602
|
||
|
Tracker.onInvalidate(function (c) { // 603
|
||
|
if (_.has(self._subscriptions, id)) // 604
|
||
|
self._subscriptions[id].inactive = true; // 605
|
||
|
// 606
|
||
|
Tracker.afterFlush(function () { // 607
|
||
|
if (_.has(self._subscriptions, id) && // 608
|
||
|
self._subscriptions[id].inactive) // 609
|
||
|
handle.stop(); // 610
|
||
|
}); // 611
|
||
|
}); // 612
|
||
|
} // 613
|
||
|
// 614
|
||
|
return handle; // 615
|
||
|
}, // 616
|
||
|
// 617
|
||
|
// options: // 618
|
||
|
// - onLateError {Function(error)} called if an error was received after the ready event. // 619
|
||
|
// (errors received before ready cause an error to be thrown) // 620
|
||
|
_subscribeAndWait: function (name, args, options) { // 621
|
||
|
var self = this; // 622
|
||
|
var f = new Future(); // 623
|
||
|
var ready = false; // 624
|
||
|
var handle; // 625
|
||
|
args = args || []; // 626
|
||
|
args.push({ // 627
|
||
|
onReady: function () { // 628
|
||
|
ready = true; // 629
|
||
|
f['return'](); // 630
|
||
|
}, // 631
|
||
|
onError: function (e) { // 632
|
||
|
if (!ready) // 633
|
||
|
f['throw'](e); // 634
|
||
|
else // 635
|
||
|
options && options.onLateError && options.onLateError(e); // 636
|
||
|
} // 637
|
||
|
}); // 638
|
||
|
// 639
|
||
|
handle = self.subscribe.apply(self, [name].concat(args)); // 640
|
||
|
f.wait(); // 641
|
||
|
return handle; // 642
|
||
|
}, // 643
|
||
|
// 644
|
||
|
methods: function (methods) { // 645
|
||
|
var self = this; // 646
|
||
|
_.each(methods, function (func, name) { // 647
|
||
|
if (self._methodHandlers[name]) // 648
|
||
|
throw new Error("A method named '" + name + "' is already defined"); // 649
|
||
|
self._methodHandlers[name] = func; // 650
|
||
|
}); // 651
|
||
|
}, // 652
|
||
|
// 653
|
||
|
/** // 654
|
||
|
* @memberOf Meteor // 655
|
||
|
* @summary Invokes a method passing any number of arguments. // 656
|
||
|
* @locus Anywhere // 657
|
||
|
* @param {String} name Name of method to invoke // 658
|
||
|
* @param {EJSONable} [arg1,arg2...] Optional method arguments // 659
|
||
|
* @param {Function} [asyncCallback] Optional callback, which is called asynchronously with the error or result after the method is complete. If not provided, the method runs synchronously if possible (see below).
|
||
|
*/ // 661
|
||
|
call: function (name /* .. [arguments] .. callback */) { // 662
|
||
|
// if it's a function, the last argument is the result callback, // 663
|
||
|
// not a parameter to the remote method. // 664
|
||
|
var args = Array.prototype.slice.call(arguments, 1); // 665
|
||
|
if (args.length && typeof args[args.length - 1] === "function") // 666
|
||
|
var callback = args.pop(); // 667
|
||
|
return this.apply(name, args, callback); // 668
|
||
|
}, // 669
|
||
|
// 670
|
||
|
// @param options {Optional Object} // 671
|
||
|
// wait: Boolean - Should we wait to call this until all current methods // 672
|
||
|
// are fully finished, and block subsequent method calls // 673
|
||
|
// until this method is fully finished? // 674
|
||
|
// (does not affect methods called from within this method) // 675
|
||
|
// onResultReceived: Function - a callback to call as soon as the method // 676
|
||
|
// result is received. the data written by // 677
|
||
|
// the method may not yet be in the cache! // 678
|
||
|
// returnStubValue: Boolean - If true then in cases where we would have // 679
|
||
|
// otherwise discarded the stub's return value // 680
|
||
|
// and returned undefined, instead we go ahead // 681
|
||
|
// and return it. Specifically, this is any // 682
|
||
|
// time other than when (a) we are already // 683
|
||
|
// inside a stub or (b) we are in Node and no // 684
|
||
|
// callback was provided. Currently we require // 685
|
||
|
// this flag to be explicitly passed to reduce // 686
|
||
|
// the likelihood that stub return values will // 687
|
||
|
// be confused with server return values; we // 688
|
||
|
// may improve this in future. // 689
|
||
|
// @param callback {Optional Function} // 690
|
||
|
// 691
|
||
|
/** // 692
|
||
|
* @memberOf Meteor // 693
|
||
|
* @summary Invoke a method passing an array of arguments. // 694
|
||
|
* @locus Anywhere // 695
|
||
|
* @param {String} name Name of method to invoke // 696
|
||
|
* @param {EJSONable[]} args Method arguments // 697
|
||
|
* @param {Object} [options] // 698
|
||
|
* @param {Boolean} options.wait (Client only) If true, don't send this method until all previous method calls have completed, and don't send any subsequent method calls until this one is completed.
|
||
|
* @param {Function} options.onResultReceived (Client only) This callback is invoked with the error or result of the method (just like `asyncCallback`) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method.
|
||
|
* @param {Function} [asyncCallback] Optional callback; same semantics as in [`Meteor.call`](#meteor_call). // 701
|
||
|
*/ // 702
|
||
|
apply: function (name, args, options, callback) { // 703
|
||
|
var self = this; // 704
|
||
|
// 705
|
||
|
// We were passed 3 arguments. They may be either (name, args, options) // 706
|
||
|
// or (name, args, callback) // 707
|
||
|
if (!callback && typeof options === 'function') { // 708
|
||
|
callback = options; // 709
|
||
|
options = {}; // 710
|
||
|
} // 711
|
||
|
options = options || {}; // 712
|
||
|
// 713
|
||
|
if (callback) { // 714
|
||
|
// XXX would it be better form to do the binding in stream.on, // 715
|
||
|
// or caller, instead of here? // 716
|
||
|
// XXX improve error message (and how we report it) // 717
|
||
|
callback = Meteor.bindEnvironment( // 718
|
||
|
callback, // 719
|
||
|
"delivering result of invoking '" + name + "'" // 720
|
||
|
); // 721
|
||
|
} // 722
|
||
|
// 723
|
||
|
// Keep our args safe from mutation (eg if we don't send the message for a // 724
|
||
|
// while because of a wait method). // 725
|
||
|
args = EJSON.clone(args); // 726
|
||
|
// 727
|
||
|
// Lazily allocate method ID once we know that it'll be needed. // 728
|
||
|
var methodId = (function () { // 729
|
||
|
var id; // 730
|
||
|
return function () { // 731
|
||
|
if (id === undefined) // 732
|
||
|
id = '' + (self._nextMethodId++); // 733
|
||
|
return id; // 734
|
||
|
}; // 735
|
||
|
})(); // 736
|
||
|
// 737
|
||
|
var enclosing = DDP._CurrentInvocation.get(); // 738
|
||
|
var alreadyInSimulation = enclosing && enclosing.isSimulation; // 739
|
||
|
// 740
|
||
|
// Lazily generate a randomSeed, only if it is requested by the stub. // 741
|
||
|
// The random streams only have utility if they're used on both the client // 742
|
||
|
// and the server; if the client doesn't generate any 'random' values // 743
|
||
|
// then we don't expect the server to generate any either. // 744
|
||
|
// Less commonly, the server may perform different actions from the client, // 745
|
||
|
// and may in fact generate values where the client did not, but we don't // 746
|
||
|
// have any client-side values to match, so even here we may as well just // 747
|
||
|
// use a random seed on the server. In that case, we don't pass the // 748
|
||
|
// randomSeed to save bandwidth, and we don't even generate it to save a // 749
|
||
|
// bit of CPU and to avoid consuming entropy. // 750
|
||
|
var randomSeed = null; // 751
|
||
|
var randomSeedGenerator = function () { // 752
|
||
|
if (randomSeed === null) { // 753
|
||
|
randomSeed = makeRpcSeed(enclosing, name); // 754
|
||
|
} // 755
|
||
|
return randomSeed; // 756
|
||
|
}; // 757
|
||
|
// 758
|
||
|
// Run the stub, if we have one. The stub is supposed to make some // 759
|
||
|
// temporary writes to the database to give the user a smooth experience // 760
|
||
|
// until the actual result of executing the method comes back from the // 761
|
||
|
// server (whereupon the temporary writes to the database will be reversed // 762
|
||
|
// during the beginUpdate/endUpdate process.) // 763
|
||
|
// // 764
|
||
|
// Normally, we ignore the return value of the stub (even if it is an // 765
|
||
|
// exception), in favor of the real return value from the server. The // 766
|
||
|
// exception is if the *caller* is a stub. In that case, we're not going // 767
|
||
|
// to do a RPC, so we use the return value of the stub as our return // 768
|
||
|
// value. // 769
|
||
|
// 770
|
||
|
var stub = self._methodHandlers[name]; // 771
|
||
|
if (stub) { // 772
|
||
|
var setUserId = function(userId) { // 773
|
||
|
self.setUserId(userId); // 774
|
||
|
}; // 775
|
||
|
// 776
|
||
|
var invocation = new MethodInvocation({ // 777
|
||
|
isSimulation: true, // 778
|
||
|
userId: self.userId(), // 779
|
||
|
setUserId: setUserId, // 780
|
||
|
randomSeed: function () { return randomSeedGenerator(); } // 781
|
||
|
}); // 782
|
||
|
// 783
|
||
|
if (!alreadyInSimulation) // 784
|
||
|
self._saveOriginals(); // 785
|
||
|
// 786
|
||
|
try { // 787
|
||
|
// Note that unlike in the corresponding server code, we never audit // 788
|
||
|
// that stubs check() their arguments. // 789
|
||
|
var stubReturnValue = DDP._CurrentInvocation.withValue(invocation, function () { // 790
|
||
|
if (Meteor.isServer) { // 791
|
||
|
// Because saveOriginals and retrieveOriginals aren't reentrant, // 792
|
||
|
// don't allow stubs to yield. // 793
|
||
|
return Meteor._noYieldsAllowed(function () { // 794
|
||
|
// re-clone, so that the stub can't affect our caller's values // 795
|
||
|
return stub.apply(invocation, EJSON.clone(args)); // 796
|
||
|
}); // 797
|
||
|
} else { // 798
|
||
|
return stub.apply(invocation, EJSON.clone(args)); // 799
|
||
|
} // 800
|
||
|
}); // 801
|
||
|
} // 802
|
||
|
catch (e) { // 803
|
||
|
var exception = e; // 804
|
||
|
} // 805
|
||
|
// 806
|
||
|
if (!alreadyInSimulation) // 807
|
||
|
self._retrieveAndStoreOriginals(methodId()); // 808
|
||
|
} // 809
|
||
|
// 810
|
||
|
// If we're in a simulation, stop and return the result we have, // 811
|
||
|
// rather than going on to do an RPC. If there was no stub, // 812
|
||
|
// we'll end up returning undefined. // 813
|
||
|
if (alreadyInSimulation) { // 814
|
||
|
if (callback) { // 815
|
||
|
callback(exception, stubReturnValue); // 816
|
||
|
return undefined; // 817
|
||
|
} // 818
|
||
|
if (exception) // 819
|
||
|
throw exception; // 820
|
||
|
return stubReturnValue; // 821
|
||
|
} // 822
|
||
|
// 823
|
||
|
// If an exception occurred in a stub, and we're ignoring it // 824
|
||
|
// because we're doing an RPC and want to use what the server // 825
|
||
|
// returns instead, log it so the developer knows. // 826
|
||
|
// // 827
|
||
|
// Tests can set the 'expected' flag on an exception so it won't // 828
|
||
|
// go to log. // 829
|
||
|
if (exception && !exception.expected) { // 830
|
||
|
Meteor._debug("Exception while simulating the effect of invoking '" + // 831
|
||
|
name + "'", exception, exception.stack); // 832
|
||
|
} // 833
|
||
|
// 834
|
||
|
// 835
|
||
|
// At this point we're definitely doing an RPC, and we're going to // 836
|
||
|
// return the value of the RPC to the caller. // 837
|
||
|
// 838
|
||
|
// If the caller didn't give a callback, decide what to do. // 839
|
||
|
if (!callback) { // 840
|
||
|
if (Meteor.isClient) { // 841
|
||
|
// On the client, we don't have fibers, so we can't block. The // 842
|
||
|
// only thing we can do is to return undefined and discard the // 843
|
||
|
// result of the RPC. If an error occurred then print the error // 844
|
||
|
// to the console. // 845
|
||
|
callback = function (err) { // 846
|
||
|
err && Meteor._debug("Error invoking Method '" + name + "':", // 847
|
||
|
err.message); // 848
|
||
|
}; // 849
|
||
|
} else { // 850
|
||
|
// On the server, make the function synchronous. Throw on // 851
|
||
|
// errors, return on success. // 852
|
||
|
var future = new Future; // 853
|
||
|
callback = future.resolver(); // 854
|
||
|
} // 855
|
||
|
} // 856
|
||
|
// Send the RPC. Note that on the client, it is important that the // 857
|
||
|
// stub have finished before we send the RPC, so that we know we have // 858
|
||
|
// a complete list of which local documents the stub wrote. // 859
|
||
|
var message = { // 860
|
||
|
msg: 'method', // 861
|
||
|
method: name, // 862
|
||
|
params: args, // 863
|
||
|
id: methodId() // 864
|
||
|
}; // 865
|
||
|
// 866
|
||
|
// Send the randomSeed only if we used it // 867
|
||
|
if (randomSeed !== null) { // 868
|
||
|
message.randomSeed = randomSeed; // 869
|
||
|
} // 870
|
||
|
// 871
|
||
|
var methodInvoker = new MethodInvoker({ // 872
|
||
|
methodId: methodId(), // 873
|
||
|
callback: callback, // 874
|
||
|
connection: self, // 875
|
||
|
onResultReceived: options.onResultReceived, // 876
|
||
|
wait: !!options.wait, // 877
|
||
|
message: message // 878
|
||
|
}); // 879
|
||
|
// 880
|
||
|
if (options.wait) { // 881
|
||
|
// It's a wait method! Wait methods go in their own block. // 882
|
||
|
self._outstandingMethodBlocks.push( // 883
|
||
|
{wait: true, methods: [methodInvoker]}); // 884
|
||
|
} else { // 885
|
||
|
// Not a wait method. Start a new block if the previous block was a wait // 886
|
||
|
// block, and add it to the last block of methods. // 887
|
||
|
if (_.isEmpty(self._outstandingMethodBlocks) || // 888
|
||
|
_.last(self._outstandingMethodBlocks).wait) // 889
|
||
|
self._outstandingMethodBlocks.push({wait: false, methods: []}); // 890
|
||
|
_.last(self._outstandingMethodBlocks).methods.push(methodInvoker); // 891
|
||
|
} // 892
|
||
|
// 893
|
||
|
// If we added it to the first block, send it out now. // 894
|
||
|
if (self._outstandingMethodBlocks.length === 1) // 895
|
||
|
methodInvoker.sendMessage(); // 896
|
||
|
// 897
|
||
|
// If we're using the default callback on the server, // 898
|
||
|
// block waiting for the result. // 899
|
||
|
if (future) { // 900
|
||
|
return future.wait(); // 901
|
||
|
} // 902
|
||
|
return options.returnStubValue ? stubReturnValue : undefined; // 903
|
||
|
}, // 904
|
||
|
// 905
|
||
|
// Before calling a method stub, prepare all stores to track changes and allow // 906
|
||
|
// _retrieveAndStoreOriginals to get the original versions of changed // 907
|
||
|
// documents. // 908
|
||
|
_saveOriginals: function () { // 909
|
||
|
var self = this; // 910
|
||
|
_.each(self._stores, function (s) { // 911
|
||
|
s.saveOriginals(); // 912
|
||
|
}); // 913
|
||
|
}, // 914
|
||
|
// Retrieves the original versions of all documents modified by the stub for // 915
|
||
|
// method 'methodId' from all stores and saves them to _serverDocuments (keyed // 916
|
||
|
// by document) and _documentsWrittenByStub (keyed by method ID). // 917
|
||
|
_retrieveAndStoreOriginals: function (methodId) { // 918
|
||
|
var self = this; // 919
|
||
|
if (self._documentsWrittenByStub[methodId]) // 920
|
||
|
throw new Error("Duplicate methodId in _retrieveAndStoreOriginals"); // 921
|
||
|
// 922
|
||
|
var docsWritten = []; // 923
|
||
|
_.each(self._stores, function (s, collection) { // 924
|
||
|
var originals = s.retrieveOriginals(); // 925
|
||
|
// not all stores define retrieveOriginals // 926
|
||
|
if (!originals) // 927
|
||
|
return; // 928
|
||
|
originals.forEach(function (doc, id) { // 929
|
||
|
docsWritten.push({collection: collection, id: id}); // 930
|
||
|
if (!_.has(self._serverDocuments, collection)) // 931
|
||
|
self._serverDocuments[collection] = new LocalCollection._IdMap; // 932
|
||
|
var serverDoc = self._serverDocuments[collection].setDefault(id, {}); // 933
|
||
|
if (serverDoc.writtenByStubs) { // 934
|
||
|
// We're not the first stub to write this doc. Just add our method ID // 935
|
||
|
// to the record. // 936
|
||
|
serverDoc.writtenByStubs[methodId] = true; // 937
|
||
|
} else { // 938
|
||
|
// First stub! Save the original value and our method ID. // 939
|
||
|
serverDoc.document = doc; // 940
|
||
|
serverDoc.flushCallbacks = []; // 941
|
||
|
serverDoc.writtenByStubs = {}; // 942
|
||
|
serverDoc.writtenByStubs[methodId] = true; // 943
|
||
|
} // 944
|
||
|
}); // 945
|
||
|
}); // 946
|
||
|
if (!_.isEmpty(docsWritten)) { // 947
|
||
|
self._documentsWrittenByStub[methodId] = docsWritten; // 948
|
||
|
} // 949
|
||
|
}, // 950
|
||
|
// 951
|
||
|
// This is very much a private function we use to make the tests // 952
|
||
|
// take up fewer server resources after they complete. // 953
|
||
|
_unsubscribeAll: function () { // 954
|
||
|
var self = this; // 955
|
||
|
_.each(_.clone(self._subscriptions), function (sub, id) { // 956
|
||
|
// Avoid killing the autoupdate subscription so that developers // 957
|
||
|
// still get hot code pushes when writing tests. // 958
|
||
|
// // 959
|
||
|
// XXX it's a hack to encode knowledge about autoupdate here, // 960
|
||
|
// but it doesn't seem worth it yet to have a special API for // 961
|
||
|
// subscriptions to preserve after unit tests. // 962
|
||
|
if (sub.name !== 'meteor_autoupdate_clientVersions') { // 963
|
||
|
self._subscriptions[id].stop(); // 964
|
||
|
} // 965
|
||
|
}); // 966
|
||
|
}, // 967
|
||
|
// 968
|
||
|
// Sends the DDP stringification of the given message object // 969
|
||
|
_send: function (obj) { // 970
|
||
|
var self = this; // 971
|
||
|
self._stream.send(stringifyDDP(obj)); // 972
|
||
|
}, // 973
|
||
|
// 974
|
||
|
// We detected via DDP-level heartbeats that we've lost the // 975
|
||
|
// connection. Unlike `disconnect` or `close`, a lost connection // 976
|
||
|
// will be automatically retried. // 977
|
||
|
_lostConnection: function (error) { // 978
|
||
|
var self = this; // 979
|
||
|
self._stream._lostConnection(error); // 980
|
||
|
}, // 981
|
||
|
// 982
|
||
|
/** // 983
|
||
|
* @summary Get the current connection status. A reactive data source. // 984
|
||
|
* @locus Client // 985
|
||
|
* @memberOf Meteor // 986
|
||
|
*/ // 987
|
||
|
status: function (/*passthrough args*/) { // 988
|
||
|
var self = this; // 989
|
||
|
return self._stream.status.apply(self._stream, arguments); // 990
|
||
|
}, // 991
|
||
|
// 992
|
||
|
/** // 993
|
||
|
* @summary Force an immediate reconnection attempt if the client is not connected to the server. // 994
|
||
|
// 995
|
||
|
This method does nothing if the client is already connected. // 996
|
||
|
* @locus Client // 997
|
||
|
* @memberOf Meteor // 998
|
||
|
*/ // 999
|
||
|
reconnect: function (/*passthrough args*/) { // 1000
|
||
|
var self = this; // 1001
|
||
|
return self._stream.reconnect.apply(self._stream, arguments); // 1002
|
||
|
}, // 1003
|
||
|
// 1004
|
||
|
/** // 1005
|
||
|
* @summary Disconnect the client from the server. // 1006
|
||
|
* @locus Client // 1007
|
||
|
* @memberOf Meteor // 1008
|
||
|
*/ // 1009
|
||
|
disconnect: function (/*passthrough args*/) { // 1010
|
||
|
var self = this; // 1011
|
||
|
return self._stream.disconnect.apply(self._stream, arguments); // 1012
|
||
|
}, // 1013
|
||
|
// 1014
|
||
|
close: function () { // 1015
|
||
|
var self = this; // 1016
|
||
|
return self._stream.disconnect({_permanent: true}); // 1017
|
||
|
}, // 1018
|
||
|
// 1019
|
||
|
/// // 1020
|
||
|
/// Reactive user system // 1021
|
||
|
/// // 1022
|
||
|
userId: function () { // 1023
|
||
|
var self = this; // 1024
|
||
|
if (self._userIdDeps) // 1025
|
||
|
self._userIdDeps.depend(); // 1026
|
||
|
return self._userId; // 1027
|
||
|
}, // 1028
|
||
|
// 1029
|
||
|
setUserId: function (userId) { // 1030
|
||
|
var self = this; // 1031
|
||
|
// Avoid invalidating dependents if setUserId is called with current value. // 1032
|
||
|
if (self._userId === userId) // 1033
|
||
|
return; // 1034
|
||
|
self._userId = userId; // 1035
|
||
|
if (self._userIdDeps) // 1036
|
||
|
self._userIdDeps.changed(); // 1037
|
||
|
}, // 1038
|
||
|
// 1039
|
||
|
// Returns true if we are in a state after reconnect of waiting for subs to be // 1040
|
||
|
// revived or early methods to finish their data, or we are waiting for a // 1041
|
||
|
// "wait" method to finish. // 1042
|
||
|
_waitingForQuiescence: function () { // 1043
|
||
|
var self = this; // 1044
|
||
|
return (! _.isEmpty(self._subsBeingRevived) || // 1045
|
||
|
! _.isEmpty(self._methodsBlockingQuiescence)); // 1046
|
||
|
}, // 1047
|
||
|
// 1048
|
||
|
// Returns true if any method whose message has been sent to the server has // 1049
|
||
|
// not yet invoked its user callback. // 1050
|
||
|
_anyMethodsAreOutstanding: function () { // 1051
|
||
|
var self = this; // 1052
|
||
|
return _.any(_.pluck(self._methodInvokers, 'sentMessage')); // 1053
|
||
|
}, // 1054
|
||
|
// 1055
|
||
|
_livedata_connected: function (msg) { // 1056
|
||
|
var self = this; // 1057
|
||
|
// 1058
|
||
|
if (self._version !== 'pre1' && self._heartbeatInterval !== 0) { // 1059
|
||
|
self._heartbeat = new Heartbeat({ // 1060
|
||
|
heartbeatInterval: self._heartbeatInterval, // 1061
|
||
|
heartbeatTimeout: self._heartbeatTimeout, // 1062
|
||
|
onTimeout: function () { // 1063
|
||
|
self._lostConnection( // 1064
|
||
|
new DDP.ConnectionError("DDP heartbeat timed out")); // 1065
|
||
|
}, // 1066
|
||
|
sendPing: function () { // 1067
|
||
|
self._send({msg: 'ping'}); // 1068
|
||
|
} // 1069
|
||
|
}); // 1070
|
||
|
self._heartbeat.start(); // 1071
|
||
|
} // 1072
|
||
|
// 1073
|
||
|
// If this is a reconnect, we'll have to reset all stores. // 1074
|
||
|
if (self._lastSessionId) // 1075
|
||
|
self._resetStores = true; // 1076
|
||
|
// 1077
|
||
|
if (typeof (msg.session) === "string") { // 1078
|
||
|
var reconnectedToPreviousSession = (self._lastSessionId === msg.session); // 1079
|
||
|
self._lastSessionId = msg.session; // 1080
|
||
|
} // 1081
|
||
|
// 1082
|
||
|
if (reconnectedToPreviousSession) { // 1083
|
||
|
// Successful reconnection -- pick up where we left off. Note that right // 1084
|
||
|
// now, this never happens: the server never connects us to a previous // 1085
|
||
|
// session, because DDP doesn't provide enough data for the server to know // 1086
|
||
|
// what messages the client has processed. We need to improve DDP to make // 1087
|
||
|
// this possible, at which point we'll probably need more code here. // 1088
|
||
|
return; // 1089
|
||
|
} // 1090
|
||
|
// 1091
|
||
|
// Server doesn't have our data any more. Re-sync a new session. // 1092
|
||
|
// 1093
|
||
|
// Forget about messages we were buffering for unknown collections. They'll // 1094
|
||
|
// be resent if still relevant. // 1095
|
||
|
self._updatesForUnknownStores = {}; // 1096
|
||
|
// 1097
|
||
|
if (self._resetStores) { // 1098
|
||
|
// Forget about the effects of stubs. We'll be resetting all collections // 1099
|
||
|
// anyway. // 1100
|
||
|
self._documentsWrittenByStub = {}; // 1101
|
||
|
self._serverDocuments = {}; // 1102
|
||
|
} // 1103
|
||
|
// 1104
|
||
|
// Clear _afterUpdateCallbacks. // 1105
|
||
|
self._afterUpdateCallbacks = []; // 1106
|
||
|
// 1107
|
||
|
// Mark all named subscriptions which are ready (ie, we already called the // 1108
|
||
|
// ready callback) as needing to be revived. // 1109
|
||
|
// XXX We should also block reconnect quiescence until unnamed subscriptions // 1110
|
||
|
// (eg, autopublish) are done re-publishing to avoid flicker! // 1111
|
||
|
self._subsBeingRevived = {}; // 1112
|
||
|
_.each(self._subscriptions, function (sub, id) { // 1113
|
||
|
if (sub.ready) // 1114
|
||
|
self._subsBeingRevived[id] = true; // 1115
|
||
|
}); // 1116
|
||
|
// 1117
|
||
|
// Arrange for "half-finished" methods to have their callbacks run, and // 1118
|
||
|
// track methods that were sent on this connection so that we don't // 1119
|
||
|
// quiesce until they are all done. // 1120
|
||
|
// // 1121
|
||
|
// Start by clearing _methodsBlockingQuiescence: methods sent before // 1122
|
||
|
// reconnect don't matter, and any "wait" methods sent on the new connection // 1123
|
||
|
// that we drop here will be restored by the loop below. // 1124
|
||
|
self._methodsBlockingQuiescence = {}; // 1125
|
||
|
if (self._resetStores) { // 1126
|
||
|
_.each(self._methodInvokers, function (invoker) { // 1127
|
||
|
if (invoker.gotResult()) { // 1128
|
||
|
// This method already got its result, but it didn't call its callback // 1129
|
||
|
// because its data didn't become visible. We did not resend the // 1130
|
||
|
// method RPC. We'll call its callback when we get a full quiesce, // 1131
|
||
|
// since that's as close as we'll get to "data must be visible". // 1132
|
||
|
self._afterUpdateCallbacks.push(_.bind(invoker.dataVisible, invoker)); // 1133
|
||
|
} else if (invoker.sentMessage) { // 1134
|
||
|
// This method has been sent on this connection (maybe as a resend // 1135
|
||
|
// from the last connection, maybe from onReconnect, maybe just very // 1136
|
||
|
// quickly before processing the connected message). // 1137
|
||
|
// // 1138
|
||
|
// We don't need to do anything special to ensure its callbacks get // 1139
|
||
|
// called, but we'll count it as a method which is preventing // 1140
|
||
|
// reconnect quiescence. (eg, it might be a login method that was run // 1141
|
||
|
// from onReconnect, and we don't want to see flicker by seeing a // 1142
|
||
|
// logged-out state.) // 1143
|
||
|
self._methodsBlockingQuiescence[invoker.methodId] = true; // 1144
|
||
|
} // 1145
|
||
|
}); // 1146
|
||
|
} // 1147
|
||
|
// 1148
|
||
|
self._messagesBufferedUntilQuiescence = []; // 1149
|
||
|
// 1150
|
||
|
// If we're not waiting on any methods or subs, we can reset the stores and // 1151
|
||
|
// call the callbacks immediately. // 1152
|
||
|
if (!self._waitingForQuiescence()) { // 1153
|
||
|
if (self._resetStores) { // 1154
|
||
|
_.each(self._stores, function (s) { // 1155
|
||
|
s.beginUpdate(0, true); // 1156
|
||
|
s.endUpdate(); // 1157
|
||
|
}); // 1158
|
||
|
self._resetStores = false; // 1159
|
||
|
} // 1160
|
||
|
self._runAfterUpdateCallbacks(); // 1161
|
||
|
} // 1162
|
||
|
}, // 1163
|
||
|
// 1164
|
||
|
// 1165
|
||
|
_processOneDataMessage: function (msg, updates) { // 1166
|
||
|
var self = this; // 1167
|
||
|
// Using underscore here so as not to need to capitalize. // 1168
|
||
|
self['_process_' + msg.msg](msg, updates); // 1169
|
||
|
}, // 1170
|
||
|
// 1171
|
||
|
// 1172
|
||
|
_livedata_data: function (msg) { // 1173
|
||
|
var self = this; // 1174
|
||
|
// 1175
|
||
|
// collection name -> array of messages // 1176
|
||
|
var updates = {}; // 1177
|
||
|
// 1178
|
||
|
if (self._waitingForQuiescence()) { // 1179
|
||
|
self._messagesBufferedUntilQuiescence.push(msg); // 1180
|
||
|
// 1181
|
||
|
if (msg.msg === "nosub") // 1182
|
||
|
delete self._subsBeingRevived[msg.id]; // 1183
|
||
|
// 1184
|
||
|
_.each(msg.subs || [], function (subId) { // 1185
|
||
|
delete self._subsBeingRevived[subId]; // 1186
|
||
|
}); // 1187
|
||
|
_.each(msg.methods || [], function (methodId) { // 1188
|
||
|
delete self._methodsBlockingQuiescence[methodId]; // 1189
|
||
|
}); // 1190
|
||
|
// 1191
|
||
|
if (self._waitingForQuiescence()) // 1192
|
||
|
return; // 1193
|
||
|
// 1194
|
||
|
// No methods or subs are blocking quiescence! // 1195
|
||
|
// We'll now process and all of our buffered messages, reset all stores, // 1196
|
||
|
// and apply them all at once. // 1197
|
||
|
_.each(self._messagesBufferedUntilQuiescence, function (bufferedMsg) { // 1198
|
||
|
self._processOneDataMessage(bufferedMsg, updates); // 1199
|
||
|
}); // 1200
|
||
|
self._messagesBufferedUntilQuiescence = []; // 1201
|
||
|
} else { // 1202
|
||
|
self._processOneDataMessage(msg, updates); // 1203
|
||
|
} // 1204
|
||
|
// 1205
|
||
|
if (self._resetStores || !_.isEmpty(updates)) { // 1206
|
||
|
// Begin a transactional update of each store. // 1207
|
||
|
_.each(self._stores, function (s, storeName) { // 1208
|
||
|
s.beginUpdate(_.has(updates, storeName) ? updates[storeName].length : 0, // 1209
|
||
|
self._resetStores); // 1210
|
||
|
}); // 1211
|
||
|
self._resetStores = false; // 1212
|
||
|
// 1213
|
||
|
_.each(updates, function (updateMessages, storeName) { // 1214
|
||
|
var store = self._stores[storeName]; // 1215
|
||
|
if (store) { // 1216
|
||
|
_.each(updateMessages, function (updateMessage) { // 1217
|
||
|
store.update(updateMessage); // 1218
|
||
|
}); // 1219
|
||
|
} else { // 1220
|
||
|
// Nobody's listening for this data. Queue it up until // 1221
|
||
|
// someone wants it. // 1222
|
||
|
// XXX memory use will grow without bound if you forget to // 1223
|
||
|
// create a collection or just don't care about it... going // 1224
|
||
|
// to have to do something about that. // 1225
|
||
|
if (!_.has(self._updatesForUnknownStores, storeName)) // 1226
|
||
|
self._updatesForUnknownStores[storeName] = []; // 1227
|
||
|
Array.prototype.push.apply(self._updatesForUnknownStores[storeName], // 1228
|
||
|
updateMessages); // 1229
|
||
|
} // 1230
|
||
|
}); // 1231
|
||
|
// 1232
|
||
|
// End update transaction. // 1233
|
||
|
_.each(self._stores, function (s) { s.endUpdate(); }); // 1234
|
||
|
} // 1235
|
||
|
// 1236
|
||
|
self._runAfterUpdateCallbacks(); // 1237
|
||
|
}, // 1238
|
||
|
// 1239
|
||
|
// Call any callbacks deferred with _runWhenAllServerDocsAreFlushed whose // 1240
|
||
|
// relevant docs have been flushed, as well as dataVisible callbacks at // 1241
|
||
|
// reconnect-quiescence time. // 1242
|
||
|
_runAfterUpdateCallbacks: function () { // 1243
|
||
|
var self = this; // 1244
|
||
|
var callbacks = self._afterUpdateCallbacks; // 1245
|
||
|
self._afterUpdateCallbacks = []; // 1246
|
||
|
_.each(callbacks, function (c) { // 1247
|
||
|
c(); // 1248
|
||
|
}); // 1249
|
||
|
}, // 1250
|
||
|
// 1251
|
||
|
_pushUpdate: function (updates, collection, msg) { // 1252
|
||
|
var self = this; // 1253
|
||
|
if (!_.has(updates, collection)) { // 1254
|
||
|
updates[collection] = []; // 1255
|
||
|
} // 1256
|
||
|
updates[collection].push(msg); // 1257
|
||
|
}, // 1258
|
||
|
// 1259
|
||
|
_getServerDoc: function (collection, id) { // 1260
|
||
|
var self = this; // 1261
|
||
|
if (!_.has(self._serverDocuments, collection)) // 1262
|
||
|
return null; // 1263
|
||
|
var serverDocsForCollection = self._serverDocuments[collection]; // 1264
|
||
|
return serverDocsForCollection.get(id) || null; // 1265
|
||
|
}, // 1266
|
||
|
// 1267
|
||
|
_process_added: function (msg, updates) { // 1268
|
||
|
var self = this; // 1269
|
||
|
var id = LocalCollection._idParse(msg.id); // 1270
|
||
|
var serverDoc = self._getServerDoc(msg.collection, id); // 1271
|
||
|
if (serverDoc) { // 1272
|
||
|
// Some outstanding stub wrote here. // 1273
|
||
|
if (serverDoc.document !== undefined) // 1274
|
||
|
throw new Error("Server sent add for existing id: " + msg.id); // 1275
|
||
|
serverDoc.document = msg.fields || {}; // 1276
|
||
|
serverDoc.document._id = id; // 1277
|
||
|
} else { // 1278
|
||
|
self._pushUpdate(updates, msg.collection, msg); // 1279
|
||
|
} // 1280
|
||
|
}, // 1281
|
||
|
// 1282
|
||
|
_process_changed: function (msg, updates) { // 1283
|
||
|
var self = this; // 1284
|
||
|
var serverDoc = self._getServerDoc( // 1285
|
||
|
msg.collection, LocalCollection._idParse(msg.id)); // 1286
|
||
|
if (serverDoc) { // 1287
|
||
|
if (serverDoc.document === undefined) // 1288
|
||
|
throw new Error("Server sent changed for nonexisting id: " + msg.id); // 1289
|
||
|
LocalCollection._applyChanges(serverDoc.document, msg.fields); // 1290
|
||
|
} else { // 1291
|
||
|
self._pushUpdate(updates, msg.collection, msg); // 1292
|
||
|
} // 1293
|
||
|
}, // 1294
|
||
|
// 1295
|
||
|
_process_removed: function (msg, updates) { // 1296
|
||
|
var self = this; // 1297
|
||
|
var serverDoc = self._getServerDoc( // 1298
|
||
|
msg.collection, LocalCollection._idParse(msg.id)); // 1299
|
||
|
if (serverDoc) { // 1300
|
||
|
// Some outstanding stub wrote here. // 1301
|
||
|
if (serverDoc.document === undefined) // 1302
|
||
|
throw new Error("Server sent removed for nonexisting id:" + msg.id); // 1303
|
||
|
serverDoc.document = undefined; // 1304
|
||
|
} else { // 1305
|
||
|
self._pushUpdate(updates, msg.collection, { // 1306
|
||
|
msg: 'removed', // 1307
|
||
|
collection: msg.collection, // 1308
|
||
|
id: msg.id // 1309
|
||
|
}); // 1310
|
||
|
} // 1311
|
||
|
}, // 1312
|
||
|
// 1313
|
||
|
_process_updated: function (msg, updates) { // 1314
|
||
|
var self = this; // 1315
|
||
|
// Process "method done" messages. // 1316
|
||
|
_.each(msg.methods, function (methodId) { // 1317
|
||
|
_.each(self._documentsWrittenByStub[methodId], function (written) { // 1318
|
||
|
var serverDoc = self._getServerDoc(written.collection, written.id); // 1319
|
||
|
if (!serverDoc) // 1320
|
||
|
throw new Error("Lost serverDoc for " + JSON.stringify(written)); // 1321
|
||
|
if (!serverDoc.writtenByStubs[methodId]) // 1322
|
||
|
throw new Error("Doc " + JSON.stringify(written) + // 1323
|
||
|
" not written by method " + methodId); // 1324
|
||
|
delete serverDoc.writtenByStubs[methodId]; // 1325
|
||
|
if (_.isEmpty(serverDoc.writtenByStubs)) { // 1326
|
||
|
// All methods whose stubs wrote this method have completed! We can // 1327
|
||
|
// now copy the saved document to the database (reverting the stub's // 1328
|
||
|
// change if the server did not write to this object, or applying the // 1329
|
||
|
// server's writes if it did). // 1330
|
||
|
// 1331
|
||
|
// This is a fake ddp 'replace' message. It's just for talking // 1332
|
||
|
// between livedata connections and minimongo. (We have to stringify // 1333
|
||
|
// the ID because it's supposed to look like a wire message.) // 1334
|
||
|
self._pushUpdate(updates, written.collection, { // 1335
|
||
|
msg: 'replace', // 1336
|
||
|
id: LocalCollection._idStringify(written.id), // 1337
|
||
|
replace: serverDoc.document // 1338
|
||
|
}); // 1339
|
||
|
// Call all flush callbacks. // 1340
|
||
|
_.each(serverDoc.flushCallbacks, function (c) { // 1341
|
||
|
c(); // 1342
|
||
|
}); // 1343
|
||
|
// 1344
|
||
|
// Delete this completed serverDocument. Don't bother to GC empty // 1345
|
||
|
// IdMaps inside self._serverDocuments, since there probably aren't // 1346
|
||
|
// many collections and they'll be written repeatedly. // 1347
|
||
|
self._serverDocuments[written.collection].remove(written.id); // 1348
|
||
|
} // 1349
|
||
|
}); // 1350
|
||
|
delete self._documentsWrittenByStub[methodId]; // 1351
|
||
|
// 1352
|
||
|
// We want to call the data-written callback, but we can't do so until all // 1353
|
||
|
// currently buffered messages are flushed. // 1354
|
||
|
var callbackInvoker = self._methodInvokers[methodId]; // 1355
|
||
|
if (!callbackInvoker) // 1356
|
||
|
throw new Error("No callback invoker for method " + methodId); // 1357
|
||
|
self._runWhenAllServerDocsAreFlushed( // 1358
|
||
|
_.bind(callbackInvoker.dataVisible, callbackInvoker)); // 1359
|
||
|
}); // 1360
|
||
|
}, // 1361
|
||
|
// 1362
|
||
|
_process_ready: function (msg, updates) { // 1363
|
||
|
var self = this; // 1364
|
||
|
// Process "sub ready" messages. "sub ready" messages don't take effect // 1365
|
||
|
// until all current server documents have been flushed to the local // 1366
|
||
|
// database. We can use a write fence to implement this. // 1367
|
||
|
_.each(msg.subs, function (subId) { // 1368
|
||
|
self._runWhenAllServerDocsAreFlushed(function () { // 1369
|
||
|
var subRecord = self._subscriptions[subId]; // 1370
|
||
|
// Did we already unsubscribe? // 1371
|
||
|
if (!subRecord) // 1372
|
||
|
return; // 1373
|
||
|
// Did we already receive a ready message? (Oops!) // 1374
|
||
|
if (subRecord.ready) // 1375
|
||
|
return; // 1376
|
||
|
subRecord.readyCallback && subRecord.readyCallback(); // 1377
|
||
|
subRecord.ready = true; // 1378
|
||
|
subRecord.readyDeps.changed(); // 1379
|
||
|
}); // 1380
|
||
|
}); // 1381
|
||
|
}, // 1382
|
||
|
// 1383
|
||
|
// Ensures that "f" will be called after all documents currently in // 1384
|
||
|
// _serverDocuments have been written to the local cache. f will not be called // 1385
|
||
|
// if the connection is lost before then! // 1386
|
||
|
_runWhenAllServerDocsAreFlushed: function (f) { // 1387
|
||
|
var self = this; // 1388
|
||
|
var runFAfterUpdates = function () { // 1389
|
||
|
self._afterUpdateCallbacks.push(f); // 1390
|
||
|
}; // 1391
|
||
|
var unflushedServerDocCount = 0; // 1392
|
||
|
var onServerDocFlush = function () { // 1393
|
||
|
--unflushedServerDocCount; // 1394
|
||
|
if (unflushedServerDocCount === 0) { // 1395
|
||
|
// This was the last doc to flush! Arrange to run f after the updates // 1396
|
||
|
// have been applied. // 1397
|
||
|
runFAfterUpdates(); // 1398
|
||
|
} // 1399
|
||
|
}; // 1400
|
||
|
_.each(self._serverDocuments, function (collectionDocs) { // 1401
|
||
|
collectionDocs.forEach(function (serverDoc) { // 1402
|
||
|
var writtenByStubForAMethodWithSentMessage = _.any( // 1403
|
||
|
serverDoc.writtenByStubs, function (dummy, methodId) { // 1404
|
||
|
var invoker = self._methodInvokers[methodId]; // 1405
|
||
|
return invoker && invoker.sentMessage; // 1406
|
||
|
}); // 1407
|
||
|
if (writtenByStubForAMethodWithSentMessage) { // 1408
|
||
|
++unflushedServerDocCount; // 1409
|
||
|
serverDoc.flushCallbacks.push(onServerDocFlush); // 1410
|
||
|
} // 1411
|
||
|
}); // 1412
|
||
|
}); // 1413
|
||
|
if (unflushedServerDocCount === 0) { // 1414
|
||
|
// There aren't any buffered docs --- we can call f as soon as the current // 1415
|
||
|
// round of updates is applied! // 1416
|
||
|
runFAfterUpdates(); // 1417
|
||
|
} // 1418
|
||
|
}, // 1419
|
||
|
// 1420
|
||
|
_livedata_nosub: function (msg) { // 1421
|
||
|
var self = this; // 1422
|
||
|
// 1423
|
||
|
// First pass it through _livedata_data, which only uses it to help get // 1424
|
||
|
// towards quiescence. // 1425
|
||
|
self._livedata_data(msg); // 1426
|
||
|
// 1427
|
||
|
// Do the rest of our processing immediately, with no // 1428
|
||
|
// buffering-until-quiescence. // 1429
|
||
|
// 1430
|
||
|
// we weren't subbed anyway, or we initiated the unsub. // 1431
|
||
|
if (!_.has(self._subscriptions, msg.id)) // 1432
|
||
|
return; // 1433
|
||
|
// 1434
|
||
|
// XXX COMPAT WITH 1.0.3.1 #errorCallback // 1435
|
||
|
var errorCallback = self._subscriptions[msg.id].errorCallback; // 1436
|
||
|
var stopCallback = self._subscriptions[msg.id].stopCallback; // 1437
|
||
|
// 1438
|
||
|
self._subscriptions[msg.id].remove(); // 1439
|
||
|
// 1440
|
||
|
var meteorErrorFromMsg = function (msgArg) { // 1441
|
||
|
return msgArg && msgArg.error && new Meteor.Error( // 1442
|
||
|
msgArg.error.error, msgArg.error.reason, msgArg.error.details); // 1443
|
||
|
} // 1444
|
||
|
// 1445
|
||
|
// XXX COMPAT WITH 1.0.3.1 #errorCallback // 1446
|
||
|
if (errorCallback && msg.error) { // 1447
|
||
|
errorCallback(meteorErrorFromMsg(msg)); // 1448
|
||
|
} // 1449
|
||
|
// 1450
|
||
|
if (stopCallback) { // 1451
|
||
|
stopCallback(meteorErrorFromMsg(msg)); // 1452
|
||
|
} // 1453
|
||
|
}, // 1454
|
||
|
// 1455
|
||
|
_process_nosub: function () { // 1456
|
||
|
// This is called as part of the "buffer until quiescence" process, but // 1457
|
||
|
// nosub's effect is always immediate. It only goes in the buffer at all // 1458
|
||
|
// because it's possible for a nosub to be the thing that triggers // 1459
|
||
|
// quiescence, if we were waiting for a sub to be revived and it dies // 1460
|
||
|
// instead. // 1461
|
||
|
}, // 1462
|
||
|
// 1463
|
||
|
_livedata_result: function (msg) { // 1464
|
||
|
// id, result or error. error has error (code), reason, details // 1465
|
||
|
// 1466
|
||
|
var self = this; // 1467
|
||
|
// 1468
|
||
|
// find the outstanding request // 1469
|
||
|
// should be O(1) in nearly all realistic use cases // 1470
|
||
|
if (_.isEmpty(self._outstandingMethodBlocks)) { // 1471
|
||
|
Meteor._debug("Received method result but no methods outstanding"); // 1472
|
||
|
return; // 1473
|
||
|
} // 1474
|
||
|
var currentMethodBlock = self._outstandingMethodBlocks[0].methods; // 1475
|
||
|
var m; // 1476
|
||
|
for (var i = 0; i < currentMethodBlock.length; i++) { // 1477
|
||
|
m = currentMethodBlock[i]; // 1478
|
||
|
if (m.methodId === msg.id) // 1479
|
||
|
break; // 1480
|
||
|
} // 1481
|
||
|
// 1482
|
||
|
if (!m) { // 1483
|
||
|
Meteor._debug("Can't match method response to original method call", msg); // 1484
|
||
|
return; // 1485
|
||
|
} // 1486
|
||
|
// 1487
|
||
|
// Remove from current method block. This may leave the block empty, but we // 1488
|
||
|
// don't move on to the next block until the callback has been delivered, in // 1489
|
||
|
// _outstandingMethodFinished. // 1490
|
||
|
currentMethodBlock.splice(i, 1); // 1491
|
||
|
// 1492
|
||
|
if (_.has(msg, 'error')) { // 1493
|
||
|
m.receiveResult(new Meteor.Error( // 1494
|
||
|
msg.error.error, msg.error.reason, // 1495
|
||
|
msg.error.details)); // 1496
|
||
|
} else { // 1497
|
||
|
// msg.result may be undefined if the method didn't return a // 1498
|
||
|
// value // 1499
|
||
|
m.receiveResult(undefined, msg.result); // 1500
|
||
|
} // 1501
|
||
|
}, // 1502
|
||
|
// 1503
|
||
|
// Called by MethodInvoker after a method's callback is invoked. If this was // 1504
|
||
|
// the last outstanding method in the current block, runs the next block. If // 1505
|
||
|
// there are no more methods, consider accepting a hot code push. // 1506
|
||
|
_outstandingMethodFinished: function () { // 1507
|
||
|
var self = this; // 1508
|
||
|
if (self._anyMethodsAreOutstanding()) // 1509
|
||
|
return; // 1510
|
||
|
// 1511
|
||
|
// No methods are outstanding. This should mean that the first block of // 1512
|
||
|
// methods is empty. (Or it might not exist, if this was a method that // 1513
|
||
|
// half-finished before disconnect/reconnect.) // 1514
|
||
|
if (! _.isEmpty(self._outstandingMethodBlocks)) { // 1515
|
||
|
var firstBlock = self._outstandingMethodBlocks.shift(); // 1516
|
||
|
if (! _.isEmpty(firstBlock.methods)) // 1517
|
||
|
throw new Error("No methods outstanding but nonempty block: " + // 1518
|
||
|
JSON.stringify(firstBlock)); // 1519
|
||
|
// 1520
|
||
|
// Send the outstanding methods now in the first block. // 1521
|
||
|
if (!_.isEmpty(self._outstandingMethodBlocks)) // 1522
|
||
|
self._sendOutstandingMethods(); // 1523
|
||
|
} // 1524
|
||
|
// 1525
|
||
|
// Maybe accept a hot code push. // 1526
|
||
|
self._maybeMigrate(); // 1527
|
||
|
}, // 1528
|
||
|
// 1529
|
||
|
// Sends messages for all the methods in the first block in // 1530
|
||
|
// _outstandingMethodBlocks. // 1531
|
||
|
_sendOutstandingMethods: function() { // 1532
|
||
|
var self = this; // 1533
|
||
|
if (_.isEmpty(self._outstandingMethodBlocks)) // 1534
|
||
|
return; // 1535
|
||
|
_.each(self._outstandingMethodBlocks[0].methods, function (m) { // 1536
|
||
|
m.sendMessage(); // 1537
|
||
|
}); // 1538
|
||
|
}, // 1539
|
||
|
// 1540
|
||
|
_livedata_error: function (msg) { // 1541
|
||
|
Meteor._debug("Received error from server: ", msg.reason); // 1542
|
||
|
if (msg.offendingMessage) // 1543
|
||
|
Meteor._debug("For: ", msg.offendingMessage); // 1544
|
||
|
}, // 1545
|
||
|
// 1546
|
||
|
_callOnReconnectAndSendAppropriateOutstandingMethods: function() { // 1547
|
||
|
var self = this; // 1548
|
||
|
var oldOutstandingMethodBlocks = self._outstandingMethodBlocks; // 1549
|
||
|
self._outstandingMethodBlocks = []; // 1550
|
||
|
// 1551
|
||
|
self.onReconnect(); // 1552
|
||
|
// 1553
|
||
|
if (_.isEmpty(oldOutstandingMethodBlocks)) // 1554
|
||
|
return; // 1555
|
||
|
// 1556
|
||
|
// We have at least one block worth of old outstanding methods to try // 1557
|
||
|
// again. First: did onReconnect actually send anything? If not, we just // 1558
|
||
|
// restore all outstanding methods and run the first block. // 1559
|
||
|
if (_.isEmpty(self._outstandingMethodBlocks)) { // 1560
|
||
|
self._outstandingMethodBlocks = oldOutstandingMethodBlocks; // 1561
|
||
|
self._sendOutstandingMethods(); // 1562
|
||
|
return; // 1563
|
||
|
} // 1564
|
||
|
// 1565
|
||
|
// OK, there are blocks on both sides. Special case: merge the last block of // 1566
|
||
|
// the reconnect methods with the first block of the original methods, if // 1567
|
||
|
// neither of them are "wait" blocks. // 1568
|
||
|
if (!_.last(self._outstandingMethodBlocks).wait && // 1569
|
||
|
!oldOutstandingMethodBlocks[0].wait) { // 1570
|
||
|
_.each(oldOutstandingMethodBlocks[0].methods, function (m) { // 1571
|
||
|
_.last(self._outstandingMethodBlocks).methods.push(m); // 1572
|
||
|
// 1573
|
||
|
// If this "last block" is also the first block, send the message. // 1574
|
||
|
if (self._outstandingMethodBlocks.length === 1) // 1575
|
||
|
m.sendMessage(); // 1576
|
||
|
}); // 1577
|
||
|
// 1578
|
||
|
oldOutstandingMethodBlocks.shift(); // 1579
|
||
|
} // 1580
|
||
|
// 1581
|
||
|
// Now add the rest of the original blocks on. // 1582
|
||
|
_.each(oldOutstandingMethodBlocks, function (block) { // 1583
|
||
|
self._outstandingMethodBlocks.push(block); // 1584
|
||
|
}); // 1585
|
||
|
}, // 1586
|
||
|
// 1587
|
||
|
// We can accept a hot code push if there are no methods in flight. // 1588
|
||
|
_readyToMigrate: function() { // 1589
|
||
|
var self = this; // 1590
|
||
|
return _.isEmpty(self._methodInvokers); // 1591
|
||
|
}, // 1592
|
||
|
// 1593
|
||
|
// If we were blocking a migration, see if it's now possible to continue. // 1594
|
||
|
// Call whenever the set of outstanding/blocked methods shrinks. // 1595
|
||
|
_maybeMigrate: function () { // 1596
|
||
|
var self = this; // 1597
|
||
|
if (self._retryMigrate && self._readyToMigrate()) { // 1598
|
||
|
self._retryMigrate(); // 1599
|
||
|
self._retryMigrate = null; // 1600
|
||
|
} // 1601
|
||
|
} // 1602
|
||
|
}); // 1603
|
||
|
// 1604
|
||
|
LivedataTest.Connection = Connection; // 1605
|
||
|
// 1606
|
||
|
// @param url {String} URL to Meteor app, // 1607
|
||
|
// e.g.: // 1608
|
||
|
// "subdomain.meteor.com", // 1609
|
||
|
// "http://subdomain.meteor.com", // 1610
|
||
|
// "/", // 1611
|
||
|
// "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" // 1612
|
||
|
// 1613
|
||
|
/** // 1614
|
||
|
* @summary Connect to the server of a different Meteor application to subscribe to its document sets and invoke its remote methods.
|
||
|
* @locus Anywhere // 1616
|
||
|
* @param {String} url The URL of another Meteor application. // 1617
|
||
|
*/ // 1618
|
||
|
DDP.connect = function (url, options) { // 1619
|
||
|
var ret = new Connection(url, options); // 1620
|
||
|
allConnections.push(ret); // hack. see below. // 1621
|
||
|
return ret; // 1622
|
||
|
}; // 1623
|
||
|
// 1624
|
||
|
// Hack for `spiderable` package: a way to see if the page is done // 1625
|
||
|
// loading all the data it needs. // 1626
|
||
|
// // 1627
|
||
|
allConnections = []; // 1628
|
||
|
DDP._allSubscriptionsReady = function () { // 1629
|
||
|
return _.all(allConnections, function (conn) { // 1630
|
||
|
return _.all(conn._subscriptions, function (sub) { // 1631
|
||
|
return sub.ready; // 1632
|
||
|
}); // 1633
|
||
|
}); // 1634
|
||
|
}; // 1635
|
||
|
// 1636
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ddp/client_convenience.js //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Meteor.refresh can be called on the client (if you're in common code) but it // 1
|
||
|
// only has an effect on the server. // 2
|
||
|
Meteor.refresh = function (notification) { // 3
|
||
|
}; // 4
|
||
|
// 5
|
||
|
if (Meteor.isClient) { // 6
|
||
|
// By default, try to connect back to the same endpoint as the page // 7
|
||
|
// was served from. // 8
|
||
|
// // 9
|
||
|
// XXX We should be doing this a different way. Right now we don't // 10
|
||
|
// include ROOT_URL_PATH_PREFIX when computing ddpUrl. (We don't // 11
|
||
|
// include it on the server when computing // 12
|
||
|
// DDP_DEFAULT_CONNECTION_URL, and we don't include it in our // 13
|
||
|
// default, '/'.) We get by with this because DDP.connect then // 14
|
||
|
// forces the URL passed to it to be interpreted relative to the // 15
|
||
|
// app's deploy path, even if it is absolute. Instead, we should // 16
|
||
|
// make DDP_DEFAULT_CONNECTION_URL, if set, include the path prefix; // 17
|
||
|
// make the default ddpUrl be '' rather that '/'; and make // 18
|
||
|
// _translateUrl in stream_client_common.js not force absolute paths // 19
|
||
|
// to be treated like relative paths. See also // 20
|
||
|
// stream_client_common.js #RationalizingRelativeDDPURLs // 21
|
||
|
var ddpUrl = '/'; // 22
|
||
|
if (typeof __meteor_runtime_config__ !== "undefined") { // 23
|
||
|
if (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL) // 24
|
||
|
ddpUrl = __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL; // 25
|
||
|
} // 26
|
||
|
// 27
|
||
|
var retry = new Retry(); // 28
|
||
|
// 29
|
||
|
var onDDPVersionNegotiationFailure = function (description) { // 30
|
||
|
Meteor._debug(description); // 31
|
||
|
if (Package.reload) { // 32
|
||
|
var migrationData = Package.reload.Reload._migrationData('livedata') || {}; // 33
|
||
|
var failures = migrationData.DDPVersionNegotiationFailures || 0; // 34
|
||
|
++failures; // 35
|
||
|
Package.reload.Reload._onMigrate('livedata', function () { // 36
|
||
|
return [true, {DDPVersionNegotiationFailures: failures}]; // 37
|
||
|
}); // 38
|
||
|
retry.retryLater(failures, function () { // 39
|
||
|
Package.reload.Reload._reload(); // 40
|
||
|
}); // 41
|
||
|
} // 42
|
||
|
}; // 43
|
||
|
// 44
|
||
|
Meteor.connection = // 45
|
||
|
DDP.connect(ddpUrl, { // 46
|
||
|
onDDPVersionNegotiationFailure: onDDPVersionNegotiationFailure // 47
|
||
|
}); // 48
|
||
|
// 49
|
||
|
// Proxy the public methods of Meteor.connection so they can // 50
|
||
|
// be called directly on Meteor. // 51
|
||
|
_.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', // 52
|
||
|
'disconnect'], // 53
|
||
|
function (name) { // 54
|
||
|
Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection); // 55
|
||
|
}); // 56
|
||
|
} else { // 57
|
||
|
// Never set up a default connection on the server. Don't even map // 58
|
||
|
// subscribe/call/etc onto Meteor. // 59
|
||
|
Meteor.connection = null; // 60
|
||
|
} // 61
|
||
|
// 62
|
||
|
// Meteor.connection used to be called // 63
|
||
|
// Meteor.default_connection. Provide backcompat as a courtesy even // 64
|
||
|
// though it was never documented. // 65
|
||
|
// XXX COMPAT WITH 0.6.4 // 66
|
||
|
Meteor.default_connection = Meteor.connection; // 67
|
||
|
// 68
|
||
|
// We should transition from Meteor.connect to DDP.connect. // 69
|
||
|
// XXX COMPAT WITH 0.6.4 // 70
|
||
|
Meteor.connect = DDP.connect; // 71
|
||
|
// 72
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
/* Exports */
|
||
|
if (typeof Package === 'undefined') Package = {};
|
||
|
Package.ddp = {
|
||
|
DDP: DDP,
|
||
|
LivedataTest: LivedataTest
|
||
|
};
|
||
|
|
||
|
})();
|