Fix Track.lookAt crash and fetchBuffer hang; bump to 2.0.1

- Track.lookAt: was calling client.user.lookAtEntity (undefined), now
  correctly routes to client.behavior.lookAtEntity — fixes unhandled
  promise rejections on every face-detection event
- connection: httpGet/httpGetStream had no socket timeout; added 15 s
  req.setTimeout so fetchBuffer/pipe reject instead of hanging forever
- connection: _txSend silently dropped commands when session not yet
  ready and returned a dead txId, causing callers to hang for the full
  timeout; now throws immediately with code NOT_READY

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paskooter
2026-04-25 12:45:48 -04:00
parent 3985b19338
commit 8c92e1c963
4 changed files with 14 additions and 6 deletions

2
API.md
View File

@@ -1,6 +1,6 @@
# rom-control # rom-control
Discord.js-style OOP client for the Jibo ROM WebSocket API (port 8160). Robust client for the Jibo ROM WebSocket API (port 8160).
**Requires:** Node.js ≥ 16, `ws` ^8.14.2 **Requires:** Node.js ≥ 16, `ws` ^8.14.2

View File

@@ -1,6 +1,6 @@
{ {
"name": "rom-control", "name": "rom-control",
"version": "2.0.0", "version": "2.0.1",
"description": "Discord.js-style OOP client for the Jibo ROM WebSocket API", "description": "Discord.js-style OOP client for the Jibo ROM WebSocket API",
"main": "./index.js", "main": "./index.js",
"exports": { "exports": {

View File

@@ -41,24 +41,30 @@ function httpPost(host, port, path, body) {
}); });
} }
function httpGet(host, port, path) { function httpGet(host, port, path, timeoutMs = 15000) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const chunks = []; const chunks = [];
const req = http.get({ host, port, path }, (res) => { const req = http.get({ host, port, path }, (res) => {
res.on('data', d => chunks.push(d)); res.on('data', d => chunks.push(d));
res.on('end', () => resolve(Buffer.concat(chunks))); res.on('end', () => resolve(Buffer.concat(chunks)));
}); });
req.setTimeout(timeoutMs, () => {
req.destroy(Object.assign(new Error('fetchMedia timed out'), { code: 'FETCH_TIMEOUT' }));
});
req.on('error', reject); req.on('error', reject);
}); });
} }
function httpGetStream(host, port, path, dest) { function httpGetStream(host, port, path, dest, timeoutMs = 15000) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = http.get({ host, port, path }, (res) => { const req = http.get({ host, port, path }, (res) => {
res.pipe(dest); res.pipe(dest);
res.on('end', resolve); res.on('end', resolve);
dest.on('close', () => req.destroy()); dest.on('close', () => req.destroy());
}); });
req.setTimeout(timeoutMs, () => {
req.destroy(Object.assign(new Error('fetchMediaStream timed out'), { code: 'FETCH_TIMEOUT' }));
});
req.on('error', reject); req.on('error', reject);
}); });
} }
@@ -323,7 +329,9 @@ class RomConnection extends EventEmitter {
if (this.ws && this.ws.readyState === WebSocket.OPEN) { if (this.ws && this.ws.readyState === WebSocket.OPEN) {
// Don't send any command except StartSession before the session ID arrives. // Don't send any command except StartSession before the session ID arrives.
// Commands sent with an empty SessionID are rejected by ROM with 403 Forbidden. // Commands sent with an empty SessionID are rejected by ROM with 403 Forbidden.
if (!this.sessionID && command.Type !== 'StartSession') return txId; if (!this.sessionID && command.Type !== 'StartSession') {
throw Object.assign(new Error(`Cannot send ${command.Type}: session not ready`), { code: 'NOT_READY' });
}
this._txCommands.set(txId, command.Type); this._txCommands.set(txId, command.Type);
if (this._txCommands.size > 60) { if (this._txCommands.size > 60) {

View File

@@ -14,7 +14,7 @@ class Track {
// Look at this entity. Resolves when the head reaches the target. // Look at this entity. Resolves when the head reaches the target.
async lookAt(track = true) { async lookAt(track = true) {
return this._client.user.lookAtEntity(this.id, track); return this._client.behavior.lookAtEntity(this.id, track);
} }
} }