diff --git a/packages/livedata/package.js b/packages/livedata/package.js index a79de184b4..40fa0fd897 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -16,6 +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_server.js', 'server'); // livedata_connection.js uses a Minimongo collection internally to diff --git a/packages/livedata/stream_client_nodejs.js b/packages/livedata/stream_client_nodejs.js index 87b3f7d32d..cb9d33693f 100644 --- a/packages/livedata/stream_client_nodejs.js +++ b/packages/livedata/stream_client_nodejs.js @@ -27,6 +27,11 @@ Meteor._DdpClientStream = function (endpoint) { return self._onConnect(connection); }); + self.client.on('connectFailed', function (error) { + // XXX: Make this do something better than make the tests hang if it does not work. + return self._lostConnection(); + }); + //// Constants // how long to wait until we declare the connection attempt @@ -69,16 +74,6 @@ Meteor._DdpClientStream = function (endpoint) { self._launchConnection(); }; -_.extend(Meteor._DdpClientStream, { - _endpointToUrl: function (endpoint) { - // XXX should be secure! - // among other problems - endpoint = endpoint.replace(/^http(s)?:\/\//, ""); - endpoint = endpoint.replace(/\/$/, ""); - return 'ws://' + endpoint + '/websocket'; - } -}); - _.extend(Meteor._DdpClientStream.prototype, { // Register for callbacks. on: function (name, callback) { @@ -303,7 +298,7 @@ _.extend(Meteor._DdpClientStream.prototype, { // a protocol and the server doesn't send one back (and sockjs // doesn't). also, related: I guess we have to accept that // 'stream' is ddp-specific - self.client.connect(Meteor._DdpClientStream._endpointToUrl(self.endpoint)); + self.client.connect(Meteor._DdpClientStream._toWebsocketUrl(self.endpoint)); if (self.connectionTimer) clearTimeout(self.connectionTimer); diff --git a/packages/livedata/stream_client_sockjs.js b/packages/livedata/stream_client_sockjs.js index 371da8a951..c8678a999d 100644 --- a/packages/livedata/stream_client_sockjs.js +++ b/packages/livedata/stream_client_sockjs.js @@ -62,56 +62,6 @@ Meteor._DdpClientStream = function (url) { self._launch_connection(); }; -_.extend(Meteor._DdpClientStream, { - // @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 sockjs endpoint, e.g. - // "http://subdomain.meteor.com/sockjs" or "/sockjs" - // or "https://ddp--1234-foo.meteor.com/sockjs" - _toSockjsUrl: function(url) { - // 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; - }; - - var ddpUrlMatch = url.match(/^ddp(i?)\+sockjs:\/\//); - if (ddpUrlMatch) { - // Remove scheme and split off the host. - var urlAfterDDP = url.substr(ddpUrlMatch[0].length); - var newScheme = ddpUrlMatch[1] === 'i' ? 'http' : 'https'; - 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; - } - - // Prefix FQDNs but not relative URLs - if (url.indexOf("://") === -1 && !startsWith(url, "/")) { - url = "http://" + url; - } - - if (endsWith(url, "/")) - return url + "sockjs"; - else - return url + "/sockjs"; - } -}); - _.extend(Meteor._DdpClientStream.prototype, { // Register for callbacks. on: function (name, callback) { diff --git a/packages/livedata/stream_tests.js b/packages/livedata/stream_tests.js index 5649cf934b..22cc0eedf3 100644 --- a/packages/livedata/stream_tests.js +++ b/packages/livedata/stream_tests.js @@ -37,7 +37,7 @@ Tinytest.add("stream - sockjs urls are computed correctly", function(test) { var testHasSockjsUrl = function(raw, expectedSockjsUrl) { var actual = Meteor._DdpClientStream._toSockjsUrl(raw); if (expectedSockjsUrl instanceof RegExp) - test.isTrue(actual.match(expectedSockjsUrl)); + test.isTrue(actual.match(expectedSockjsUrl), actual); else test.equal(actual, expectedSockjsUrl); }; diff --git a/packages/livedata/urls.js b/packages/livedata/urls.js new file mode 100644 index 0000000000..a77517bfba --- /dev/null +++ b/packages/livedata/urls.js @@ -0,0 +1,70 @@ + +// 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; + } +});