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

panates / postgrejs / 9408791a-d115-4074-ba77-eb0683c78922

14 Sep 2024 08:58AM UTC coverage: 85.726% (-0.1%) from 85.832%
9408791a-d115-4074-ba77-eb0683c78922

push

circleci

erayhanoglu
chore: fixed lint issues

704 of 993 branches covered (70.9%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

25 existing lines in 1 file now uncovered.

2539 of 2790 relevant lines covered (91.0%)

824.31 hits per line

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

65.56
/src/protocol/pg-socket.ts
1
import crypto from 'crypto';
30✔
2
import net from 'net';
30✔
3
import promisify from 'putil-promisify';
30✔
4
import tls from 'tls';
30✔
5
import { ConnectionState } from '../constants.js';
30✔
6
import { ConnectionConfiguration } from '../interfaces/database-connection-params.js';
7
import { SafeEventEmitter } from '../safe-event-emitter.js';
30✔
8
import { Callback, Maybe } from '../types.js';
9
import { Backend } from './backend.js';
30✔
10
import { DatabaseError } from './database-error.js';
30✔
11
import { Frontend } from './frontend.js';
30✔
12
import { Protocol } from './protocol.js';
30✔
13
import { SASL } from './sasl.js';
30✔
14

15
const DEFAULT_PORT_NUMBER = 5432;
30✔
16
const COMMAND_RESULT_PATTERN = /^([^\d]+)(?: (\d+)(?: (\d+))?)?$/;
30✔
17

18
export type CaptureCallback = (
19
  code: Protocol.BackendMessageCode,
20
  msg: any,
21
  done: (err: Maybe<Error>, result?: any) => void,
22
) => void | Promise<void>;
23

24
export interface SocketError extends Error {
25
  code: string;
26
}
27

28
export class PgSocket extends SafeEventEmitter {
30✔
29
  private _state = ConnectionState.CLOSED;
87✔
30
  private _socket?: net.Socket;
31
  private _backend = new Backend();
87✔
32
  private _frontend: Frontend;
33
  private _sessionParameters: Record<string, string> = {};
87✔
34
  private _saslSession?: SASL.Session;
35
  private _processID?: number;
36
  private _secretKey?: number;
37

38
  constructor(public options: ConnectionConfiguration) {
87✔
39
    super();
87✔
40
    this._frontend = new Frontend({ buffer: options.buffer });
87✔
41
    this.setMaxListeners(99);
87✔
42
  }
43

44
  get state(): ConnectionState {
45
    if (!this._socket || this._socket.destroyed) this._state = ConnectionState.CLOSED;
3,828✔
46
    return this._state;
3,828✔
47
  }
48

49
  get processID(): Maybe<number> {
50
    return this._processID;
3✔
51
  }
52

53
  get secretKey(): Maybe<number> {
54
    return this._secretKey;
3✔
55
  }
56

57
  get sessionParameters(): Record<string, string> {
58
    return this._sessionParameters;
×
59
  }
60

61
  connect() {
62
    if (this._socket) return;
87!
63
    this._state = ConnectionState.CONNECTING;
87✔
64
    const options = this.options;
87✔
65
    const socket = (this._socket = new net.Socket());
87✔
66

67
    const errorHandler = (err: Error) => {
87✔
68
      this._state = ConnectionState.CLOSED;
×
69
      this._removeListeners();
×
70
      this._reset();
×
71
      socket.destroy();
×
72
      this._socket = undefined;
×
73
      this.emit('error', err);
×
74
    };
75

76
    const connectHandler = () => {
87✔
77
      socket.setTimeout(0);
87✔
78
      if (this.options.keepAlive || this.options.keepAlive == null) socket.setKeepAlive(true);
87✔
79
      socket.write(this._frontend.getSSLRequestMessage());
87✔
80
      socket.once('data', x => {
87✔
81
        this._removeListeners();
87✔
82
        if (x.toString() === 'S') {
87!
83
          const tslOptions = { ...options.ssl, socket };
×
84
          if (options.host && net.isIP(options.host) === 0) tslOptions.servername = options.host;
×
85
          const tlsSocket = (this._socket = tls.connect(tslOptions));
×
86
          tlsSocket.once('error', errorHandler);
×
87
          tlsSocket.once('secureConnect', () => {
×
88
            this._removeListeners();
×
89
            this._handleConnect();
×
90
          });
91
          return;
×
92
        }
93
        if (x.toString() === 'N') {
87✔
94
          if (options.requireSSL) {
87!
95
            return errorHandler(new Error('Server does not support SSL connections'));
×
96
          }
97
          this._removeListeners();
87✔
98
          this._handleConnect();
87✔
99
          return;
87✔
100
        }
101
        return errorHandler(new Error('There was an error establishing an SSL connection'));
×
102
      });
103
    };
104

105
    socket.setNoDelay(true);
87✔
106
    socket.setTimeout(options.connectTimeoutMs || 30000, () => errorHandler(new Error('Connection timed out')));
87✔
107
    socket.once('error', errorHandler);
87✔
108
    socket.once('connect', connectHandler);
87✔
109

110
    this.emit('connecting');
87✔
111
    if (options.host && options.host.startsWith('/')) socket.connect(options.host);
87!
112
    else socket.connect(options.port || DEFAULT_PORT_NUMBER, options.host || 'localhost');
87!
113
  }
114

115
  close(): void {
116
    if (!this._socket || this._socket.destroyed) {
87!
117
      this._state = ConnectionState.CLOSED;
×
118
      this._socket = undefined;
×
119
      this._reset();
×
120
      return;
×
121
    }
122
    if (this._state === ConnectionState.CLOSING) return;
87!
123
    const socket = this._socket;
87✔
124
    this._state = ConnectionState.CLOSING;
87✔
125
    this._removeListeners();
87✔
126
    socket.once('close', () => this._handleClose());
87✔
127
    socket.destroy();
87✔
128
  }
129

130
  sendParseMessage(args: Frontend.ParseMessageArgs, cb?: Callback): void {
131
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendParseMessage', args });
687!
132

133
    this._send(this._frontend.getParseMessage(args), cb);
687✔
134
  }
135

136
  sendBindMessage(args: Frontend.BindMessageArgs, cb?: Callback): void {
137
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendBindMessage', args });
684!
138

139
    this._send(this._frontend.getBindMessage(args), cb);
684✔
140
  }
141

142
  sendDescribeMessage(args: Frontend.DescribeMessageArgs, cb?: Callback): void {
143
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendDescribeMessage', args });
684!
144

145
    this._send(this._frontend.getDescribeMessage(args), cb);
684✔
146
  }
147

148
  sendExecuteMessage(args: Frontend.ExecuteMessageArgs, cb?: Callback): void {
149
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendDescribeMessage', args });
687!
150

151
    this._send(this._frontend.getExecuteMessage(args), cb);
687✔
152
  }
153

154
  sendCloseMessage(args: Frontend.CloseMessageArgs, cb?: Callback): void {
155
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendCloseMessage', args });
1,362!
156

157
    this._send(this._frontend.getCloseMessage(args), cb);
1,362✔
158
  }
159

160
  sendQueryMessage(sql: string, cb?: Callback): void {
161
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendQueryMessage', sql });
315!
162

163
    this._send(this._frontend.getQueryMessage(sql), cb);
315✔
164
  }
165

166
  sendFlushMessage(cb?: Callback): void {
167
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendFlushMessage' });
2,736!
168

169
    this._send(this._frontend.getFlushMessage(), cb);
2,736✔
170
  }
171

172
  sendTerminateMessage(cb?: Callback): void {
173
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendTerminateMessage' });
87!
174

175
    this._send(this._frontend.getTerminateMessage(), cb);
87✔
176
  }
177

178
  sendSyncMessage(): void {
179
    if (this.listenerCount('debug')) this.emit('debug', { location: 'PgSocket.sendSyncMessage' });
2,049!
180

181
    this._send(this._frontend.getSyncMessage());
2,049✔
182
  }
183

184
  capture(callback: CaptureCallback): Promise<any> {
185
    if (this._state === ConnectionState.CLOSING || this._state === ConnectionState.CLOSED) {
5,100!
UNCOV
186
      return Promise.reject(new Error('Connection closed'));
×
187
    }
188
    if (this._state !== ConnectionState.READY) return Promise.reject(new Error('Connection is not ready'));
5,100!
189
    return new Promise((resolve, reject) => {
5,100✔
190
      const done = (err?: Error, result?: any) => {
5,100✔
191
        this.removeListener('close', closeHandler);
5,094✔
192
        this.removeListener('error', errorHandler);
5,094✔
193
        this.removeListener('message', msgHandler);
5,094✔
194
        if (err) reject(err);
5,094!
195
        else resolve(result);
5,094✔
196
      };
197
      const errorHandler = (err: Error) => {
5,100✔
198
        this.removeListener('close', closeHandler);
6✔
199
        this.removeListener('message', msgHandler);
6✔
200
        reject(err);
6✔
201
      };
202
      const closeHandler = () => {
5,100✔
UNCOV
203
        this.removeListener('error', errorHandler);
×
UNCOV
204
        this.removeListener('message', msgHandler);
×
UNCOV
205
        reject(new Error('Connection closed'));
×
206
      };
207
      const msgHandler = (code: Protocol.BackendMessageCode, msg: any) => {
5,100✔
208
        const x = callback(code, msg, done);
10,377✔
209
        if (promisify.isPromise(x)) (x as Promise<void>).catch(err => done(err));
10,377✔
210
      };
211
      this.once('close', closeHandler);
5,100✔
212
      this.once('error', errorHandler);
5,100✔
213
      this.on('message', msgHandler);
5,100✔
214
    });
215
  }
216

217
  protected _removeListeners(): void {
218
    if (!this._socket) return;
261!
219
    this._socket.removeAllListeners('error');
261✔
220
    this._socket.removeAllListeners('connect');
261✔
221
    this._socket.removeAllListeners('data');
261✔
222
    this._socket.removeAllListeners('close');
261✔
223
  }
224

225
  protected _reset(): void {
226
    this._backend.reset();
168✔
227
    this._sessionParameters = {};
168✔
228
    this._processID = undefined;
168✔
229
    this._secretKey = undefined;
168✔
230
    this._saslSession = undefined;
168✔
231
  }
232

233
  protected _handleConnect(): void {
234
    const socket = this._socket;
87✔
235
    if (!socket) return;
87!
236
    this._state = ConnectionState.AUTHORIZING;
87✔
237
    this._reset();
87✔
238
    socket.on('data', (data: Buffer) => this._handleData(data));
5,215✔
239
    socket.on('error', (err: SocketError) => this._handleError(err));
87✔
240
    socket.on('close', () => this._handleClose());
87✔
241
    this._send(
87✔
242
      this._frontend.getStartupMessage({
243
        user: this.options.user || 'postgres',
87!
244
        database: this.options.database || '',
87!
245
      }),
246
    );
247
  }
248

249
  protected _handleClose(): void {
250
    this._reset();
81✔
251
    this._socket = undefined;
81✔
252
    this._state = ConnectionState.CLOSED;
81✔
253
    this.emit('close');
81✔
254
  }
255

256
  protected _handleError(err: unknown): void {
UNCOV
257
    if (this._state !== ConnectionState.READY) {
×
UNCOV
258
      this._socket?.end();
×
259
    }
UNCOV
260
    this.emit('error', err);
×
261
  }
262

263
  protected _handleData(data: Buffer): void {
264
    this._backend.parse(data, (code: Protocol.BackendMessageCode, payload?: any) => {
5,215✔
265
      try {
11,841✔
266
        switch (code) {
11,841✔
267
          case Protocol.BackendMessageCode.Authentication:
268
            this._handleAuthenticationMessage(payload);
87✔
269
            break;
87✔
270
          case Protocol.BackendMessageCode.ErrorResponse:
271
            this.emit('error', new DatabaseError(payload));
6✔
272
            break;
6✔
273
          case Protocol.BackendMessageCode.NoticeResponse:
274
            this.emit('notice', payload);
36✔
275
            break;
36✔
276
          case Protocol.BackendMessageCode.NotificationResponse:
277
            this.emit('notification', payload);
18✔
278
            break;
18✔
279
          case Protocol.BackendMessageCode.ParameterStatus:
280
            this._handleParameterStatus(payload as Protocol.ParameterStatusMessage);
1,137✔
281
            break;
1,137✔
282
          case Protocol.BackendMessageCode.BackendKeyData:
283
            this._handleBackendKeyData(payload as Protocol.BackendKeyDataMessage);
87✔
284
            break;
87✔
285
          case Protocol.BackendMessageCode.ReadyForQuery:
286
            if (this._state !== ConnectionState.READY) {
2,451✔
287
              this._state = ConnectionState.READY;
87✔
288
              this.emit('ready');
87✔
289
            } else this.emit('message', code, payload);
2,364✔
290
            break;
2,451✔
291
          case Protocol.BackendMessageCode.CommandComplete: {
292
            const msg = this._handleCommandComplete(payload);
2,337✔
293
            this.emit('message', code, msg);
2,337✔
294
            break;
2,337✔
295
          }
296
          default:
297
            this.emit('message', code, payload);
5,682✔
298
        }
299
      } catch (e) {
UNCOV
300
        this._handleError(e);
×
301
      }
302
    });
303
  }
304

305
  protected _resolvePassword(cb: (password: string) => void): void {
UNCOV
306
    (async (): Promise<void> => {
×
307
      const pass = typeof this.options.password === 'function' ? await this.options.password() : this.options.password;
×
UNCOV
308
      cb(pass || '');
×
309
    })().catch(err => this._handleError(err));
×
310
  }
311

312
  protected _handleAuthenticationMessage(msg?: any): void {
313
    if (!msg) {
87✔
314
      this.emit('authenticate');
87✔
315
      return;
87✔
316
    }
317

318
    switch (msg.kind) {
×
319
      case Protocol.AuthenticationMessageKind.CleartextPassword:
UNCOV
320
        this._resolvePassword(password => {
×
321
          this._send(this._frontend.getPasswordMessage(password));
×
322
        });
323
        break;
×
324
      case Protocol.AuthenticationMessageKind.MD5Password:
UNCOV
325
        this._resolvePassword(password => {
×
326
          const md5 = (x: any) => crypto.createHash('md5').update(x, 'utf8').digest('hex');
×
327
          const l = md5(password + this.options.user);
×
328
          const r = md5(Buffer.concat([Buffer.from(l), msg.salt]));
×
UNCOV
329
          const pass = 'md5' + r;
×
UNCOV
330
          this._send(this._frontend.getPasswordMessage(pass));
×
331
        });
332
        break;
×
333
      case Protocol.AuthenticationMessageKind.SASL: {
334
        if (!msg.mechanisms.includes('SCRAM-SHA-256')) {
×
335
          throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported');
×
336
        }
UNCOV
337
        const saslSession = (this._saslSession = SASL.createSession(this.options.user || '', 'SCRAM-SHA-256'));
×
338
        this._send(this._frontend.getSASLMessage(saslSession));
×
UNCOV
339
        break;
×
340
      }
341
      case Protocol.AuthenticationMessageKind.SASLContinue: {
342
        const saslSession = this._saslSession;
×
343
        if (!saslSession) throw new Error('SASL: Session not started yet');
×
344
        this._resolvePassword(password => {
×
345
          SASL.continueSession(saslSession, password, msg.data);
×
UNCOV
346
          const buf = this._frontend.getSASLFinalMessage(saslSession);
×
UNCOV
347
          this._send(buf);
×
348
        });
UNCOV
349
        break;
×
350
      }
351
      case Protocol.AuthenticationMessageKind.SASLFinal: {
UNCOV
352
        const session = this._saslSession;
×
UNCOV
353
        if (!session) throw new Error('SASL: Session not started yet');
×
UNCOV
354
        SASL.finalizeSession(session, msg.data);
×
UNCOV
355
        this._saslSession = undefined;
×
UNCOV
356
        break;
×
357
      }
358
      default:
UNCOV
359
        break;
×
360
    }
361
  }
362

363
  protected _handleParameterStatus(msg: Protocol.ParameterStatusMessage): void {
364
    this._sessionParameters[msg.name] = msg.value;
1,137✔
365
  }
366

367
  protected _handleBackendKeyData(msg: Protocol.BackendKeyDataMessage): void {
368
    this._processID = msg.processID;
87✔
369
    this._secretKey = msg.secretKey;
87✔
370
  }
371

372
  protected _handleCommandComplete(msg: any): Protocol.CommandCompleteMessage {
373
    const m = msg.command && msg.command.match(COMMAND_RESULT_PATTERN);
2,337✔
374
    const result: Protocol.CommandCompleteMessage = {
2,337✔
375
      command: m[1],
376
    };
377
    if (m[3] != null) {
2,337✔
378
      result.oid = parseInt(m[2], 10);
1,311✔
379
      result.rowCount = parseInt(m[3], 10);
1,311✔
380
    } else if (m[2]) result.rowCount = parseInt(m[2], 10);
1,026✔
381
    return result;
2,337✔
382
  }
383

384
  protected _send(data: Buffer, cb?: Callback): void {
385
    if (this._socket && this._socket.writable) {
9,372✔
386
      this._socket.write(data, cb);
9,372✔
387
    }
388
  }
389
}
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