• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

project-slippi / slippi-js / 19311487078

12 Nov 2025 08:46PM UTC coverage: 82.538% (-1.1%) from 83.652%
19311487078

Pull #149

github

vinceau
mark enet and reconnect-core as optional dependencies
Pull Request #149: Fix broken enet loading in CJS bundle

668 of 867 branches covered (77.05%)

Branch coverage included in aggregate %.

3 of 21 new or added lines in 2 files covered. (14.29%)

9 existing lines in 1 file now uncovered.

1856 of 2191 relevant lines covered (84.71%)

246813.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

11.32
/src/console/dolphinConnection.ts
1
import { EventEmitter } from "events";
24✔
2

3
import { loadEnetModule } from "./loadEnetModule";
24✔
4
import type { Connection, ConnectionDetails, ConnectionSettings } from "./types";
5
import { ConnectionEvent, ConnectionStatus, Ports } from "./types";
24✔
6

7
const MAX_PEERS = 32;
24✔
8

9
export enum DolphinMessageType {
24✔
10
  CONNECT_REPLY = "connect_reply",
24✔
11
  GAME_EVENT = "game_event",
24✔
12
  START_GAME = "start_game",
24✔
13
  END_GAME = "end_game",
24✔
14
}
15

16
export class DolphinConnection extends EventEmitter implements Connection {
24✔
17
  private ipAddress: string;
18
  private port: number;
19
  private connectionStatus = ConnectionStatus.DISCONNECTED;
×
20
  private gameCursor = 0;
×
21
  private nickname = "unknown";
×
22
  private version = "";
×
23
  private peer: any | null = null;
×
24
  private client: any | null;
25

26
  public constructor() {
27
    super();
×
28
    this.ipAddress = "0.0.0.0";
×
29
    this.port = Ports.DEFAULT;
×
30
  }
31

32
  /**
33
   * @returns The current connection status.
34
   */
35
  public getStatus(): ConnectionStatus {
36
    return this.connectionStatus;
×
37
  }
38

39
  /**
40
   * @returns The IP address and port of the current connection.
41
   */
42
  public getSettings(): ConnectionSettings {
43
    return {
×
44
      ipAddress: this.ipAddress,
45
      port: this.port,
46
    };
47
  }
48

49
  public getDetails(): ConnectionDetails {
50
    return {
×
51
      consoleNick: this.nickname,
52
      gameDataCursor: this.gameCursor,
53
      version: this.version,
54
    };
55
  }
56

57
  public async connect(ip: string, port: number): Promise<void> {
58
    console.log(`Connecting to: ${ip}:${port}`);
×
59
    this.ipAddress = ip;
×
60
    this.port = port;
×
61

NEW
62
    const enet = await loadEnetModule();
×
63
    // Create the enet client
64
    this.client = enet.createClient({ peers: MAX_PEERS, channels: 3, down: 0, up: 0 }, (err) => {
×
65
      if (err) {
×
66
        console.error(err);
×
67
        return;
×
68
      }
69
    });
70

71
    this.peer = this.client.connect(
×
72
      {
73
        address: this.ipAddress,
74
        port: this.port,
75
      },
76
      3,
77
      1337, // Data to send, not sure what this is or what this represents
78
      (err: any, newPeer: any) => {
79
        if (err) {
×
80
          console.error(err);
×
81
          return;
×
82
        }
83

84
        newPeer.ping();
×
85
        this.emit(ConnectionEvent.CONNECT);
×
86
        this._setStatus(ConnectionStatus.CONNECTED);
×
87
      },
88
    );
89

90
    this.peer.on("connect", () => {
×
91
      // Reset the game cursor to the beginning of the game. Do we need to do this or
92
      // should it just continue from where it left off?
93
      this.gameCursor = 0;
×
94

95
      const request = {
×
96
        type: "connect_request",
97
        cursor: this.gameCursor,
98
      };
99
      const packet = new enet.Packet(JSON.stringify(request), enet.PACKET_FLAG.RELIABLE);
×
100
      this.peer.send(0, packet);
×
101
    });
102

103
    this.peer.on("message", (packet: any) => {
×
104
      const data = packet.data();
×
105
      if (data.length === 0) {
×
106
        return;
×
107
      }
108

109
      const dataString = data.toString("ascii");
×
110
      const message = JSON.parse(dataString);
×
111
      const { dolphin_closed } = message;
×
112
      if (dolphin_closed) {
×
113
        // We got a disconnection request
114
        this.disconnect();
×
115
        return;
×
116
      }
117
      this.emit(ConnectionEvent.MESSAGE, message);
×
118
      switch (message.type) {
×
119
        case DolphinMessageType.CONNECT_REPLY:
120
          this.connectionStatus = ConnectionStatus.CONNECTED;
×
121
          this.gameCursor = message.cursor;
×
122
          this.nickname = message.nick;
×
123
          this.version = message.version;
×
124
          this.emit(ConnectionEvent.HANDSHAKE, this.getDetails());
×
125
          break;
×
126
        case DolphinMessageType.GAME_EVENT: {
127
          const { payload } = message;
×
128
          //TODO: remove after game start and end messages have been in stable Ishii for a bit
129
          if (!payload) {
×
130
            // We got a disconnection request
131
            this.disconnect();
×
132
            return;
×
133
          }
134

135
          this._updateCursor(message, dataString);
×
136

137
          const gameData = Buffer.from(payload, "base64");
×
138
          this._handleReplayData(gameData);
×
139
          break;
×
140
        }
141
        case DolphinMessageType.START_GAME: {
142
          this._updateCursor(message, dataString);
×
143
          break;
×
144
        }
145
        case DolphinMessageType.END_GAME: {
146
          this._updateCursor(message, dataString);
×
147
          break;
×
148
        }
149
      }
150
    });
151

152
    this.peer.on("disconnect", () => {
×
153
      this.disconnect();
×
154
    });
155

156
    this._setStatus(ConnectionStatus.CONNECTING);
×
157
  }
158

159
  public disconnect(): void {
160
    if (this.peer) {
×
161
      this.peer.disconnect();
×
162
      this.peer = null;
×
163
    }
164
    if (this.client) {
×
165
      this.client.destroy();
×
166
      this.client = null;
×
167
    }
168
    this._setStatus(ConnectionStatus.DISCONNECTED);
×
169
  }
170

171
  private _handleReplayData(data: Uint8Array): void {
172
    this.emit(ConnectionEvent.DATA, data);
×
173
  }
174

175
  private _setStatus(status: ConnectionStatus): void {
176
    // Don't fire the event if the status hasn't actually changed
177
    if (this.connectionStatus !== status) {
×
178
      this.connectionStatus = status;
×
179
      this.emit(ConnectionEvent.STATUS_CHANGE, this.connectionStatus);
×
180
    }
181
  }
182

183
  private _updateCursor(message: { cursor: number; next_cursor: number }, dataString: string): void {
184
    const { cursor, next_cursor } = message;
×
185

186
    if (this.gameCursor !== cursor) {
×
187
      const err = new Error(
×
188
        `Unexpected game data cursor. Expected: ${this.gameCursor} but got: ${cursor}. Payload: ${dataString}`,
189
      );
190
      console.warn(err);
×
191
      this.emit(ConnectionEvent.ERROR, err);
×
192
    }
193

194
    this.gameCursor = next_cursor;
×
195
  }
196
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc