@cratis/arc 22.138.1 → 22.138.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/queries/WebSocketHubConnection.d.ts +5 -0
- package/dist/cjs/queries/WebSocketHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/WebSocketHubConnection.js +39 -11
- package/dist/cjs/queries/WebSocketHubConnection.js.map +1 -1
- package/dist/esm/queries/WebSocketHubConnection.d.ts +5 -0
- package/dist/esm/queries/WebSocketHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/WebSocketHubConnection.js +39 -11
- package/dist/esm/queries/WebSocketHubConnection.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_server_validation_failing.js +1 -0
- package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_server_validation_failing.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.js +1 -0
- package/dist/esm/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.js.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/given/a_web_socket_hub_connection.d.ts +2 -1
- package/dist/esm/queries/for_WebSocketHubConnection/given/a_web_socket_hub_connection.d.ts.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/given/a_web_socket_hub_connection.js +8 -1
- package/dist/esm/queries/for_WebSocketHubConnection/given/a_web_socket_hub_connection.js.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js +2 -6
- package/dist/esm/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.js.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.js +4 -17
- package/dist/esm/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.js.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.js +3 -7
- package/dist/esm/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.js.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/before_the_server_announces_itself.d.ts +2 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/before_the_server_announces_itself.d.ts.map +1 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/before_the_server_announces_itself.js +31 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_subscribing/before_the_server_announces_itself.js.map +1 -0
- package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.js +7 -11
- package/dist/esm/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.js.map +1 -1
- package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.js +1 -5
- package/dist/esm/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/queries/WebSocketHubConnection.ts +44 -15
- package/queries/for_ObservableQueryFor/when_subscribing/with_server_validation_failing.ts +2 -0
- package/queries/for_ObservableQueryFor/when_subscribing/with_web_socket_hub_payload_without_readiness.ts +2 -1
- package/queries/for_WebSocketHubConnection/given/a_web_socket_hub_connection.ts +14 -1
- package/queries/for_WebSocketHubConnection/when_receiving_unauthorized/notifies_subscriber_and_removes_subscription.ts +2 -6
- package/queries/for_WebSocketHubConnection/when_reconnecting/resubscribes_all_queries.ts +5 -19
- package/queries/for_WebSocketHubConnection/when_replacing_a_subscription/ignores_stale_result_unauthorized_and_error.ts +3 -7
- package/queries/for_WebSocketHubConnection/when_subscribing/before_the_server_announces_itself.ts +43 -0
- package/queries/for_WebSocketHubConnection/when_subscription_revisions_are_supported/upgrades_active_subscriptions_to_strict_mode.ts +7 -14
- package/queries/for_WebSocketHubConnection/when_unsubscribing/the_only_query.ts +1 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@cratis/arc",
|
|
4
|
-
"version": "22.138.
|
|
4
|
+
"version": "22.138.2",
|
|
5
5
|
"description": "TypeScript command, query, validation, identity, and messaging runtime for Cratis Arc — the CQRS application framework for ASP.NET Core — used by generated proxies.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"cratis",
|
|
@@ -80,6 +80,9 @@ interface ActiveSubscription {
|
|
|
80
80
|
* revision-aware operations when the server advertises support in a Connected message.
|
|
81
81
|
*/
|
|
82
82
|
export class WebSocketHubConnection {
|
|
83
|
+
/** How long to wait for the server's Connected message before subscribing the way older servers expect. */
|
|
84
|
+
static readonly ConnectedGraceMs = 1000;
|
|
85
|
+
|
|
83
86
|
private _socket?: WebSocket;
|
|
84
87
|
private _disconnected = false;
|
|
85
88
|
private _subscriptions: Map<string, ActiveSubscription> = new Map();
|
|
@@ -90,6 +93,8 @@ export class WebSocketHubConnection {
|
|
|
90
93
|
private _nextRevision = 0;
|
|
91
94
|
private _supportsSubscriptionRevisions = false;
|
|
92
95
|
private _reconnectPending = false;
|
|
96
|
+
private _subscriptionsReleased = false;
|
|
97
|
+
private _releaseTimer?: ReturnType<typeof setTimeout>;
|
|
93
98
|
|
|
94
99
|
/**
|
|
95
100
|
* Initializes a new instance of {@link WebSocketHubConnection}.
|
|
@@ -162,10 +167,10 @@ export class WebSocketHubConnection {
|
|
|
162
167
|
this._subscriptions.set(queryId, subscription);
|
|
163
168
|
this.ensureConnected();
|
|
164
169
|
|
|
165
|
-
if (this._socket?.readyState === WebSocket.OPEN) {
|
|
170
|
+
if (this._socket?.readyState === WebSocket.OPEN && this._subscriptionsReleased) {
|
|
166
171
|
this.sendSubscribeMessage(queryId, subscription);
|
|
167
172
|
}
|
|
168
|
-
//
|
|
173
|
+
// Until the server has said what it supports, subscriptions are held back and sent once, by releaseSubscriptions.
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
/**
|
|
@@ -176,7 +181,7 @@ export class WebSocketHubConnection {
|
|
|
176
181
|
const subscription = this._subscriptions.get(queryId);
|
|
177
182
|
this._subscriptions.delete(queryId);
|
|
178
183
|
|
|
179
|
-
if (this._socket?.readyState === WebSocket.OPEN && subscription) {
|
|
184
|
+
if (this._socket?.readyState === WebSocket.OPEN && this._subscriptionsReleased && subscription) {
|
|
180
185
|
const message: HubMessage = {
|
|
181
186
|
type: HubMessageType.Unsubscribe,
|
|
182
187
|
queryId,
|
|
@@ -227,6 +232,7 @@ export class WebSocketHubConnection {
|
|
|
227
232
|
this._keepAlive.stop();
|
|
228
233
|
this._policy.cancel();
|
|
229
234
|
this._reconnectPending = false;
|
|
235
|
+
this.resetSubscriptionRelease();
|
|
230
236
|
const socket = this._socket;
|
|
231
237
|
this._socket = undefined;
|
|
232
238
|
this._supportsSubscriptionRevisions = false;
|
|
@@ -251,6 +257,7 @@ export class WebSocketHubConnection {
|
|
|
251
257
|
}
|
|
252
258
|
|
|
253
259
|
this._supportsSubscriptionRevisions = false;
|
|
260
|
+
this.resetSubscriptionRelease();
|
|
254
261
|
|
|
255
262
|
let url = this._url;
|
|
256
263
|
if (this._microservice?.length > 0) {
|
|
@@ -267,7 +274,17 @@ export class WebSocketHubConnection {
|
|
|
267
274
|
this.cancelPendingReconnect();
|
|
268
275
|
this._policy.reset();
|
|
269
276
|
this._keepAlive.start();
|
|
270
|
-
|
|
277
|
+
|
|
278
|
+
// A capable server announces itself with a Connected message straight after accepting the socket. Sending
|
|
279
|
+
// the subscriptions before that would mean sending them twice (once without a revision, once with), and the
|
|
280
|
+
// server would run every query twice and cancel the first run. Wait for it, and only fall back to the
|
|
281
|
+
// revision-less form for a server that never says anything.
|
|
282
|
+
this._releaseTimer = setTimeout(() => {
|
|
283
|
+
this._releaseTimer = undefined;
|
|
284
|
+
if (this._socket === socket && !this._disconnected) {
|
|
285
|
+
this.releaseSubscriptions();
|
|
286
|
+
}
|
|
287
|
+
}, WebSocketHubConnection.ConnectedGraceMs);
|
|
271
288
|
};
|
|
272
289
|
|
|
273
290
|
socket.onclose = () => {
|
|
@@ -275,6 +292,7 @@ export class WebSocketHubConnection {
|
|
|
275
292
|
console.log('Hub connection closed', url);
|
|
276
293
|
this._socket = undefined;
|
|
277
294
|
this._supportsSubscriptionRevisions = false;
|
|
295
|
+
this.resetSubscriptionRelease();
|
|
278
296
|
this._keepAlive.stop();
|
|
279
297
|
if (this._subscriptions.size === 0) return;
|
|
280
298
|
this._reconnectPending = true;
|
|
@@ -317,6 +335,24 @@ export class WebSocketHubConnection {
|
|
|
317
335
|
socket.close();
|
|
318
336
|
}
|
|
319
337
|
|
|
338
|
+
private resetSubscriptionRelease(): void {
|
|
339
|
+
this._subscriptionsReleased = false;
|
|
340
|
+
if (this._releaseTimer !== undefined) {
|
|
341
|
+
clearTimeout(this._releaseTimer);
|
|
342
|
+
this._releaseTimer = undefined;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private releaseSubscriptions(): void {
|
|
347
|
+
if (this._subscriptionsReleased) return;
|
|
348
|
+
this._subscriptionsReleased = true;
|
|
349
|
+
if (this._releaseTimer !== undefined) {
|
|
350
|
+
clearTimeout(this._releaseTimer);
|
|
351
|
+
this._releaseTimer = undefined;
|
|
352
|
+
}
|
|
353
|
+
this.sendAllSubscriptions();
|
|
354
|
+
}
|
|
355
|
+
|
|
320
356
|
private sendAllSubscriptions(): void {
|
|
321
357
|
for (const [queryId, sub] of this._subscriptions) {
|
|
322
358
|
this.sendSubscribeMessage(queryId, sub);
|
|
@@ -382,18 +418,11 @@ export class WebSocketHubConnection {
|
|
|
382
418
|
}
|
|
383
419
|
|
|
384
420
|
private handleConnected(message: HubMessage): void {
|
|
385
|
-
if (
|
|
386
|
-
message.supportsSubscriptionRevisions !== true ||
|
|
387
|
-
this._supportsSubscriptionRevisions
|
|
388
|
-
) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
421
|
+
if (this._subscriptionsReleased) return;
|
|
391
422
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
// Ordered WebSocket delivery means these revision-aware replacements are processed afterwards.
|
|
396
|
-
this.sendAllSubscriptions();
|
|
423
|
+
// Capability is known before anything is sent, so every subscription goes out exactly once, in its final form.
|
|
424
|
+
this._supportsSubscriptionRevisions = message.supportsSubscriptionRevisions === true;
|
|
425
|
+
this.releaseSubscriptions();
|
|
397
426
|
}
|
|
398
427
|
|
|
399
428
|
private handleQueryResult(message: HubMessage): void {
|
|
@@ -54,6 +54,8 @@ for (const direct of [true, false]) {
|
|
|
54
54
|
stream.onmessage?.({ data: JSON.stringify({ type: 'Connected', payload: 'connection-1' }) });
|
|
55
55
|
return JSON.parse(fetchStub.firstCall.args[1].body).queryId;
|
|
56
56
|
}
|
|
57
|
+
// The server announces itself before any subscription is sent.
|
|
58
|
+
stream.onmessage?.({ data: JSON.stringify({ type: 'Connected', payload: 'connection-1' }) });
|
|
57
59
|
return JSON.parse(stream.send.firstCall.args[0]).queryId;
|
|
58
60
|
};
|
|
59
61
|
|
|
@@ -62,8 +62,9 @@ describe(
|
|
|
62
62
|
socket.readyState = 1;
|
|
63
63
|
socket.onopen?.();
|
|
64
64
|
|
|
65
|
-
// An older Arc server
|
|
65
|
+
// An older Arc server announces itself without the revision capability, so this fixture intentionally
|
|
66
66
|
// remains in legacy mode and accepts only result frames without a revision.
|
|
67
|
+
socket.onmessage?.({ data: JSON.stringify({ type: HubMessageType.Connected, payload: 'connection-1' }) });
|
|
67
68
|
let subscribeMessage: { queryId: string };
|
|
68
69
|
try {
|
|
69
70
|
subscribeMessage = JSON.parse(socket.send.firstCall.args[0]);
|
|
@@ -65,7 +65,20 @@ export class a_web_socket_hub_connection {
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Opens the socket and has the server announce itself, as a real server does straight after accepting it.
|
|
70
|
+
* Subscriptions are only sent once that announcement arrives.
|
|
71
|
+
* @param supportsSubscriptionRevisions Whether the announcing server supports revision-aware subscriptions.
|
|
72
|
+
*/
|
|
73
|
+
simulateOpen(supportsSubscriptionRevisions = false): void {
|
|
74
|
+
this.simulateOpenWithoutAnnouncement();
|
|
75
|
+
this.simulateMessage({
|
|
76
|
+
type: 'Connected',
|
|
77
|
+
...(supportsSubscriptionRevisions ? { supportsSubscriptionRevisions: true } : {}),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
simulateOpenWithoutAnnouncement(): void {
|
|
69
82
|
this.fakeSocket.readyState = 1; // OPEN
|
|
70
83
|
this.fakeSocket.onopen?.();
|
|
71
84
|
}
|
|
@@ -19,11 +19,7 @@ describe(
|
|
|
19
19
|
callback = sinon.stub();
|
|
20
20
|
|
|
21
21
|
context.connection.subscribe(queryId, { queryName: 'SecureQuery' }, callback);
|
|
22
|
-
context.simulateOpen();
|
|
23
|
-
context.simulateMessage({
|
|
24
|
-
type: HubMessageType.Connected,
|
|
25
|
-
supportsSubscriptionRevisions: true,
|
|
26
|
-
});
|
|
22
|
+
context.simulateOpen(true);
|
|
27
23
|
const subscribe = getSubscribeMessage();
|
|
28
24
|
|
|
29
25
|
// Server replies with Unauthorized for this exact subscription.
|
|
@@ -44,7 +40,7 @@ describe(
|
|
|
44
40
|
|
|
45
41
|
function getSubscribeMessage(): { revision: number } {
|
|
46
42
|
try {
|
|
47
|
-
return JSON.parse(context.fakeSocket.send.getCall(
|
|
43
|
+
return JSON.parse(context.fakeSocket.send.getCall(0).args[0]) as {
|
|
48
44
|
revision: number;
|
|
49
45
|
};
|
|
50
46
|
} catch (error) {
|
|
@@ -17,11 +17,7 @@ describe(
|
|
|
17
17
|
callbackStub = sinon.stub();
|
|
18
18
|
context.setup();
|
|
19
19
|
context.connection.subscribe(queryId, { queryName: 'MyQuery' }, callbackStub);
|
|
20
|
-
context.simulateOpen();
|
|
21
|
-
context.simulateMessage({
|
|
22
|
-
type: HubMessageType.Connected,
|
|
23
|
-
supportsSubscriptionRevisions: true,
|
|
24
|
-
});
|
|
20
|
+
context.simulateOpen(true);
|
|
25
21
|
|
|
26
22
|
// Simulate drop and policy-driven reconnect
|
|
27
23
|
context.simulateClose();
|
|
@@ -32,27 +28,17 @@ describe(
|
|
|
32
28
|
(context.policy.reset as sinon.SinonStub).reset();
|
|
33
29
|
reconnectCallback();
|
|
34
30
|
|
|
35
|
-
// The new socket
|
|
36
|
-
context.simulateOpen();
|
|
37
|
-
context.simulateMessage({
|
|
38
|
-
type: HubMessageType.Connected,
|
|
39
|
-
supportsSubscriptionRevisions: true,
|
|
40
|
-
});
|
|
31
|
+
// The new socket announces itself again, and only then are the subscriptions sent.
|
|
32
|
+
context.simulateOpen(true);
|
|
41
33
|
});
|
|
42
34
|
|
|
43
35
|
afterEach(() => sinon.restore());
|
|
44
36
|
|
|
45
|
-
it('should
|
|
37
|
+
it('should resubscribe once, in revision-aware form, on the new socket', () => {
|
|
38
|
+
context.fakeSocket.send.calledOnce.should.equal(true);
|
|
46
39
|
const msg = getSentMessage(0);
|
|
47
40
|
msg.type.should.equal(HubMessageType.Subscribe);
|
|
48
41
|
msg.queryId.should.equal(queryId);
|
|
49
|
-
(msg.revision === undefined).should.equal(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should upgrade the reconnect subscription after the new handshake', () => {
|
|
53
|
-
const msg = getSentMessage(1);
|
|
54
|
-
msg.type.should.equal(HubMessageType.Subscribe);
|
|
55
|
-
msg.queryId.should.equal(queryId);
|
|
56
42
|
(msg.revision ?? 0).should.be.greaterThan(0);
|
|
57
43
|
});
|
|
58
44
|
|
|
@@ -25,19 +25,15 @@ describe(
|
|
|
25
25
|
{ queryName: 'Original' },
|
|
26
26
|
originalCallback,
|
|
27
27
|
);
|
|
28
|
-
context.simulateOpen();
|
|
29
|
-
|
|
30
|
-
type: HubMessageType.Connected,
|
|
31
|
-
supportsSubscriptionRevisions: true,
|
|
32
|
-
});
|
|
33
|
-
const originalRevision = sentMessage(1).revision!;
|
|
28
|
+
context.simulateOpen(true);
|
|
29
|
+
const originalRevision = sentMessage(0).revision!;
|
|
34
30
|
|
|
35
31
|
context.connection.subscribe(
|
|
36
32
|
queryId,
|
|
37
33
|
{ queryName: 'Replacement' },
|
|
38
34
|
replacementCallback,
|
|
39
35
|
);
|
|
40
|
-
const replacementRevision = sentMessage(
|
|
36
|
+
const replacementRevision = sentMessage(1).revision!;
|
|
41
37
|
|
|
42
38
|
context.simulateMessage({
|
|
43
39
|
type: HubMessageType.QueryResult,
|
package/queries/for_WebSocketHubConnection/when_subscribing/before_the_server_announces_itself.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import sinon from 'sinon';
|
|
5
|
+
import { a_web_socket_hub_connection } from '../given/a_web_socket_hub_connection.js';
|
|
6
|
+
import { given } from '../../../given.js';
|
|
7
|
+
import { HubMessageType, WebSocketHubConnection } from '../../WebSocketHubConnection.js';
|
|
8
|
+
|
|
9
|
+
describe(
|
|
10
|
+
'when the socket opens but the server has not announced itself',
|
|
11
|
+
given(a_web_socket_hub_connection, (context) => {
|
|
12
|
+
let clock: sinon.SinonFakeTimers;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
clock = sinon.useFakeTimers();
|
|
16
|
+
context.setup();
|
|
17
|
+
context.connection.subscribe('q1', { queryName: 'MyQuery' }, sinon.stub());
|
|
18
|
+
context.simulateOpenWithoutAnnouncement();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
clock.restore();
|
|
23
|
+
sinon.restore();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should hold the subscription back', () =>
|
|
27
|
+
context.fakeSocket.send.called.should.equal(false));
|
|
28
|
+
|
|
29
|
+
it('should send nothing more than a subscription without a revision once the grace period passes', () => {
|
|
30
|
+
clock.tick(WebSocketHubConnection.ConnectedGraceMs);
|
|
31
|
+
context.fakeSocket.send.calledOnce.should.equal(true);
|
|
32
|
+
const message = JSON.parse(context.fakeSocket.send.firstCall.args[0]);
|
|
33
|
+
message.type.should.equal(HubMessageType.Subscribe);
|
|
34
|
+
(message.revision === undefined).should.equal(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should not send it a second time when the announcement arrives late', () => {
|
|
38
|
+
clock.tick(WebSocketHubConnection.ConnectedGraceMs);
|
|
39
|
+
context.simulateMessage({ type: HubMessageType.Connected, supportsSubscriptionRevisions: true });
|
|
40
|
+
context.fakeSocket.send.calledOnce.should.equal(true);
|
|
41
|
+
});
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
@@ -17,15 +17,10 @@ describe(
|
|
|
17
17
|
context.setup();
|
|
18
18
|
callback = sinon.stub();
|
|
19
19
|
context.connection.subscribe(queryId, { queryName: 'MyQuery' }, callback);
|
|
20
|
-
context.simulateOpen();
|
|
21
|
-
|
|
22
|
-
context.simulateMessage({
|
|
23
|
-
type: HubMessageType.Connected,
|
|
24
|
-
supportsSubscriptionRevisions: true,
|
|
25
|
-
});
|
|
26
|
-
const upgradedRevision = sentMessage(1).revision;
|
|
20
|
+
context.simulateOpen(true);
|
|
21
|
+
const upgradedRevision = sentMessage(0).revision;
|
|
27
22
|
if (upgradedRevision === undefined) {
|
|
28
|
-
throw new Error('Expected the
|
|
23
|
+
throw new Error('Expected the subscription to carry a revision');
|
|
29
24
|
}
|
|
30
25
|
revision = upgradedRevision;
|
|
31
26
|
|
|
@@ -44,17 +39,15 @@ describe(
|
|
|
44
39
|
|
|
45
40
|
afterEach(() => sinon.restore());
|
|
46
41
|
|
|
47
|
-
it('should
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
it('should replace the legacy subscription with its current revision', () => {
|
|
51
|
-
const message = sentMessage(1);
|
|
42
|
+
it('should send the subscription once, with its revision, and not before the server has announced itself', () => {
|
|
43
|
+
context.fakeSocket.send.calledOnce.should.equal(true);
|
|
44
|
+
const message = sentMessage(0);
|
|
52
45
|
message.type.should.equal(HubMessageType.Subscribe);
|
|
53
46
|
(message.queryId === queryId).should.equal(true);
|
|
54
47
|
revision.should.be.greaterThan(0);
|
|
55
48
|
});
|
|
56
49
|
|
|
57
|
-
it('should ignore
|
|
50
|
+
it('should ignore a result without a revision and deliver the strict result once', () => {
|
|
58
51
|
callback.calledOnce.should.equal(true);
|
|
59
52
|
callback.firstCall.args[0].should.deep.equal({ data: ['current'] });
|
|
60
53
|
});
|
|
@@ -14,11 +14,7 @@ describe(
|
|
|
14
14
|
beforeEach(() => {
|
|
15
15
|
context.setup();
|
|
16
16
|
context.connection.subscribe(queryId, { queryName: 'MyQuery' }, sinon.stub());
|
|
17
|
-
context.simulateOpen();
|
|
18
|
-
context.simulateMessage({
|
|
19
|
-
type: HubMessageType.Connected,
|
|
20
|
-
supportsSubscriptionRevisions: true,
|
|
21
|
-
});
|
|
17
|
+
context.simulateOpen(true);
|
|
22
18
|
context.fakeSocket.send.reset();
|
|
23
19
|
context.connection.unsubscribe(queryId);
|
|
24
20
|
});
|