node-iracing-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,401 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.constants = exports.IRacingClient = void 0;
7
+ const events_1 = require("events");
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Resolve and load the native binding from prebuilds or local build locations.
11
+ * @returns The loaded native binding module.
12
+ * @throws The last load error if no candidate path loads successfully.
13
+ */
14
+ const binding = (() => {
15
+ let lastError;
16
+ try {
17
+ const load = require('node-gyp-build');
18
+ return load(path_1.default.join(__dirname, '..'));
19
+ }
20
+ catch (error) {
21
+ lastError = error;
22
+ }
23
+ const candidates = ['./build/Release/irsdk_native.node', './build/Debug/irsdk_native.node'];
24
+ // Attempt both release and debug outputs, keeping the last failure for context.
25
+ for (const candidate of candidates) {
26
+ try {
27
+ return require(candidate);
28
+ }
29
+ catch (error) {
30
+ lastError = error;
31
+ }
32
+ }
33
+ // Re-throw the last error so callers can see the most relevant failure.
34
+ throw lastError;
35
+ })();
36
+ /**
37
+ * Provide an empty numeric enum table for missing native constants.
38
+ * @returns A new empty mapping object.
39
+ */
40
+ const emptyEnum = () => ({});
41
+ // Use native constants when available; otherwise default to empty tables.
42
+ const constants = binding.constants ?? {
43
+ BroadcastMsg: emptyEnum(),
44
+ ChatCommandMode: emptyEnum(),
45
+ PitCommandMode: emptyEnum(),
46
+ TelemCommandMode: emptyEnum(),
47
+ FFBCommandMode: emptyEnum(),
48
+ CameraState: emptyEnum(),
49
+ ReplaySearchMode: emptyEnum(),
50
+ ReplayPositionMode: emptyEnum(),
51
+ ReplayStateMode: emptyEnum(),
52
+ ReloadTexturesMode: emptyEnum(),
53
+ VideoCaptureMode: emptyEnum(),
54
+ CameraFocusMode: emptyEnum()
55
+ };
56
+ exports.constants = constants;
57
+ class IRacingClient extends events_1.EventEmitter {
58
+ _pollIntervalMs;
59
+ _waitTimeoutMs;
60
+ _telemetryVars;
61
+ _useAllTelemetry;
62
+ _emitSessionOnConnect;
63
+ _timer;
64
+ _connected;
65
+ _lastSessionUpdate;
66
+ /**
67
+ * Create a new telemetry client with optional polling configuration.
68
+ * @param options.pollIntervalMs Poll interval in milliseconds.
69
+ * @param options.waitTimeoutMs Wait timeout in milliseconds passed to the native wait.
70
+ * @param options.telemetryVariables Names of telemetry variables to read on each tick.
71
+ * @param options.emitSessionOnConnect Emit session payload immediately on connect.
72
+ * @returns A new IRacingClient instance.
73
+ */
74
+ constructor(options = {}) {
75
+ super();
76
+ const { pollIntervalMs = 16, waitTimeoutMs = 0, telemetryVariables, emitSessionOnConnect = true } = options;
77
+ const hasTelemetryOptions = Object.prototype.hasOwnProperty.call(options, 'telemetryVariables');
78
+ // Validate and normalize options to known-safe defaults.
79
+ this._pollIntervalMs = Number.isFinite(pollIntervalMs) ? pollIntervalMs : 16;
80
+ this._waitTimeoutMs = Number.isFinite(waitTimeoutMs) ? waitTimeoutMs : 0;
81
+ this._telemetryVars = Array.isArray(telemetryVariables) ? telemetryVariables.slice() : [];
82
+ this._useAllTelemetry = !hasTelemetryOptions;
83
+ this._emitSessionOnConnect = emitSessionOnConnect !== false;
84
+ // Initialize runtime state.
85
+ this._timer = null;
86
+ this._connected = false;
87
+ this._lastSessionUpdate = -1;
88
+ }
89
+ /**
90
+ * Override the telemetry variables to read on each poll.
91
+ * @param names List of telemetry variable names.
92
+ * @returns void
93
+ */
94
+ setTelemetryVariables(names) {
95
+ // Copy the array to avoid external mutation.
96
+ this._telemetryVars = Array.isArray(names) ? names.slice() : [];
97
+ this._useAllTelemetry = false;
98
+ }
99
+ /**
100
+ * Query whether iRacing is currently connected.
101
+ * @returns True if connected.
102
+ */
103
+ isConnected() {
104
+ return binding.isConnected();
105
+ }
106
+ /**
107
+ * Read the native iRacing status id.
108
+ * @returns Numeric status id.
109
+ */
110
+ getStatusId() {
111
+ return binding.getStatusId();
112
+ }
113
+ /**
114
+ * Retrieve the latest session info as a structured object.
115
+ * @returns Session info object or null if unavailable.
116
+ */
117
+ getSessionInfoObj() {
118
+ return binding.getSessionInfoObj();
119
+ }
120
+ /**
121
+ * Read telemetry variables by name.
122
+ * @param names Optional list of names; defaults to configured list.
123
+ * @returns Telemetry data mapping.
124
+ */
125
+ readVars(names) {
126
+ // Fall back to the configured names if none are provided.
127
+ const vars = Array.isArray(names) ? names : this._telemetryVars;
128
+ return binding.readVars(vars);
129
+ }
130
+ /**
131
+ * Read all available telemetry variables.
132
+ * @returns Telemetry data mapping or null when unavailable.
133
+ */
134
+ readAllVars() {
135
+ return binding.readAllVars();
136
+ }
137
+ /**
138
+ * Get telemetry variable headers (name, type, count, etc).
139
+ * @returns Array of variable headers.
140
+ */
141
+ getVarHeaders() {
142
+ return binding.getVarHeaders();
143
+ }
144
+ /**
145
+ * Read a single telemetry variable value.
146
+ * @param name Telemetry variable name.
147
+ * @param entry Optional array index for multi-value variables.
148
+ * @returns The telemetry value.
149
+ */
150
+ getVarValue(name, entry) {
151
+ return binding.getVarValue(name, entry);
152
+ }
153
+ /**
154
+ * Send a raw broadcast message with optional third parameter.
155
+ * @param msg Broadcast message id.
156
+ * @param var1 First parameter (number or string depending on message).
157
+ * @param var2 Second parameter.
158
+ * @param var3 Optional third parameter.
159
+ * @returns void
160
+ */
161
+ broadcastMsg(msg, var1, var2, var3) {
162
+ // Use the 3-argument native call only when a valid third value is provided.
163
+ if (Number.isFinite(var3)) {
164
+ binding.broadcastMsg(msg, var1, var2, var3);
165
+ return;
166
+ }
167
+ binding.broadcastMsg(msg, var1, var2);
168
+ }
169
+ /**
170
+ * Send a broadcast message that expects a float value parameter.
171
+ * @param msg Broadcast message id.
172
+ * @param var1 First parameter (number or string depending on message).
173
+ * @param value Float value parameter.
174
+ * @returns void
175
+ */
176
+ broadcastMsgFloat(msg, var1, value) {
177
+ binding.broadcastMsg(msg, var1, value);
178
+ }
179
+ /**
180
+ * Switch camera by car position.
181
+ * @param carPos Car position index.
182
+ * @param group Camera group.
183
+ * @param camera Camera index.
184
+ * @returns void
185
+ */
186
+ switchCameraByPos(carPos, group, camera) {
187
+ this.broadcastMsg(constants.BroadcastMsg.CamSwitchPos, carPos, group, camera);
188
+ }
189
+ /**
190
+ * Switch camera by driver number.
191
+ * @param driverNum Driver number or string identifier.
192
+ * @param group Camera group.
193
+ * @param camera Camera index.
194
+ * @returns void
195
+ */
196
+ switchCameraByNum(driverNum, group, camera) {
197
+ this.broadcastMsg(constants.BroadcastMsg.CamSwitchNum, driverNum, group, camera);
198
+ }
199
+ /**
200
+ * Set the current camera state.
201
+ * @param cameraState Camera state enum value.
202
+ * @returns void
203
+ */
204
+ setCameraState(cameraState) {
205
+ this.broadcastMsg(constants.BroadcastMsg.CamSetState, cameraState, 0);
206
+ }
207
+ /**
208
+ * Control replay playback speed.
209
+ * @param speed Playback speed.
210
+ * @param slowMotion Slow motion flag value.
211
+ * @returns void
212
+ */
213
+ replaySetPlaySpeed(speed, slowMotion = 0) {
214
+ this.broadcastMsg(constants.BroadcastMsg.ReplaySetPlaySpeed, speed, slowMotion);
215
+ }
216
+ /**
217
+ * Set replay position by frame number.
218
+ * @param mode Position mode enum value.
219
+ * @param frameNumber Frame number.
220
+ * @returns void
221
+ */
222
+ replaySetPlayPosition(mode, frameNumber) {
223
+ // Split the 32-bit frame number into low/high 16-bit values.
224
+ const low = frameNumber & 0xffff;
225
+ const high = (frameNumber >> 16) & 0xffff;
226
+ this.broadcastMsg(constants.BroadcastMsg.ReplaySetPlayPosition, mode, low, high);
227
+ }
228
+ /**
229
+ * Search replay by mode.
230
+ * @param mode Replay search mode enum value.
231
+ * @returns void
232
+ */
233
+ replaySearch(mode) {
234
+ this.broadcastMsg(constants.BroadcastMsg.ReplaySearch, mode, 0);
235
+ }
236
+ /**
237
+ * Set the replay state.
238
+ * @param state Replay state enum value.
239
+ * @returns void
240
+ */
241
+ replaySetState(state) {
242
+ this.broadcastMsg(constants.BroadcastMsg.ReplaySetState, state, 0);
243
+ }
244
+ /**
245
+ * Reload textures for a car or all cars.
246
+ * @param mode Reload textures mode enum value.
247
+ * @param carIdx Optional car index.
248
+ * @returns void
249
+ */
250
+ reloadTextures(mode, carIdx = 0) {
251
+ this.broadcastMsg(constants.BroadcastMsg.ReloadTextures, mode, carIdx);
252
+ }
253
+ /**
254
+ * Send a chat command to iRacing.
255
+ * @param command Chat command enum value.
256
+ * @param subCommand Optional sub-command value.
257
+ * @returns void
258
+ */
259
+ sendChatCommand(command, subCommand = 0) {
260
+ this.broadcastMsg(constants.BroadcastMsg.ChatCommand, command, subCommand);
261
+ }
262
+ /**
263
+ * Send a pit command to iRacing.
264
+ * @param command Pit command enum value.
265
+ * @param parameter Optional command parameter.
266
+ * @returns void
267
+ */
268
+ sendPitCommand(command, parameter = 0) {
269
+ this.broadcastMsg(constants.BroadcastMsg.PitCommand, command, parameter);
270
+ }
271
+ /**
272
+ * Send a telemetry command to iRacing.
273
+ * @param command Telemetry command enum value.
274
+ * @returns void
275
+ */
276
+ sendTelemCommand(command) {
277
+ this.broadcastMsg(constants.BroadcastMsg.TelemCommand, command, 0);
278
+ }
279
+ /**
280
+ * Send a force feedback command with a float value.
281
+ * @param command FFB command enum value.
282
+ * @param value Float parameter.
283
+ * @returns void
284
+ */
285
+ sendFFBCommand(command, value) {
286
+ this.broadcastMsgFloat(constants.BroadcastMsg.FFBCommand, command, value);
287
+ }
288
+ /**
289
+ * Search replay by session time.
290
+ * @param sessionNum Session number.
291
+ * @param sessionTimeMs Session time in milliseconds.
292
+ * @returns void
293
+ */
294
+ replaySearchSessionTime(sessionNum, sessionTimeMs) {
295
+ // Split the 32-bit time into low/high 16-bit values.
296
+ const low = sessionTimeMs & 0xffff;
297
+ const high = (sessionTimeMs >> 16) & 0xffff;
298
+ this.broadcastMsg(constants.BroadcastMsg.ReplaySearchSessionTime, sessionNum, low, high);
299
+ }
300
+ /**
301
+ * Start or stop video capture.
302
+ * @param mode Video capture mode enum value.
303
+ * @returns void
304
+ */
305
+ videoCapture(mode) {
306
+ this.broadcastMsg(constants.BroadcastMsg.VideoCapture, mode, 0);
307
+ }
308
+ /**
309
+ * Start the polling loop if not already running.
310
+ * @returns void
311
+ */
312
+ start() {
313
+ // Avoid creating multiple intervals.
314
+ if (this._timer) {
315
+ return;
316
+ }
317
+ // Schedule a periodic tick to read iRacing data.
318
+ this._timer = setInterval(() => {
319
+ this._tick();
320
+ }, this._pollIntervalMs);
321
+ }
322
+ /**
323
+ * Stop the polling loop if running.
324
+ * @returns void
325
+ */
326
+ stop() {
327
+ if (this._timer) {
328
+ // Clear the interval and reset state.
329
+ clearInterval(this._timer);
330
+ this._timer = null;
331
+ }
332
+ }
333
+ /**
334
+ * Poll native state and emit events.
335
+ * @returns void
336
+ */
337
+ _tick() {
338
+ try {
339
+ // Wait for new data and read connection state.
340
+ const hadData = binding.waitForData(this._waitTimeoutMs);
341
+ const isConnected = binding.isConnected();
342
+ // Transition to connected state and optionally emit session payload.
343
+ if (isConnected && !this._connected) {
344
+ this._connected = true;
345
+ this.emit('connect');
346
+ if (this._emitSessionOnConnect) {
347
+ this._emitSessionUpdate();
348
+ }
349
+ // Transition to disconnected state.
350
+ }
351
+ else if (!isConnected && this._connected) {
352
+ this._connected = false;
353
+ this.emit('disconnect');
354
+ }
355
+ // Skip telemetry processing when not connected.
356
+ if (!isConnected) {
357
+ return;
358
+ }
359
+ // Only read telemetry when the native layer reports new data.
360
+ if (hadData) {
361
+ // Emit session update when it changes.
362
+ if (binding.wasSessionInfoUpdated()) {
363
+ this._emitSessionUpdate();
364
+ }
365
+ // Emit all telemetry variables, or only the configured subset.
366
+ if (this._useAllTelemetry) {
367
+ const telemetry = binding.readAllVars();
368
+ if (telemetry) {
369
+ this.emit('telemetry', telemetry);
370
+ }
371
+ }
372
+ else if (this._telemetryVars.length > 0) {
373
+ const telemetry = binding.readVars(this._telemetryVars);
374
+ this.emit('telemetry', telemetry);
375
+ }
376
+ }
377
+ }
378
+ catch (error) {
379
+ // Surface unexpected native errors to the consumer.
380
+ this.emit('error', error);
381
+ }
382
+ }
383
+ /**
384
+ * Emit the latest session payload if available.
385
+ * @returns void
386
+ */
387
+ _emitSessionUpdate() {
388
+ const sessionInfo = binding.getSessionInfoObj();
389
+ if (!sessionInfo) {
390
+ return;
391
+ }
392
+ // Track update count and emit a unified session payload.
393
+ this._lastSessionUpdate = binding.getSessionInfoUpdateCount();
394
+ const payload = {
395
+ updateCount: this._lastSessionUpdate,
396
+ sessionInfo
397
+ };
398
+ this.emit('session', payload);
399
+ }
400
+ }
401
+ exports.IRacingClient = IRacingClient;
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "node-iracing-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Node.js bindings for the iRacing SDK client with event-driven telemetry",
5
+ "main": "dist/index.js",
6
+ "gypfile": false,
7
+ "scripts": {
8
+ "build": "node-gyp rebuild && tsc -p tsconfig.json",
9
+ "build:clean": "node-gyp clean && node-gyp rebuild && tsc -p tsconfig.json",
10
+ "build:js": "tsc -p tsconfig.json",
11
+ "prebuild": "prebuildify --napi --strip",
12
+ "prepublishOnly": "npm run build:js"
13
+ },
14
+ "license": "BSD-3-Clause",
15
+ "dependencies": {
16
+ "node-gyp-build": "^4.8.1"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node-iracing-sdk": "file:../types",
20
+ "@types/node": "^22.0.0",
21
+ "prebuildify": "^6.0.0",
22
+ "typescript": "^5.6.0"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "prebuilds",
27
+ "binding.gyp",
28
+ "README.md",
29
+ "LICENSE"
30
+ ]
31
+ }