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/LICENSE +674 -0
- package/README.md +336 -0
- package/binding.gyp +39 -0
- package/dist/index.js +401 -0
- package/package.json +31 -0
- package/prebuilds/win32-x64/node-iracing-sdk.node +0 -0
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
|
+
}
|
|
Binary file
|