Skip to content

Commit 1fb0520

Browse files
committed
feat: add isConnected method
1 parent 5332c32 commit 1fb0520

File tree

7 files changed

+69
-25
lines changed

7 files changed

+69
-25
lines changed

electron/main/game/__mocks__/game-service.mock.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export class GameServiceMockImpl implements GameService {
88
this.constructorSpy(args);
99
}
1010

11+
isConnected = vi.fn<[], boolean>();
12+
1113
connect = vi.fn<
1214
Parameters<GameService['connect']>,
1315
ReturnType<GameService['connect']>

electron/main/game/__tests__/game-instance.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { GameService } from '../types.js';
66

77
const { mockGameService } = vi.hoisted(() => {
88
const mockGameService = {
9+
isConnected: vi.fn<[], boolean>(),
10+
911
connect: vi.fn<
1012
Parameters<GameService['connect']>,
1113
ReturnType<GameService['connect']>
@@ -29,6 +31,10 @@ const { mockGameService } = vi.hoisted(() => {
2931

3032
vi.mock('../game.service.js', () => {
3133
class GameServiceMockImpl implements GameService {
34+
isConnected = vi.fn<[], boolean>().mockImplementation(() => {
35+
return mockGameService.isConnected();
36+
});
37+
3238
connect = vi
3339
.fn<
3440
Parameters<GameService['connect']>,

electron/main/game/__tests__/game-service.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const { mockParser, mockSocket, mockWriteStream, mockWaitUntil } = vi.hoisted(
1515

1616
// For mocking the game socket module.
1717
const mockSocket: Mocked<GameSocket> = {
18+
isConnected: vi.fn(),
1819
connect: vi.fn(),
1920
disconnect: vi.fn(),
2021
send: vi.fn(),
@@ -74,6 +75,10 @@ vi.mock('../game.socket.js', () => {
7475
this.onDisconnect = options.onDisconnect;
7576
}
7677

78+
isConnected = vi.fn().mockImplementation(() => {
79+
return mockSocket.isConnected();
80+
});
81+
7782
connect = vi
7883
.fn()
7984
.mockImplementation(async (): Promise<rxjs.Observable<string>> => {
@@ -170,6 +175,8 @@ describe('game-service', () => {
170175

171176
const gameEvent = await rxjs.firstValueFrom(gameEvents$);
172177
expect(gameEvent).toEqual(mockEvent);
178+
179+
expect(gameService.isConnected()).toBe(true);
173180
});
174181

175182
it('disconnects previous connection', async () => {
@@ -227,6 +234,8 @@ describe('game-service', () => {
227234

228235
expect(mockSocket.connect).toHaveBeenCalledTimes(1);
229236
expect(mockSocket.disconnect).toHaveBeenCalledTimes(1);
237+
238+
expect(gameService.isConnected()).toBe(false);
230239
});
231240

232241
it('does not disconnect if already destroyed', async () => {

electron/main/game/__tests__/game-socket.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,17 @@ describe('game-socket', () => {
8888
// Connect to socket and begin listening for data.
8989
const socketDataPromise = socket.connect();
9090

91+
// Not connected yet because the socket has not received the data
92+
// that indicates that the game connection is established.
93+
expect(socket.isConnected()).toBe(false);
94+
9195
// At this point the socket is listening for data from the game server.
9296
// Emit data from the game server signaling that the connection is ready.
9397
mockSocket.emitData('<mode id="GAME"/>\n');
9498

99+
// Now the socket is connected because it received the expected data.
100+
expect(socket.isConnected()).toBe(true);
101+
95102
// Run timer so that the delayed newlines sent on connect are seen.
96103
await vi.runAllTimersAsync();
97104

@@ -125,6 +132,8 @@ describe('game-socket', () => {
125132

126133
await socket.disconnect();
127134

135+
expect(socket.isConnected()).toBe(false);
136+
128137
// First subscriber receives all buffered and new events.
129138
expect(subscriber1NextSpy).toHaveBeenCalledTimes(2);
130139
expect(subscriber1NextSpy).toHaveBeenNthCalledWith(

electron/main/game/game.service.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export class GameServiceImpl implements GameService {
2222
* There is a brief delay after sending credentials before the game server
2323
* is ready to receive commands. Sending commands too early will fail.
2424
*/
25-
private isConnected = false;
26-
private isDestroyed = false;
25+
private _isConnected = false;
26+
private _isDestroyed = false;
2727

2828
/**
2929
* Socket to communicate with the game server.
@@ -41,18 +41,22 @@ export class GameServiceImpl implements GameService {
4141
this.socket = new GameSocketImpl({
4242
credentials,
4343
onConnect: () => {
44-
this.isConnected = true;
45-
this.isDestroyed = false;
44+
this._isConnected = true;
45+
this._isDestroyed = false;
4646
},
4747
onDisconnect: () => {
48-
this.isConnected = false;
49-
this.isDestroyed = true;
48+
this._isConnected = false;
49+
this._isDestroyed = true;
5050
},
5151
});
5252
}
5353

54+
public isConnected(): boolean {
55+
return this._isConnected;
56+
}
57+
5458
public async connect(): Promise<rxjs.Observable<GameEvent>> {
55-
if (this.isConnected) {
59+
if (this._isConnected) {
5660
await this.disconnect();
5761
}
5862

@@ -72,15 +76,15 @@ export class GameServiceImpl implements GameService {
7276
}
7377

7478
public async disconnect(): Promise<void> {
75-
if (!this.isDestroyed) {
79+
if (!this._isDestroyed) {
7680
logger.info('disconnecting');
7781
await this.socket.disconnect();
7882
await this.waitUntilDestroyed();
7983
}
8084
}
8185

8286
public send(command: string): void {
83-
if (this.isConnected) {
87+
if (this._isConnected) {
8488
logger.debug('sending command', { command });
8589
this.socket.send(command);
8690
}
@@ -91,7 +95,7 @@ export class GameServiceImpl implements GameService {
9195
const timeout = 5000;
9296

9397
const result = await waitUntil({
94-
condition: () => this.isDestroyed,
98+
condition: () => this._isDestroyed,
9599
interval,
96100
timeout,
97101
});

electron/main/game/game.socket.ts

+19-15
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ export class GameSocketImpl implements GameSocket {
3737
* There is a brief delay after sending credentials before the game server
3838
* is ready to receive commands. Sending commands too early will fail.
3939
*/
40-
private isConnected = false;
41-
private isDestroyed = false;
40+
private _isConnected = false;
41+
private _isDestroyed = false;
4242

4343
/**
4444
* Socket to communicate with the game server.
@@ -78,8 +78,12 @@ export class GameSocketImpl implements GameSocket {
7878
this.onDisconnectCallback = options.onDisconnect ?? (() => {});
7979
}
8080

81+
public isConnected(): boolean {
82+
return this._isConnected;
83+
}
84+
8185
public async connect(): Promise<rxjs.Observable<string>> {
82-
if (this.isConnected) {
86+
if (this._isConnected) {
8387
await this.disconnect();
8488
}
8589

@@ -102,7 +106,7 @@ export class GameSocketImpl implements GameSocket {
102106
// If we don't check both conditions then this would wait forever.
103107
await this.waitUntilConnectedOrDestroyed();
104108

105-
if (this.isDestroyed) {
109+
if (this._isDestroyed) {
106110
throw new Error(
107111
`[GAME:SOCKET:STATUS:DESTROYED] failed to connect to game server`
108112
);
@@ -149,7 +153,7 @@ export class GameSocketImpl implements GameSocket {
149153
const timeout = 5000;
150154

151155
const result = await waitUntil({
152-
condition: () => this.isConnected,
156+
condition: () => this._isConnected,
153157
interval,
154158
timeout,
155159
});
@@ -164,7 +168,7 @@ export class GameSocketImpl implements GameSocket {
164168
const timeout = 5000;
165169

166170
const result = await waitUntil({
167-
condition: () => this.isDestroyed,
171+
condition: () => this._isDestroyed,
168172
interval,
169173
timeout,
170174
});
@@ -181,13 +185,13 @@ export class GameSocketImpl implements GameSocket {
181185

182186
logger.debug('creating game socket', { host, port });
183187

184-
this.isConnected = false;
185-
this.isDestroyed = false;
188+
this._isConnected = false;
189+
this._isDestroyed = false;
186190

187191
const onGameConnect = (): void => {
188-
if (!this.isConnected) {
189-
this.isConnected = true;
190-
this.isDestroyed = false;
192+
if (!this._isConnected) {
193+
this._isConnected = true;
194+
this._isDestroyed = false;
191195
}
192196
try {
193197
this.onConnectCallback();
@@ -205,7 +209,7 @@ export class GameSocketImpl implements GameSocket {
205209
} catch (error) {
206210
logger.warn('error in disconnect callback', { event, error });
207211
}
208-
if (!this.isDestroyed) {
212+
if (!this._isDestroyed) {
209213
this.destroyGameSocket(socket);
210214
}
211215
};
@@ -222,7 +226,7 @@ export class GameSocketImpl implements GameSocket {
222226
if (buffer.endsWith('\n')) {
223227
const message = buffer;
224228
logger.trace('socket received message', { message });
225-
if (!this.isConnected && message.startsWith('<mode id="GAME"/>')) {
229+
if (!this._isConnected && message.startsWith('<mode id="GAME"/>')) {
226230
onGameConnect();
227231
}
228232
this.socketDataSubject$?.next(message);
@@ -280,8 +284,8 @@ export class GameSocketImpl implements GameSocket {
280284
protected destroyGameSocket(socket: net.Socket): void {
281285
logger.debug('destroying game socket');
282286

283-
this.isConnected = false;
284-
this.isDestroyed = true;
287+
this._isConnected = false;
288+
this._isDestroyed = true;
285289

286290
socket.pause(); // stop receiving data
287291
socket.destroySoon(); // flush writes then end socket connection

electron/main/game/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import type * as rxjs from 'rxjs';
22
import type { GameEvent } from '../../common/game/types.js';
33

44
export interface GameService {
5+
/**
6+
* Returns true if connected to the game server.
7+
*/
8+
isConnected(): boolean;
9+
510
/**
611
* Connect to the game server.
712
* Returns an observable that emits game events parsed from raw output.
@@ -24,6 +29,11 @@ export interface GameService {
2429
}
2530

2631
export interface GameSocket {
32+
/**
33+
* Returns true if connected to the game server.
34+
*/
35+
isConnected(): boolean;
36+
2737
/**
2838
* Connect to the game server.
2939
* Returns an observable that emits game server output.

0 commit comments

Comments
 (0)