mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Cleaning up duplicated code
This commit is contained in:
@@ -16,7 +16,7 @@ Package.on_use(function (api) {
|
||||
api.add_files(['sockjs-0.3.4.js',
|
||||
'stream_client_sockjs.js'], 'client');
|
||||
api.add_files('stream_client_nodejs.js', 'server');
|
||||
api.add_files('urls.js', ['client', 'server']);
|
||||
api.add_files('stream_client_common.js', ['client', 'server']);
|
||||
api.add_files('stream_server.js', 'server');
|
||||
|
||||
// livedata_connection.js uses a Minimongo collection internally to
|
||||
|
||||
249
packages/livedata/stream_client_common.js
Normal file
249
packages/livedata/stream_client_common.js
Normal file
@@ -0,0 +1,249 @@
|
||||
|
||||
// XXX from Underscore.String (http://epeli.github.com/underscore.string/)
|
||||
var startsWith = function(str, starts) {
|
||||
return str.length >= starts.length &&
|
||||
str.substring(0, starts.length) === starts;
|
||||
};
|
||||
var endsWith = function(str, ends) {
|
||||
return str.length >= ends.length &&
|
||||
str.substring(str.length - ends.length) === ends;
|
||||
};
|
||||
|
||||
// @param url {String} URL to Meteor app, eg:
|
||||
// "/" or "madewith.meteor.com" or "https://foo.meteor.com"
|
||||
// or "ddp+sockjs://ddp--****-foo.meteor.com/sockjs"
|
||||
// @returns {String} URL to the endpoint with the specific scheme and subPath, e.g.
|
||||
// for scheme "http" and subPath "sockjs"
|
||||
// "http://subdomain.meteor.com/sockjs" or "/sockjs"
|
||||
// or "https://ddp--1234-foo.meteor.com/sockjs"
|
||||
var translateUrl = function(url, newSchemeBase, subPath) {
|
||||
if (! newSchemeBase) {
|
||||
newSchemeBase = "http";
|
||||
}
|
||||
|
||||
var ddpUrlMatch = url.match(/^ddp(i?)\+sockjs:\/\//);
|
||||
var httpUrlMatch = url.match(/^http(s?):\/\//);
|
||||
var newScheme;
|
||||
if (ddpUrlMatch) {
|
||||
// Remove scheme and split off the host.
|
||||
var urlAfterDDP = url.substr(ddpUrlMatch[0].length);
|
||||
newScheme = ddpUrlMatch[1] === "i" ? newSchemeBase : newSchemeBase + "s";
|
||||
var slashPos = urlAfterDDP.indexOf('/');
|
||||
var host =
|
||||
slashPos === -1 ? urlAfterDDP : urlAfterDDP.substr(0, slashPos);
|
||||
var rest = slashPos === -1 ? '' : urlAfterDDP.substr(slashPos);
|
||||
|
||||
// In the host (ONLY!), change '*' characters into random digits. This
|
||||
// allows different stream connections to connect to different hostnames
|
||||
// and avoid browser per-hostname connection limits.
|
||||
host = host.replace(/\*/g, function () {
|
||||
return Math.floor(Random.fraction()*10);
|
||||
});
|
||||
|
||||
return newScheme + '://' + host + rest;
|
||||
} else if (httpUrlMatch) {
|
||||
newScheme = !httpUrlMatch[1] ? newSchemeBase : newSchemeBase + "s";
|
||||
var urlAfterHttp = url.substr(httpUrlMatch[0].length);
|
||||
url = newScheme + "://" + urlAfterHttp;
|
||||
}
|
||||
|
||||
// Prefix FQDNs but not relative URLs
|
||||
if (url.indexOf("://") === -1 && !startsWith(url, "/")) {
|
||||
url = newSchemeBase + "://" + url;
|
||||
}
|
||||
|
||||
if (endsWith(url, "/"))
|
||||
return url + subPath;
|
||||
else
|
||||
return url + "/" + subPath;
|
||||
};
|
||||
|
||||
_.extend(Meteor._DdpClientStream.prototype, {
|
||||
|
||||
_initCommon: function () {
|
||||
var self = this;
|
||||
//// Constants
|
||||
|
||||
// how long to wait until we declare the connection attempt
|
||||
// failed.
|
||||
self.CONNECT_TIMEOUT = 10000;
|
||||
// how long between hearing heartbeat from the server until we declare
|
||||
// the connection dead. heartbeats come every 25s (stream_server.js)
|
||||
//
|
||||
// NOTE: this is a workaround until sockjs detects heartbeats on the
|
||||
// client automatically.
|
||||
// https://github.com/sockjs/sockjs-client/issues/67
|
||||
// https://github.com/sockjs/sockjs-node/issues/68
|
||||
self.HEARTBEAT_TIMEOUT = 60000;
|
||||
|
||||
// time for initial reconnect attempt.
|
||||
self.RETRY_BASE_TIMEOUT = 1000;
|
||||
// exponential factor to increase timeout each attempt.
|
||||
self.RETRY_EXPONENT = 2.2;
|
||||
// maximum time between reconnects.
|
||||
self.RETRY_MAX_TIMEOUT = 1800000; // 30min.
|
||||
// time to wait for the first 2 retries. this helps page reload
|
||||
// speed during dev mode restarts, but doesn't hurt prod too
|
||||
// much (due to CONNECT_TIMEOUT)
|
||||
self.RETRY_MIN_TIMEOUT = 10;
|
||||
// how many times to try to reconnect 'instantly'
|
||||
self.RETRY_MIN_COUNT = 2;
|
||||
// fuzz factor to randomize reconnect times by. avoid reconnect
|
||||
// storms.
|
||||
self.RETRY_FUZZ = 0.5; // +- 25%
|
||||
|
||||
|
||||
|
||||
self.eventCallbacks = {}; // name -> [callback]
|
||||
|
||||
self._forcedToDisconnect = false;
|
||||
|
||||
//// Reactive status
|
||||
self.currentStatus = {
|
||||
status: "connecting",
|
||||
connected: false,
|
||||
retryCount: 0,
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
retry_count: 0
|
||||
};
|
||||
|
||||
|
||||
self.statusListeners = typeof Deps !== 'undefined' && new Deps.Dependency;
|
||||
self.statusChanged = function () {
|
||||
if (self.statusListeners)
|
||||
self.statusListeners.changed();
|
||||
};
|
||||
|
||||
//// Retry logic
|
||||
self.retryTimer = null;
|
||||
self.connectionTimer = null;
|
||||
|
||||
},
|
||||
|
||||
// Trigger a reconnect.
|
||||
reconnect: function (options) {
|
||||
var self = this;
|
||||
|
||||
if (self.currentStatus.connected) {
|
||||
if (options && options._force) {
|
||||
// force reconnect.
|
||||
self._lostConnection();
|
||||
} // else, noop.
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're mid-connection, stop it.
|
||||
if (self.currentStatus.status === "connecting") {
|
||||
self._lostConnection();
|
||||
}
|
||||
|
||||
if (self.retryTimer)
|
||||
clearTimeout(self.retryTimer);
|
||||
self.retryTimer = null;
|
||||
self.currentStatus.retryCount -= 1; // don't count manual retries
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
self.currentStatus.retry_count = self.currentStatus.retryCount;
|
||||
self._retryNow();
|
||||
},
|
||||
|
||||
// Permanently disconnect a stream.
|
||||
forceDisconnect: function (optionalErrorMessage) {
|
||||
var self = this;
|
||||
self._forcedToDisconnect = true;
|
||||
self._cleanup();
|
||||
if (self.retryTimer) {
|
||||
clearTimeout(self.retryTimer);
|
||||
self.retryTimer = null;
|
||||
}
|
||||
self.currentStatus = {
|
||||
status: "failed",
|
||||
connected: false,
|
||||
retryCount: 0,
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
retryCount: 0
|
||||
};
|
||||
if (optionalErrorMessage)
|
||||
self.currentStatus.reason = optionalErrorMessage;
|
||||
self.statusChanged();
|
||||
},
|
||||
|
||||
|
||||
_lostConnection: function () {
|
||||
var self = this;
|
||||
|
||||
self._cleanup();
|
||||
self._retryLater(); // sets status. no need to do it here.
|
||||
},
|
||||
|
||||
_retryTimeout: function (count) {
|
||||
var self = this;
|
||||
|
||||
if (count < self.RETRY_MIN_COUNT)
|
||||
return self.RETRY_MIN_TIMEOUT;
|
||||
|
||||
var timeout = Math.min(
|
||||
self.RETRY_MAX_TIMEOUT,
|
||||
self.RETRY_BASE_TIMEOUT * Math.pow(self.RETRY_EXPONENT, count));
|
||||
// fuzz the timeout randomly, to avoid reconnect storms when a
|
||||
// server goes down.
|
||||
timeout = timeout * ((Random.fraction() * self.RETRY_FUZZ) +
|
||||
(1 - self.RETRY_FUZZ/2));
|
||||
return timeout;
|
||||
},
|
||||
|
||||
_retryLater: function () {
|
||||
var self = this;
|
||||
|
||||
var timeout = self._retryTimeout(self.currentStatus.retryCount);
|
||||
if (self.retryTimer)
|
||||
clearTimeout(self.retryTimer);
|
||||
self.retryTimer = setTimeout(_.bind(self._retryNow, self), timeout);
|
||||
|
||||
self.currentStatus.status = "waiting";
|
||||
self.currentStatus.connected = false;
|
||||
self.currentStatus.retryTime = (new Date()).getTime() + timeout;
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
self.currentStatus.retry_time = self.currentStatus.retryTime;
|
||||
self.statusChanged();
|
||||
},
|
||||
|
||||
_retryNow: function () {
|
||||
var self = this;
|
||||
|
||||
if (self._forcedToDisconnect)
|
||||
return;
|
||||
|
||||
self.currentStatus.retryCount += 1;
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
self.currentStatus.retry_count = self.currentStatus.retryCount;
|
||||
self.currentStatus.status = "connecting";
|
||||
self.currentStatus.connected = false;
|
||||
delete self.currentStatus.retryTime;
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
delete self.currentStatus.retry_time;
|
||||
self.statusChanged();
|
||||
|
||||
self._launchConnection();
|
||||
},
|
||||
|
||||
|
||||
// Get current status. Reactive.
|
||||
status: function () {
|
||||
var self = this;
|
||||
if (self.statusListeners)
|
||||
self.statusListeners.depend();
|
||||
return self.currentStatus;
|
||||
}
|
||||
});
|
||||
|
||||
_.extend(Meteor._DdpClientStream, {
|
||||
|
||||
_toSockjsUrl: function (url) {
|
||||
return translateUrl(url, "http", "sockjs");
|
||||
},
|
||||
|
||||
_toWebsocketUrl: function (url) {
|
||||
var ret = translateUrl(url, "ws", "websocket");
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
@@ -20,8 +20,6 @@ Meteor._DdpClientStream = function (endpoint) {
|
||||
self.client = new WebSocketClient;
|
||||
self.endpoint = endpoint;
|
||||
self.currentConnection = null;
|
||||
self.eventCallbacks = {}; // name -> [callback]
|
||||
self._forcedToDisconnect = false;
|
||||
|
||||
self.client.on('connect', function (connection) {
|
||||
return self._onConnect(connection);
|
||||
@@ -32,44 +30,9 @@ Meteor._DdpClientStream = function (endpoint) {
|
||||
return self._lostConnection();
|
||||
});
|
||||
|
||||
//// Constants
|
||||
self._initCommon();
|
||||
|
||||
// how long to wait until we declare the connection attempt
|
||||
// failed.
|
||||
self.CONNECT_TIMEOUT = 10000;
|
||||
|
||||
// time for initial reconnect attempt.
|
||||
self.RETRY_BASE_TIMEOUT = 1000;
|
||||
// exponential factor to increase timeout each attempt.
|
||||
self.RETRY_EXPONENT = 2.2;
|
||||
// maximum time between reconnects.
|
||||
self.RETRY_MAX_TIMEOUT = 1800000; // 30min.
|
||||
// time to wait for the first 2 retries. this helps page reload
|
||||
// speed during dev mode restarts, but doesn't hurt prod too
|
||||
// much (due to CONNECT_TIMEOUT)
|
||||
self.RETRY_MIN_TIMEOUT = 10;
|
||||
// how many times to try to reconnect 'instantly'
|
||||
self.RETRY_MIN_COUNT = 2;
|
||||
// fuzz factor to randomize reconnect times by. avoid reconnect
|
||||
// storms.
|
||||
self.RETRY_FUZZ = 0.5; // +- 25%
|
||||
|
||||
//// Reactive status
|
||||
self.currentStatus = {
|
||||
status: "connecting", connected: false, retryCount: 0
|
||||
};
|
||||
|
||||
self.statusListeners = typeof Deps !== 'undefined' && new Deps.Dependency;
|
||||
self.statusChanged = function () {
|
||||
if (self.statusListeners)
|
||||
self.statusListeners.changed();
|
||||
};
|
||||
self.expectingWelcome = false;
|
||||
|
||||
//// Retry logic
|
||||
self.retryTimer = null;
|
||||
self.connectionTimer = null;
|
||||
|
||||
//// Kickoff!
|
||||
self._launchConnection();
|
||||
};
|
||||
@@ -97,38 +60,6 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
// Get current status. Reactive.
|
||||
status: function () {
|
||||
var self = this;
|
||||
if (self.statusListeners)
|
||||
self.statusListeners.depend();
|
||||
return self.currentStatus;
|
||||
},
|
||||
|
||||
// Trigger a reconnect.
|
||||
reconnect: function (options) {
|
||||
var self = this;
|
||||
|
||||
if (self.currentStatus.connected) {
|
||||
if (options && options._force) {
|
||||
// force reconnect.
|
||||
self._lostConnection();
|
||||
} // else, noop.
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're mid-connection, stop it.
|
||||
if (self.currentStatus.status === "connecting") {
|
||||
self._lostConnection();
|
||||
}
|
||||
|
||||
if (self.retryTimer)
|
||||
clearTimeout(self.retryTimer);
|
||||
self.retryTimer = null;
|
||||
self.currentStatus.retryCount -= 1; // don't count manual retries
|
||||
self._retryNow();
|
||||
},
|
||||
|
||||
_onConnect: function (connection) {
|
||||
var self = this;
|
||||
|
||||
@@ -194,7 +125,7 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
_.each(self.eventCallbacks.reset, function (callback) { callback(); });
|
||||
},
|
||||
|
||||
_cleanupConnection: function () {
|
||||
_cleanup: function () {
|
||||
var self = this;
|
||||
|
||||
self._clearConnectionTimer();
|
||||
@@ -213,80 +144,9 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
forceDisconnect: function (optionalErrorMessage) {
|
||||
var self = this;
|
||||
self._forcedToDisconnect = true;
|
||||
self._cleanupConnection();
|
||||
if (self.retryTimer) {
|
||||
clearTimeout(self.retryTimer);
|
||||
self.retryTimer = null;
|
||||
}
|
||||
self.currentStatus = {
|
||||
status: "failed",
|
||||
connected: false,
|
||||
retryCount: 0,
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
retry_count: 0
|
||||
};
|
||||
if (optionalErrorMessage)
|
||||
self.currentStatus.reason = optionalErrorMessage;
|
||||
self.statusChanged();
|
||||
},
|
||||
|
||||
_lostConnection: function () {
|
||||
var self = this;
|
||||
self._cleanupConnection();
|
||||
self._retryLater();
|
||||
},
|
||||
|
||||
_retryTimeout: function (count) {
|
||||
var self = this;
|
||||
|
||||
if (count < self.RETRY_MIN_COUNT)
|
||||
return self.RETRY_MIN_TIMEOUT;
|
||||
|
||||
var timeout = Math.min(
|
||||
self.RETRY_MAX_TIMEOUT,
|
||||
self.RETRY_BASE_TIMEOUT * Math.pow(self.RETRY_EXPONENT, count));
|
||||
// fuzz the timeout randomly, to avoid reconnect storms when a
|
||||
// server goes down.
|
||||
timeout = timeout * ((Random.fraction() * self.RETRY_FUZZ) +
|
||||
(1 - self.RETRY_FUZZ/2));
|
||||
return timeout;
|
||||
},
|
||||
|
||||
_retryLater: function () {
|
||||
var self = this;
|
||||
|
||||
var timeout = self._retryTimeout(self.currentStatus.retryCount);
|
||||
if (self.retryTimer)
|
||||
clearTimeout(self.retryTimer);
|
||||
self.retryTimer = setTimeout(_.bind(self._retryNow, self), timeout);
|
||||
|
||||
self.currentStatus.status = "waiting";
|
||||
self.currentStatus.connected = false;
|
||||
self.currentStatus.retryTime = (new Date()).getTime() + timeout;
|
||||
self.statusChanged();
|
||||
},
|
||||
|
||||
_retryNow: function () {
|
||||
var self = this;
|
||||
|
||||
if (self._forcedToDisconnect)
|
||||
return;
|
||||
|
||||
self.currentStatus.retryCount += 1;
|
||||
self.currentStatus.status = "connecting";
|
||||
self.currentStatus.connected = false;
|
||||
delete self.currentStatus.retryTime;
|
||||
self.statusChanged();
|
||||
|
||||
self._launchConnection();
|
||||
},
|
||||
|
||||
_launchConnection: function () {
|
||||
var self = this;
|
||||
self._cleanupConnection(); // cleanup the old socket, if there was one.
|
||||
self._cleanup(); // cleanup the old socket, if there was one.
|
||||
|
||||
// launch a connect attempt. we have no way to track it. we either
|
||||
// get an _onConnect event, or we don't.
|
||||
|
||||
@@ -3,63 +3,16 @@
|
||||
// "ddp+sockjs://foo-**.meteor.com/sockjs"
|
||||
Meteor._DdpClientStream = function (url) {
|
||||
var self = this;
|
||||
|
||||
self._initCommon();
|
||||
self.rawUrl = url;
|
||||
self.socket = null;
|
||||
self.event_callbacks = {}; // name -> [callback]
|
||||
|
||||
self.sent_update_available = false;
|
||||
self._forcedToDisconnect = false;
|
||||
|
||||
//// Constants
|
||||
|
||||
// how long to wait until we declare the connection attempt
|
||||
// failed.
|
||||
self.CONNECT_TIMEOUT = 10000;
|
||||
// how long between hearing heartbeat from the server until we declare
|
||||
// the connection dead. heartbeats come every 25s (stream_server.js)
|
||||
//
|
||||
// NOTE: this is a workaround until sockjs detects heartbeats on the
|
||||
// client automatically.
|
||||
// https://github.com/sockjs/sockjs-client/issues/67
|
||||
// https://github.com/sockjs/sockjs-node/issues/68
|
||||
self.HEARTBEAT_TIMEOUT = 60000;
|
||||
|
||||
// time for initial reconnect attempt.
|
||||
self.RETRY_BASE_TIMEOUT = 1000;
|
||||
// exponential factor to increase timeout each attempt.
|
||||
self.RETRY_EXPONENT = 2.2;
|
||||
// maximum time between reconnects.
|
||||
self.RETRY_MAX_TIMEOUT = 1800000; // 30min.
|
||||
// time to wait for the first 2 retries. this helps page reload
|
||||
// speed during dev mode restarts, but doesn't hurt prod too
|
||||
// much (due to CONNECT_TIMEOUT)
|
||||
self.RETRY_MIN_TIMEOUT = 10;
|
||||
// how many times to try to reconnect 'instantly'
|
||||
self.RETRY_MIN_COUNT = 2;
|
||||
// fuzz factor to randomize reconnect times by. avoid reconnect
|
||||
// storms.
|
||||
self.RETRY_FUZZ = 0.5; // +- 25%
|
||||
|
||||
//// Reactive status
|
||||
self.current_status = {
|
||||
status: "connecting", connected: false, retryCount: 0,
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
retry_count: 0
|
||||
};
|
||||
|
||||
self.status_listeners = window.Deps && new Deps.Dependency;
|
||||
self.status_changed = function () {
|
||||
if (self.status_listeners)
|
||||
self.status_listeners.changed();
|
||||
};
|
||||
|
||||
//// Retry logic
|
||||
self.retry_timer = null;
|
||||
self.connection_timer = null;
|
||||
self.heartbeat_timer = null;
|
||||
self.heartbeatTimer = null;
|
||||
|
||||
//// Kickoff!
|
||||
self._launch_connection();
|
||||
self._launchConnection();
|
||||
};
|
||||
|
||||
_.extend(Meteor._DdpClientStream.prototype, {
|
||||
@@ -70,9 +23,9 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
if (name !== 'message' && name !== 'reset' && name !== 'update_available')
|
||||
throw new Error("unknown event type: " + name);
|
||||
|
||||
if (!self.event_callbacks[name])
|
||||
self.event_callbacks[name] = [];
|
||||
self.event_callbacks[name].push(callback);
|
||||
if (!self.eventCallbacks[name])
|
||||
self.eventCallbacks[name] = [];
|
||||
self.eventCallbacks[name].push(callback);
|
||||
},
|
||||
|
||||
// data is a utf8 string. Data sent while not connected is dropped on
|
||||
@@ -80,54 +33,20 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
// messages on 'reset'
|
||||
send: function (data) {
|
||||
var self = this;
|
||||
if (self.current_status.connected) {
|
||||
if (self.currentStatus.connected) {
|
||||
self.socket.send(data);
|
||||
}
|
||||
},
|
||||
|
||||
// Get current status. Reactive.
|
||||
status: function () {
|
||||
var self = this;
|
||||
if (self.status_listeners)
|
||||
self.status_listeners.depend();
|
||||
return self.current_status;
|
||||
},
|
||||
|
||||
// Trigger a reconnect.
|
||||
reconnect: function (options) {
|
||||
var self = this;
|
||||
|
||||
if (self.current_status.connected) {
|
||||
if (options && options._force) {
|
||||
// force reconnect.
|
||||
self._lostConnection();
|
||||
} // else, noop.
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're mid-connection, stop it.
|
||||
if (self.current_status.status === "connecting") {
|
||||
self._lostConnection();
|
||||
}
|
||||
|
||||
if (self.retry_timer)
|
||||
clearTimeout(self.retry_timer);
|
||||
self.retry_timer = null;
|
||||
self.current_status.retryCount -= 1; // don't count manual retries
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
self.current_status.retry_count = self.current_status.retryCount;
|
||||
self._retry_now();
|
||||
},
|
||||
|
||||
_connected: function (welcome_message) {
|
||||
var self = this;
|
||||
|
||||
if (self.connection_timer) {
|
||||
clearTimeout(self.connection_timer);
|
||||
self.connection_timer = null;
|
||||
if (self.connectionTimer) {
|
||||
clearTimeout(self.connectionTimer);
|
||||
self.connectionTimer = null;
|
||||
}
|
||||
|
||||
if (self.current_status.connected) {
|
||||
if (self.currentStatus.connected) {
|
||||
// already connected. do nothing. this probably shouldn't happen.
|
||||
return;
|
||||
}
|
||||
@@ -144,27 +63,27 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
__meteor_runtime_config__.serverId !== welcome_data.server_id &&
|
||||
!self.sent_update_available) {
|
||||
self.sent_update_available = true;
|
||||
_.each(self.event_callbacks.update_available,
|
||||
_.each(self.eventCallbacks.update_available,
|
||||
function (callback) { callback(); });
|
||||
}
|
||||
} else
|
||||
Meteor._debug("DEBUG: invalid welcome packet", welcome_data);
|
||||
|
||||
// update status
|
||||
self.current_status.status = "connected";
|
||||
self.current_status.connected = true;
|
||||
self.current_status.retryCount = 0;
|
||||
self.currentStatus.status = "connected";
|
||||
self.currentStatus.connected = true;
|
||||
self.currentStatus.retryCount = 0;
|
||||
// XXX Backwards compatibility only. Remove before 1.0.
|
||||
self.current_status.retry_count = self.current_status.retryCount;
|
||||
self.status_changed();
|
||||
self.currentStatus.retryCount = self.currentStatus.retryCount;
|
||||
self.statusChanged();
|
||||
|
||||
// fire resets. This must come after status change so that clients
|
||||
// can call send from within a reset callback.
|
||||
_.each(self.event_callbacks.reset, function (callback) { callback(); });
|
||||
_.each(self.eventCallbacks.reset, function (callback) { callback(); });
|
||||
|
||||
},
|
||||
|
||||
_cleanupSocket: function () {
|
||||
_cleanup: function () {
|
||||
var self = this;
|
||||
|
||||
self._clearConnectionAndHeartbeatTimers();
|
||||
@@ -178,44 +97,16 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
|
||||
_clearConnectionAndHeartbeatTimers: function () {
|
||||
var self = this;
|
||||
if (self.connection_timer) {
|
||||
clearTimeout(self.connection_timer);
|
||||
self.connection_timer = null;
|
||||
if (self.connectionTimer) {
|
||||
clearTimeout(self.connectionTimer);
|
||||
self.connectionTimer = null;
|
||||
}
|
||||
if (self.heartbeat_timer) {
|
||||
clearTimeout(self.heartbeat_timer);
|
||||
self.heartbeat_timer = null;
|
||||
if (self.heartbeatTimer) {
|
||||
clearTimeout(self.heartbeatTimer);
|
||||
self.heartbeatTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// Permanently disconnect a stream.
|
||||
forceDisconnect: function (optionalErrorMessage) {
|
||||
var self = this;
|
||||
self._forcedToDisconnect = true;
|
||||
self._cleanupSocket();
|
||||
if (self.retry_timer) {
|
||||
clearTimeout(self.retry_timer);
|
||||
self.retry_timer = null;
|
||||
}
|
||||
self.current_status = {
|
||||
status: "failed",
|
||||
connected: false,
|
||||
retryCount: 0,
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
retry_count: 0
|
||||
};
|
||||
if (optionalErrorMessage)
|
||||
self.current_status.reason = optionalErrorMessage;
|
||||
self.status_changed();
|
||||
},
|
||||
|
||||
_lostConnection: function () {
|
||||
var self = this;
|
||||
|
||||
self._cleanupSocket();
|
||||
self._retry_later(); // sets status. no need to do it here.
|
||||
},
|
||||
|
||||
_heartbeat_timeout: function () {
|
||||
var self = this;
|
||||
Meteor._debug("Connection timeout. No heartbeat received.");
|
||||
@@ -228,67 +119,17 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
// already cleared, and we don't need to set it again.
|
||||
if (self._forcedToDisconnect)
|
||||
return;
|
||||
if (self.heartbeat_timer)
|
||||
clearTimeout(self.heartbeat_timer);
|
||||
self.heartbeat_timer = setTimeout(
|
||||
if (self.heartbeatTimer)
|
||||
clearTimeout(self.heartbeatTimer);
|
||||
self.heartbeatTimer = setTimeout(
|
||||
_.bind(self._heartbeat_timeout, self),
|
||||
self.HEARTBEAT_TIMEOUT);
|
||||
},
|
||||
|
||||
_retry_timeout: function (count) {
|
||||
|
||||
_launchConnection: function () {
|
||||
var self = this;
|
||||
|
||||
if (count < self.RETRY_MIN_COUNT)
|
||||
return self.RETRY_MIN_TIMEOUT;
|
||||
|
||||
var timeout = Math.min(
|
||||
self.RETRY_MAX_TIMEOUT,
|
||||
self.RETRY_BASE_TIMEOUT * Math.pow(self.RETRY_EXPONENT, count));
|
||||
// fuzz the timeout randomly, to avoid reconnect storms when a
|
||||
// server goes down.
|
||||
timeout = timeout * ((Random.fraction() * self.RETRY_FUZZ) +
|
||||
(1 - self.RETRY_FUZZ/2));
|
||||
return timeout;
|
||||
},
|
||||
|
||||
_retry_later: function () {
|
||||
var self = this;
|
||||
|
||||
var timeout = self._retry_timeout(self.current_status.retryCount);
|
||||
if (self.retry_timer)
|
||||
clearTimeout(self.retry_timer);
|
||||
self.retry_timer = setTimeout(_.bind(self._retry_now, self), timeout);
|
||||
|
||||
self.current_status.status = "waiting";
|
||||
self.current_status.connected = false;
|
||||
self.current_status.retryTime = (new Date()).getTime() + timeout;
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
self.current_status.retry_time = self.current_status.retryTime;
|
||||
self.status_changed();
|
||||
},
|
||||
|
||||
_retry_now: function () {
|
||||
var self = this;
|
||||
|
||||
if (self._forcedToDisconnect)
|
||||
return;
|
||||
|
||||
self.current_status.retryCount += 1;
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
self.current_status.retry_count = self.current_status.retryCount;
|
||||
self.current_status.status = "connecting";
|
||||
self.current_status.connected = false;
|
||||
delete self.current_status.retryTime;
|
||||
// XXX Backwards compatibility only. Remove this before 1.0.
|
||||
delete self.current_status.retry_time;
|
||||
self.status_changed();
|
||||
|
||||
self._launch_connection();
|
||||
},
|
||||
|
||||
_launch_connection: function () {
|
||||
var self = this;
|
||||
self._cleanupSocket(); // cleanup the old socket, if there was one.
|
||||
self._cleanup(); // cleanup the old socket, if there was one.
|
||||
|
||||
// Convert raw URL to SockJS URL each time we open a connection, so that we
|
||||
// can connect to random hostnames and get around browser per-host
|
||||
@@ -307,10 +148,10 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
// first message we get when we're connecting goes to _connected,
|
||||
// which connects us. All subsequent messages (while connected) go to
|
||||
// the callback.
|
||||
if (self.current_status.status === "connecting")
|
||||
if (self.currentStatus.status === "connecting")
|
||||
self._connected(data.data);
|
||||
else if (self.current_status.connected)
|
||||
_.each(self.event_callbacks.message, function (callback) {
|
||||
else if (self.currentStatus.connected)
|
||||
_.each(self.eventCallbacks.message, function (callback) {
|
||||
callback(data.data);
|
||||
});
|
||||
};
|
||||
@@ -327,9 +168,9 @@ _.extend(Meteor._DdpClientStream.prototype, {
|
||||
self._heartbeat_received();
|
||||
};
|
||||
|
||||
if (self.connection_timer)
|
||||
clearTimeout(self.connection_timer);
|
||||
self.connection_timer = setTimeout(
|
||||
if (self.connectionTimer)
|
||||
clearTimeout(self.connectionTimer);
|
||||
self.connectionTimer = setTimeout(
|
||||
_.bind(self._lostConnection, self),
|
||||
self.CONNECT_TIMEOUT);
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
|
||||
// XXX from Underscore.String (http://epeli.github.com/underscore.string/)
|
||||
var startsWith = function(str, starts) {
|
||||
return str.length >= starts.length &&
|
||||
str.substring(0, starts.length) === starts;
|
||||
};
|
||||
var endsWith = function(str, ends) {
|
||||
return str.length >= ends.length &&
|
||||
str.substring(str.length - ends.length) === ends;
|
||||
};
|
||||
|
||||
// @param url {String} URL to Meteor app, eg:
|
||||
// "/" or "madewith.meteor.com" or "https://foo.meteor.com"
|
||||
// or "ddp+sockjs://ddp--****-foo.meteor.com/sockjs"
|
||||
// @returns {String} URL to the endpoint with the specific scheme and subPath, e.g.
|
||||
// for scheme "http" and subPath "sockjs"
|
||||
// "http://subdomain.meteor.com/sockjs" or "/sockjs"
|
||||
// or "https://ddp--1234-foo.meteor.com/sockjs"
|
||||
var translateUrl = function(url, newSchemeBase, subPath) {
|
||||
if (! newSchemeBase) {
|
||||
newSchemeBase = "http";
|
||||
}
|
||||
|
||||
var ddpUrlMatch = url.match(/^ddp(i?)\+sockjs:\/\//);
|
||||
var httpUrlMatch = url.match(/^http(s?):\/\//);
|
||||
var newScheme;
|
||||
if (ddpUrlMatch) {
|
||||
// Remove scheme and split off the host.
|
||||
var urlAfterDDP = url.substr(ddpUrlMatch[0].length);
|
||||
newScheme = ddpUrlMatch[1] === "i" ? newSchemeBase : newSchemeBase + "s";
|
||||
var slashPos = urlAfterDDP.indexOf('/');
|
||||
var host =
|
||||
slashPos === -1 ? urlAfterDDP : urlAfterDDP.substr(0, slashPos);
|
||||
var rest = slashPos === -1 ? '' : urlAfterDDP.substr(slashPos);
|
||||
|
||||
// In the host (ONLY!), change '*' characters into random digits. This
|
||||
// allows different stream connections to connect to different hostnames
|
||||
// and avoid browser per-hostname connection limits.
|
||||
host = host.replace(/\*/g, function () {
|
||||
return Math.floor(Random.fraction()*10);
|
||||
});
|
||||
|
||||
return newScheme + '://' + host + rest;
|
||||
} else if (httpUrlMatch) {
|
||||
newScheme = !httpUrlMatch[1] ? newSchemeBase : newSchemeBase + "s";
|
||||
var urlAfterHttp = url.substr(httpUrlMatch[0].length);
|
||||
url = newScheme + "://" + urlAfterHttp;
|
||||
}
|
||||
|
||||
// Prefix FQDNs but not relative URLs
|
||||
if (url.indexOf("://") === -1 && !startsWith(url, "/")) {
|
||||
url = newSchemeBase + "://" + url;
|
||||
}
|
||||
|
||||
if (endsWith(url, "/"))
|
||||
return url + subPath;
|
||||
else
|
||||
return url + "/" + subPath;
|
||||
};
|
||||
|
||||
_.extend(Meteor._DdpClientStream, {
|
||||
_toSockjsUrl: function (url) {
|
||||
return translateUrl(url, "http", "sockjs");
|
||||
},
|
||||
|
||||
_toWebsocketUrl: function (url) {
|
||||
var ret = translateUrl(url, "ws", "websocket");
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user