Skip to content

Commit e9695b4

Browse files
adamcheldkaminsky
authored andcommitted
STITCH-2576 Close streams when user context switches (#247)
1 parent 1d33efd commit e9695b4

File tree

13 files changed

+586
-37
lines changed

13 files changed

+586
-37
lines changed

Diff for: packages/browser/core/src/core/internal/StitchAppClientImpl.ts

+57-15
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
*/
1616

1717
import {
18+
AuthEventKind,
19+
AuthRebindEvent,
1820
CoreStitchAppClient,
21+
CoreStitchServiceClient,
1922
CoreStitchServiceClientImpl,
23+
RebindEvent,
2024
StitchAppClientConfiguration,
2125
StitchAppClientInfo,
2226
StitchAppRequestClient
@@ -28,17 +32,22 @@ import StitchServiceClientImpl from "../../services/internal/StitchServiceClient
2832
import StitchServiceClient from "../../services/StitchServiceClient";
2933
import StitchAuthImpl from "../auth/internal/StitchAuthImpl";
3034
import StitchBrowserAppRoutes from "../auth/internal/StitchBrowserAppRoutes";
35+
import StitchAuth from "../auth/StitchAuth";
36+
import StitchAuthListener from "../auth/StitchAuthListener";
37+
import StitchUser from "../auth/StitchUser";
3138
import StitchAppClient from "../StitchAppClient";
3239

33-
3440
/** @hidden */
35-
export default class StitchAppClientImpl implements StitchAppClient {
41+
export default class StitchAppClientImpl implements StitchAppClient,
42+
StitchAuthListener {
3643
public readonly auth: StitchAuthImpl;
3744

3845
private readonly coreClient: CoreStitchAppClient;
3946
private readonly info: StitchAppClientInfo;
4047
private readonly routes: StitchBrowserAppRoutes;
4148

49+
private serviceClients: CoreStitchServiceClient[];
50+
4251
public constructor(
4352
clientAppId: string,
4453
config: StitchAppClientConfiguration
@@ -64,42 +73,75 @@ export default class StitchAppClientImpl implements StitchAppClient {
6473
this.info
6574
);
6675
this.coreClient = new CoreStitchAppClient(this.auth, this.routes);
76+
this.serviceClients = [];
77+
this.auth.addSynchronousAuthListener(this);
6778
}
6879

6980
public getServiceClient<T>(
7081
factory: ServiceClientFactory<T> | NamedServiceClientFactory<T>,
7182
serviceName?: string
7283
): T {
7384
if (isServiceClientFactory(factory)) {
74-
return factory.getClient(
75-
new CoreStitchServiceClientImpl(this.auth, this.routes.serviceRoutes, ""),
76-
this.info
85+
const serviceClient = new CoreStitchServiceClientImpl(
86+
this.auth, this.routes.serviceRoutes, ""
7787
);
88+
this.bindServiceClient(serviceClient);
89+
return factory.getClient(serviceClient, this.info);
7890
} else {
91+
const serviceClient = new CoreStitchServiceClientImpl(
92+
this.auth,
93+
this.routes.serviceRoutes,
94+
serviceName!
95+
);
96+
this.bindServiceClient(serviceClient);
7997
return factory.getNamedClient(
80-
new CoreStitchServiceClientImpl(
81-
this.auth,
82-
this.routes.serviceRoutes,
83-
serviceName!
84-
),
98+
serviceClient,
8599
this.info
86100
);
87101
}
88102
}
89103

90104
public getGeneralServiceClient(serviceName: string): StitchServiceClient {
105+
const serviceClient = new CoreStitchServiceClientImpl(
106+
this.auth,
107+
this.routes.serviceRoutes,
108+
serviceName
109+
);
110+
this.bindServiceClient(serviceClient);
91111
return new StitchServiceClientImpl(
92-
new CoreStitchServiceClientImpl(
93-
this.auth,
94-
this.routes.serviceRoutes,
95-
serviceName
96-
)
112+
serviceClient
97113
);
98114
}
99115

100116
public callFunction(name: string, args: any[]): Promise<any> {
101117
return this.coreClient.callFunction(name, args);
102118
}
119+
120+
// note: this is the only rebind event we care about for JS. if we add
121+
// services in the future, or update existing services in such a way that
122+
// they'll need to rebind on other types of events, those handlers should be
123+
// added to this file.
124+
public onActiveUserChanged(
125+
_: StitchAuth,
126+
currentActiveUser: StitchUser | undefined,
127+
previousActiveUser: StitchUser | undefined
128+
) {
129+
this.onRebindEvent(new AuthRebindEvent({
130+
currentActiveUser,
131+
kind: AuthEventKind.ActiveUserChanged,
132+
previousActiveUser
133+
}))
134+
}
135+
136+
private bindServiceClient(coreStitchServiceClient: CoreStitchServiceClient) {
137+
this.serviceClients.push(coreStitchServiceClient);
138+
}
139+
140+
private onRebindEvent(rebindEvent: RebindEvent) {
141+
this.serviceClients.forEach(serviceClient => {
142+
serviceClient.onRebindEvent(rebindEvent);
143+
})
144+
}
103145
}
104146

105147
function isServiceClientFactory<T>(

Diff for: packages/core/sdk/__tests__/StreamTestUtils.ts

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Copyright 2018-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { instance, mock, when } from "ts-mockito";
18+
import { BaseEventStream, Decoder, Event, EventStream, Stream } from "../src";
19+
20+
// prevent Jest from thinking this file is an empty test suite
21+
it('should pass', () => {})
22+
23+
class TestStream <T> extends Stream<T> {
24+
public closeCalled: number
25+
26+
constructor(eventStream: EventStream, decoder: Decoder<T>) {
27+
super(eventStream, decoder)
28+
this.closeCalled = 0;
29+
}
30+
31+
public close() {
32+
this.closeCalled += 1;
33+
super.close();
34+
}
35+
}
36+
37+
/**
38+
* Utility methods for working with streams in tests by creating a real
39+
* {@link Stream} object with a mocked {@link EventStream} underneath.
40+
*/
41+
class StreamTestUtils {
42+
public static createStream<T>(
43+
decoder: Decoder<T> | undefined,
44+
...content: string[]
45+
): Stream<T> {
46+
return this.doCreateStream(decoder, true, content);
47+
}
48+
49+
public static createClosedStream<T>(
50+
decoder: Decoder<T> | undefined,
51+
...content: string[]
52+
): Stream<T> {
53+
return this.doCreateStream(decoder, false, content);
54+
}
55+
56+
private static doCreateStream<T>(
57+
decoder: Decoder<T> | undefined,
58+
open: boolean,
59+
streamEvents: string[],
60+
): Stream<T> {
61+
const eventStream = mock(BaseEventStream);
62+
63+
when(eventStream.isOpen()).thenReturn(open);
64+
65+
if (open) {
66+
when(eventStream.nextEvent()).thenResolve(
67+
...streamEvents.map(s => new Event(Event.MESSAGE_EVENT, s))
68+
);
69+
}
70+
71+
return new TestStream(instance(eventStream), decoder)
72+
}
73+
}
74+
75+
export {
76+
StreamTestUtils,
77+
TestStream
78+
}

0 commit comments

Comments
 (0)