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

fvdm / nodejs-ns-api / 21319910708

30 Nov 2025 04:37AM UTC coverage: 96.875% (-1.6%) from 98.438%
21319910708

push

github

web-flow
Chore(github): Bump github/codeql-action from 3 to 4 (#43)

30 of 33 branches covered (90.91%)

62 of 64 relevant lines covered (96.88%)

4.34 hits per line

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

96.88
/nsapi.js
1
/*
2
Name:       nsapi.js - Unofficial NodeJS module for Nederlandse Spoorwegen API
3
Author:     Franklin (https://frankl.in)
4
Source:     https://github.com/fvdm/nodejs-ns-api
5
API Docs:   https://apiportal.ns.nl
6
License:    Unlicense (Public Domain, see LICENSE file)
7
*/
8

9

10
module.exports = class NSAPI {
1✔
11

12
  /**
13
   * Configuration
14
   *
15
   * @param   {string}  key             Primary API key
16
   * @param   {number}  [timeout=5000]  Request timeout in ms
17
   */
18

19
  constructor ( {
20

21
    key,
22
    timeout = 8000,
×
23

24
  } ) {
25

26
    this._config = {
2✔
27
      key,
28
      timeout,
29
    };
30

31
  }
32

33

34
  /**
35
   * Talk to the API
36
   *
37
   * @param   {string}  path          Method path
38
   * @param   {object}  [parameters]  Request details
39
   *
40
   * @return  {Promise<object>}
41
   */
42

43
  async _request ( { path, parameters } ) {
44
    let url = `https://gateway.apiportal.ns.nl${path}`;
23✔
45
    const params = new URLSearchParams( parameters );
23✔
46

47
    url += '?' + params.toString();
23✔
48

49
    const options = {
23✔
50
      method: 'GET',
51
      signal: AbortSignal.timeout( this._config.timeout ),
52
      headers: {
53
        'Accept': 'application/json',
54
        'Ocp-Apim-Subscription-Key': this._config.key,
55
      },
56
    };
57

58
    const res = await fetch( url, options );
23✔
59
    const data = await res.json();
22✔
60
    let error;
61

62
    // Normal API error
63
    if ( data.code && data.message ) {
22✔
64
      error = new Error( `API: ${data.message}` );
2✔
65
      error.code = data.code;
2✔
66
      error.reason = data.errors;
2✔
67
      throw error;
2✔
68
    }
69

70
    // API field error - hard to replicate
71
    /* istanbul ignore next */
72
    if ( data.fieldErrors && data.fieldErrors.length ) {
73
      error = new Error( 'API field error' );
74
      error.reason = data.fieldErrors;
75
      throw error;
76
    }
77

78
    // API error without message - hard to replicate
79
    /* istanbul ignore next */
80
    if ( data.errors && data.errors[0] ) {
81
      error = new Error( 'API error' );
82
      error.reason = data.errors;
83
      throw error;
84
    }
85

86
    // API server error
87
    if ( res.status >= 300 ) {
20✔
88
      error = new Error( 'API error' );
1✔
89
      error.statusCode = res.status;
1✔
90
      error.statusText = res.statusText;
1✔
91
      throw error;
1✔
92
    }
93

94
    // ok
95
    return data;
19✔
96
  }
97

98

99
  // ! REISINFORMATIE
100

101
  /**
102
   * Get a list of all stations
103
   *
104
   * @return  {Promise<array>}
105
   */
106

107
  async getAllStations () {
108
    const data = await this._request( {
2✔
109
      path: '/reisinformatie-api/api/v2/stations',
110
    } );
111

112
    return data.payload;
1✔
113
  }
114

115

116
  /**
117
   * Get arrivals for a station
118
   *
119
   * @param   {object}          parameters  Request parameters
120
   * @return  {Promise<array>}
121
   */
122

123
  async getArrivals ( parameters ) {
124
    if ( parameters.dateTime && ! ( parameters.dateTime instanceof Date ) ) {
3✔
125
      parameters.dateTime = new Date( parameters.dateTime ).toString();
1✔
126
    }
127

128
    const data = await this._request( {
3✔
129
      path: '/reisinformatie-api/api/v2/arrivals',
130
      parameters,
131
    } );
132

133
    return data.payload.arrivals;
3✔
134
  }
135

136

137
  /**
138
   * Get calamities
139
   *
140
   * @param   {object}          [parameters]  Request parameters
141
   * @return  {Promise<array>}
142
   */
143

144
  async getCalamities ( parameters = {} ) {
1✔
145
    const data = await this._request( {
1✔
146
      path: '/reisinformatie-api/api/v1/calamities',
147
      parameters,
148
    } );
149

150
    return data.meldingen;
×
151
  }
152

153

154
  /**
155
   * Get a list of departure times
156
   *
157
   * @param   {object}          parameters  Request parameters
158
   * @return  {Promise<array>}
159
   */
160

161
  async getDepartures ( parameters ) {
162
    if ( parameters.dateTime && ! ( parameters.dateTime instanceof Date ) ) {
2✔
163
      parameters.dateTime = new Date( parameters.dateTime ).toISOString();
1✔
164
    }
165

166
    const data = await this._request( {
2✔
167
      path: '/reisinformatie-api/api/v2/departures',
168
      parameters,
169
    } );
170

171
    return data.payload.departures;
2✔
172
  }
173

174

175
  /**
176
   * Get details about one disruption
177
   *
178
   * @param   {object}  parameters       Request parameters
179
   * @param   {string}  parameters.type  Disruption type
180
   * @param   {string}  parameters.id    Disruption ID
181
   *
182
   * @return  {Promise<object>}
183
   */
184

185
  async getDisruption ( parameters ) {
186
    const type = parameters.type;
1✔
187
    const id = parameters.id;
1✔
188

189
    delete parameters.type;
1✔
190
    delete parameters.id;
1✔
191

192
    const data = await this._request( {
1✔
193
      path: `/reisinformatie-api/api/v3/disruptions/${type}/${id}`,
194
      parameters,
195
    } );
196

197
    return data;
1✔
198
  }
199

200

201
  /**
202
   * Get a list of disruptions
203
   *
204
   * @param   {object}          parameters  Request parameters
205
   * @return  {Promise<array>}
206
   */
207

208
  async getDisruptions ( parameters = {} ) {
1✔
209
    parameters.actual = parameters.actual === true ? 'true' : 'false';
2✔
210

211
    const data = await this._request( {
2✔
212
      path: '/reisinformatie-api/api/v3/disruptions',
213
      parameters,
214
    } );
215

216
    return data;
2✔
217
  }
218

219

220
  /**
221
   * Get a list of disruptions for a specific station
222
   *
223
   * @param   {object}           parameters       Request parameters
224
   * @param   {object}           parameters.code  UICCode or station code
225
   *
226
   * @return  {Promise<object>}
227
   */
228

229
  async getStationDisruption ( parameters ) {
230
    const code = parameters.code;
1✔
231

232
    delete parameters.code;
1✔
233

234
    const data = await this._request( {
1✔
235
      path: `/reisinformatie-api/api/v3/disruptions/station/${code}`,
236
      parameters,
237
    } );
238

239
    return data;
1✔
240
  }
241

242

243
  /**
244
   * Reconstruct a trip if possible using the given reconCtx
245
   *
246
   * @param   {object}          parameters  Request parameters
247
   * @return  {Promise<array>}
248
   */
249

250
  async getTrip ( parameters = {} ) {
1✔
251

252
    /*
253
    if ( parameters.date && !( parameters.date instanceof Date ) ) {
254
      parameters.date = new Date ( parameters.date ).toISOString();
255
    }
256
    */
257

258
    const data = await this._request( {
2✔
259
      path: '/reisinformatie-api/api/v3/trips/trip',
260
      parameters,
261
    } );
262

263
    return data;
1✔
264
  }
265

266

267
  /**
268
   * Searches for a travel advice with the specified options between the
269
   * possible backends (HARP, 9292 or PAS/AVG)
270
   *
271
   * For door-to-door trips you need a special API key
272
   *
273
   * @param   {object}          parameters  Request parameters
274
   * @return  {Promise<array>}
275
   */
276

277
  async getTrips ( parameters ) {
278
    if ( parameters.dateTime && ! ( parameters.dateTime instanceof Date ) ) {
2✔
279
      parameters.dateTime = new Date( parameters.dateTime ).toISOString();
1✔
280
    }
281

282
    const data = await this._request( {
2✔
283
      path: '/reisinformatie-api/api/v3/trips',
284
      parameters,
285
    } );
286

287
    return data.trips;
2✔
288
  }
289

290

291
  /**
292
   * Get pricing for travel between two stations
293
   *
294
   * @param   {object}          parameters  Request parameters
295
   * @return  {Promise<array>}
296
   */
297

298
  async getPrice ( parameters ) {
299
    // YYYY-MM-DD
300
    if ( parameters.date && ! ( parameters.date instanceof Date ) ) {
3✔
301
      parameters.date = new Date( parameters.date ).toISOString().split( 'T' )[0];
1✔
302
    }
303

304
    const data = await this._request( {
3✔
305
      path: '/reisinformatie-api/api/v2/price',
306
      parameters,
307
    } );
308

309
    return data.payload;
2✔
310
  }
311

312

313
  /**
314
   * Get information about a specific journey
315
   *
316
   * @param   {object}          Parameters  Request parameters
317
   * @return  {Promise<object>}
318
   */
319

320
  async getJourney ( parameters ) {
321
    if ( parameters.dateTime && ! ( parameters.date instanceof Date ) ) {
1!
322
      parameters.dateTime = new Date( parameters.dateTime ).toISOString();
×
323
    }
324

325
    const data = await this._request( {
1✔
326
      path: '/reisinformatie-api/api/v2/journey',
327
      parameters,
328
    } );
329

330
    return data.payload;
1✔
331
  }
332

333

334
  // ! PLACES
335

336
  /**
337
   * Get list of places
338
   *
339
   * @param   {object}  parameters  Request parameters
340
   * @return  {Promise<array>}
341
   */
342

343
  async placesList ( parameters ) {
344
    const data = await this._request( {
1✔
345
      path: '/places-api/v2/places',
346
      parameters,
347
    } );
348

349
    return data.payload;
1✔
350
  }
351

352

353
  /**
354
   * Get list of OV Fiets locations
355
   *
356
   * @param   {object}  parameters  Request parameters
357
   * @return  {Promise<array>}
358
   */
359

360
  async placesOvfiets ( parameters ) {
361
    const data = await this._request( {
1✔
362
      path: '/places-api/v2/ovfiets',
363
      parameters,
364
    } );
365

366
    return data.payload;
1✔
367
  }
368

369

370
  /**
371
   * Get place
372
   *
373
   * @param   {string}  type    Place type, ie. stationV2
374
   * @param   {string}  id      Place ID, ie. AMF
375
   * @param   {string}  [lang]  Response language
376
   *
377
   * @return  {Promise<object>}
378
   */
379

380
  async placesGet ( {
381
    type,
382
    id,
383
    lang = '',
1✔
384
  } ) {
385
    const data = await this._request( {
1✔
386
      path: `/places-api/v2/places/${type}/${id}`,
387
      parameters: {
388
        lang,
389
      },
390
    } );
391

392
    return data.payload;
1✔
393
  }
394

395
};
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