Merge pull request #9371 from meteor/extract-socket-stream-client

Extract ClientStream into its own package.
This commit is contained in:
Ben Newman
2017-11-15 17:34:30 -05:00
committed by GitHub
17 changed files with 132 additions and 120 deletions

View File

@@ -1,35 +1,10 @@
{
"lockfileVersion": 1,
"dependencies": {
"faye-websocket": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
"integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg="
},
"http-parser-js": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.6.tgz",
"integrity": "sha1-GVJz9YcExFLWcQdr4gEyndNB3FU="
},
"lolex": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/lolex/-/lolex-1.4.0.tgz",
"integrity": "sha1-LycSsbwYDendzF06epbvPAuxYq0="
},
"permessage-deflate": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.6.tgz",
"integrity": "sha1-WB8c7fvUQPrEfQd3vohjM4a5kt4="
},
"websocket-driver": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
"integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs="
},
"websocket-extensions": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.2.tgz",
"integrity": "sha1-Dhh4HeYpoYMIzhSBZQ9n/6JpOl0="
}
}
}

View File

@@ -1,11 +1,5 @@
export { DDP } from '../common/namespace.js';
if (false) {
// This is used inside livedata_connection, but this is what gets
// it included in the client bundle
import './stream_client_sockjs';
}
import '../common/livedata_connection';
// Initialize the default server connection and put it on Meteor.connection

View File

@@ -1,18 +0,0 @@
import { Meteor } from 'meteor/meteor';
// In the client and server entry points, we make sure the
// bundler loads the correct thing. Here, we just need to
// make sure that we require the right one.
export default function getClientStreamClass() {
// The static analyzer of the bundler specifically looks
// for static calls to 'require', so it won't treat the
// below calls as a request to include that module.
//
// That means stream_client_nodejs won't be included on
// the client, as desired.
const modulePath = Meteor.isClient
? '../client/stream_client_sockjs'
: '../server/stream_client_nodejs';
return require(modulePath).default;
}

View File

@@ -6,7 +6,6 @@ import { Random } from 'meteor/random';
import { Hook } from 'meteor/callback-hook';
import { MongoID } from 'meteor/mongo-id';
import { DDP } from './namespace.js';
import getClientStreamClass from './getClientStreamClass.js';
import MethodInvoker from './MethodInvoker.js';
import {
hasOwn,
@@ -83,8 +82,10 @@ export class Connection {
if (typeof url === 'object') {
self._stream = url;
} else {
self._stream = new (getClientStreamClass())(url, {
const { ClientStream } = require("meteor/socket-stream-client");
self._stream = new ClientStream(url, {
retry: options.retry,
ConnectionError: DDP.ConnectionError,
headers: options.headers,
_sockjsOptions: options._sockjsOptions,
// Used to keep some tests quiet, or for other cases in which

View File

@@ -5,9 +5,7 @@ Package.describe({
});
Npm.depends({
'faye-websocket': '0.11.1',
lolex: '1.4.0',
'permessage-deflate': '0.1.6'
lolex: '1.4.0'
});
Package.onUse((api) => {
@@ -22,6 +20,7 @@ Package.onUse((api) => {
'callback-hook',
'ddp-common',
'reload',
'socket-stream-client',
// we depend on _diffObjects, _applyChanges,
'diff-sequence',
@@ -73,6 +72,4 @@ Package.onTest((api) => {
api.addFiles('test/livedata_tests.js');
api.addFiles('test/livedata_test_service.js');
api.addFiles('test/random_stream_tests.js');
api.addFiles('test/stream_tests.js', 'client');
api.addFiles('test/stream_client_tests.js', 'server');
});

View File

@@ -1,7 +1 @@
export { DDP } from '../common/namespace.js';
if (false) {
// This is used inside livedata_connection, but this is what gets
// it included in the server bundle
import './stream_client_nodejs';
}

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,7 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

View File

@@ -0,0 +1,30 @@
{
"lockfileVersion": 1,
"dependencies": {
"faye-websocket": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
"integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg="
},
"http-parser-js": {
"version": "0.4.9",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.9.tgz",
"integrity": "sha1-6hoE+2St/wJC6ZdPKX3Uw8rSceE="
},
"permessage-deflate": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.6.tgz",
"integrity": "sha1-WB8c7fvUQPrEfQd3vohjM4a5kt4="
},
"websocket-driver": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
"integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs="
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg=="
}
}
}

View File

@@ -0,0 +1,8 @@
# socket-stream-client
[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/socket-stream-client) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/socket-stream-client)
***
This package provides the `ClientStream` abstraction used by the
[`ddp-client`](https://github.com/meteor/meteor/tree/devel/packages/ddp-client)
package.

View File

@@ -1,22 +1,16 @@
import { Meteor } from 'meteor/meteor';
import { DDP } from '../common/namespace.js';
import {
toSockjsUrl,
toWebsocketUrl,
} from '../common/urlHelpers.js';
import StreamClientCommon from '../common/stream_client_common.js';
} from "./urls.js";
export default class ClientStream extends StreamClientCommon {
import { StreamClientCommon } from "./common.js";
export class ClientStream extends StreamClientCommon {
// @param url {String} URL to Meteor app
// "http://subdomain.meteor.com/" or "/" or
// "ddp+sockjs://foo-**.meteor.com/sockjs"
constructor(url, options) {
super();
this.options = {
retry: true,
...options
};
super(options);
this._initCommon(this.options);
@@ -114,8 +108,8 @@ export default class ClientStream extends StreamClientCommon {
}
_heartbeat_timeout() {
Meteor._debug('Connection timeout. No sockjs heartbeat received.');
this._lostConnection(new DDP.ConnectionError('Heartbeat timed out'));
console.log('Connection timeout. No sockjs heartbeat received.');
this._lostConnection(new this.ConnectionError("Heartbeat timed out"));
}
_heartbeat_received() {
@@ -186,11 +180,11 @@ export default class ClientStream extends StreamClientCommon {
this.socket.onclose = () => {
this._lostConnection();
};
this.socket.onerror = () => {
this.socket.onerror = (...args) => {
// XXX is this ever called?
Meteor._debug(
console.log(
'stream error',
Array.from(arguments),
args,
new Date().toDateString()
);
};
@@ -201,7 +195,9 @@ export default class ClientStream extends StreamClientCommon {
if (this.connectionTimer) clearTimeout(this.connectionTimer);
this.connectionTimer = setTimeout(() => {
this._lostConnection(new DDP.ConnectionError('DDP connection timed out'));
this._lostConnection(
new this.ConnectionError("DDP connection timed out")
);
}, this.CONNECT_TIMEOUT);
}
}

View File

@@ -1,7 +1,5 @@
import { toSockjsUrl } from '../common/urlHelpers.js';
import getClientStreamClass from '../common/getClientStreamClass.js';
const ClientStream = getClientStreamClass();
import { toSockjsUrl } from "./urls.js";
import { ClientStream } from "meteor/socket-stream-client";
Tinytest.add('stream - status', function(test) {
// Very basic test. Just see that it runs and returns something. Not a

View File

@@ -1,11 +1,18 @@
import { Random } from 'meteor/random';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Retry } from 'meteor/retry';
import { DDP } from './namespace.js';
const forcedReconnectError = new Error("forced reconnect");
export class StreamClientCommon {
constructor(options) {
this.options = {
retry: true,
...(options || null),
};
this.ConnectionError =
options && options.ConnectionError || Error;
}
export default class StreamClientCommon {
// Register for callbacks.
on(name, callback) {
if (name !== 'message' && name !== 'reset' && name !== 'disconnect')
@@ -43,11 +50,14 @@ export default class StreamClientCommon {
retryCount: 0
};
this.statusListeners =
typeof Tracker !== 'undefined' && new Tracker.Dependency();
if (Package.tracker) {
this.statusListeners = new Package.tracker.Tracker.Dependency();
}
this.statusChanged = () => {
if (this.statusListeners) this.statusListeners.changed();
if (this.statusListeners) {
this.statusListeners.changed();
}
};
//// Retry logic
@@ -69,9 +79,8 @@ export default class StreamClientCommon {
if (this.currentStatus.connected) {
if (options._force || options.url) {
// force reconnect.
this._lostConnection(new DDP.ForcedReconnectError());
} // else, noop.
this._lostConnection(forcedReconnectError);
}
return;
}
@@ -131,10 +140,8 @@ export default class StreamClientCommon {
_retryLater(maybeError) {
var timeout = 0;
if (
this.options.retry ||
(maybeError && maybeError.errorType === 'DDP.ForcedReconnectError')
) {
if (this.options.retry ||
maybeError === forcedReconnectError) {
timeout = this._retry.retryLater(
this.currentStatus.retryCount,
this._retryNow.bind(this)
@@ -164,7 +171,9 @@ export default class StreamClientCommon {
// Get current status. Reactive.
status() {
if (this.statusListeners) this.statusListeners.depend();
if (this.statusListeners) {
this.statusListeners.depend();
}
return this.currentStatus;
}
}

View File

@@ -1,7 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { DDP } from '../common/namespace.js';
import { toWebsocketUrl } from '../common/urlHelpers.js';
import StreamClientCommon from '../common/stream_client_common.js';
import { Meteor } from "meteor/meteor";
import { toWebsocketUrl } from "./urls.js";
import { StreamClientCommon } from "./common.js";
// @param endpoint {String} URL to Meteor app
// "http://subdomain.meteor.com/" or "/" or
@@ -14,14 +13,9 @@ import StreamClientCommon from '../common/stream_client_common.js';
// We don't do any heartbeating. (The logic that did this in sockjs was removed,
// because it used a built-in sockjs mechanism. We could do it with WebSocket
// ping frames or with DDP-level messages.)
export default class ClientStream extends StreamClientCommon {
export class ClientStream extends StreamClientCommon {
constructor(endpoint, options) {
super();
this.options = {
retry: true,
...(options || null),
};
super(options);
this.client = null; // created in _launchConnection
this.endpoint = endpoint;
@@ -155,7 +149,7 @@ export default class ClientStream extends StreamClientCommon {
this._clearConnectionTimer();
this.connectionTimer = Meteor.setTimeout(() => {
this._lostConnection(new DDP.ConnectionError('DDP connection timed out'));
this._lostConnection(new this.ConnectionError('DDP connection timed out'));
}, this.CONNECT_TIMEOUT);
this.client.on(
@@ -182,7 +176,7 @@ export default class ClientStream extends StreamClientCommon {
// Faye's 'error' object is not a JS error (and among other things,
// doesn't stringify well). Convert it to one.
this._lostConnection(new DDP.ConnectionError(error.message));
this._lostConnection(new this.ConnectionError(error.message));
});
clientOnIfCurrent('close', 'stream close callback', () => {

View File

@@ -0,0 +1,30 @@
Package.describe({
name: "socket-stream-client",
version: "0.1.0",
summary: "Provides the ClientStream abstraction used by ddp-client",
documentation: "README.md"
});
Npm.depends({
"faye-websocket": "0.11.1",
"permessage-deflate": "0.1.6"
});
Package.onUse(function(api) {
api.use("ecmascript");
api.use("retry"); // TODO Try to remove this.
api.mainModule("browser.js", "client", { lazy: true });
api.mainModule("node.js", "server", { lazy: true });
});
Package.onTest(function(api) {
api.use("underscore");
api.use("ecmascript");
api.use("tinytest");
api.use("test-helpers");
api.use("tracker");
api.use("http");
api.use("socket-stream-client");
api.mainModule("client-tests.js", "client");
api.mainModule("server-tests.js", "server");
});

View File

@@ -1,6 +1,5 @@
import ClientStream from '../server/stream_client_nodejs.js';
var Fiber = Npm.require('fibers');
import { ClientStream } from "meteor/socket-stream-client";
import Fiber from "fibers";
testAsyncMulti('stream client - callbacks run in a fiber', [
function(test, expect) {

View File

@@ -1,5 +1,3 @@
import { Random } from 'meteor/random';
// @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"
@@ -30,7 +28,7 @@ function translateUrl(url, newSchemeBase, subPath) {
// 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, () => Math.floor(Random.fraction() * 10));
host = host.replace(/\*/g, () => Math.floor(Math.random() * 10));
return newScheme + '://' + host + rest;
} else if (httpUrlMatch) {
@@ -65,6 +63,5 @@ export function toSockjsUrl(url) {
}
export function toWebsocketUrl(url) {
var ret = translateUrl(url, 'ws', 'websocket');
return ret;
return translateUrl(url, 'ws', 'websocket');
}