@apocaliss92/nodelink-js 0.4.5 → 0.4.7
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/{DiagnosticsTools-55PR4WFD.js → DiagnosticsTools-UMN4C7SY.js} +2 -2
- package/dist/{chunk-WDFKIHM5.js → chunk-GKLOJJ34.js} +177 -29
- package/dist/chunk-GKLOJJ34.js.map +1 -0
- package/dist/{chunk-DEOMUWBN.js → chunk-TR3V5FTO.js} +15 -1
- package/dist/chunk-TR3V5FTO.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +187 -25
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +278 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +93 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DEOMUWBN.js.map +0 -1
- package/dist/chunk-WDFKIHM5.js.map +0 -1
- /package/dist/{DiagnosticsTools-55PR4WFD.js.map → DiagnosticsTools-UMN4C7SY.js.map} +0 -0
package/dist/cli/rtsp-server.cjs
CHANGED
|
@@ -1567,6 +1567,19 @@ var init_BaichuanVideoStream = __esm({
|
|
|
1567
1567
|
// Stateful AES decryptor for fragmented BcMedia packets (full_aes mode)
|
|
1568
1568
|
// In CFB mode, continuation frames must use the cipher state from previous frames.
|
|
1569
1569
|
aesStreamDecryptor = null;
|
|
1570
|
+
/**
|
|
1571
|
+
* Pending startup error stashed when emitSafeError is called before any
|
|
1572
|
+
* "error" listener is registered (e.g. camera returns 400 during start()).
|
|
1573
|
+
* The rfc4571-server's waitForKeyframe can consume this immediately instead
|
|
1574
|
+
* of waiting for the full keyframe timeout.
|
|
1575
|
+
*/
|
|
1576
|
+
_pendingStartupError;
|
|
1577
|
+
/** Consume and clear any pending startup error. */
|
|
1578
|
+
consumePendingStartupError() {
|
|
1579
|
+
const err = this._pendingStartupError;
|
|
1580
|
+
this._pendingStartupError = void 0;
|
|
1581
|
+
return err;
|
|
1582
|
+
}
|
|
1570
1583
|
emitSafeError(err) {
|
|
1571
1584
|
if (!this.active) {
|
|
1572
1585
|
this.logger?.warn?.(
|
|
@@ -1578,6 +1591,7 @@ var init_BaichuanVideoStream = __esm({
|
|
|
1578
1591
|
this.logger?.warn?.(
|
|
1579
1592
|
`[BaichuanVideoStream] Unhandled stream error: ${err.message}`
|
|
1580
1593
|
);
|
|
1594
|
+
this._pendingStartupError = err;
|
|
1581
1595
|
return;
|
|
1582
1596
|
}
|
|
1583
1597
|
this.emit("error", err);
|
|
@@ -10657,6 +10671,9 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
10657
10671
|
resendTimer;
|
|
10658
10672
|
hbTimer;
|
|
10659
10673
|
discoveryTid;
|
|
10674
|
+
// Track discovery-phase timers so close() can cancel them even if
|
|
10675
|
+
// discovery is still in progress (prevents ERR_SOCKET_DGRAM_NOT_RUNNING).
|
|
10676
|
+
discoveryTimers = [];
|
|
10660
10677
|
acceptSent = false;
|
|
10661
10678
|
lastAcceptAtMs;
|
|
10662
10679
|
ackScheduled = false;
|
|
@@ -10705,9 +10722,31 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
10705
10722
|
});
|
|
10706
10723
|
sock.on("error", (e) => this.emit("error", e));
|
|
10707
10724
|
sock.on("close", () => this.emit("close"));
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10725
|
+
const portRange = Array.from({ length: 500 }, (_, i) => 53500 + i);
|
|
10726
|
+
for (let i = portRange.length - 1; i > 0; i--) {
|
|
10727
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
10728
|
+
[portRange[i], portRange[j]] = [portRange[j], portRange[i]];
|
|
10729
|
+
}
|
|
10730
|
+
let bound = false;
|
|
10731
|
+
for (const port of portRange) {
|
|
10732
|
+
try {
|
|
10733
|
+
await new Promise((resolve, reject) => {
|
|
10734
|
+
sock.once("error", reject);
|
|
10735
|
+
sock.bind(port, "0.0.0.0", () => {
|
|
10736
|
+
sock.removeListener("error", reject);
|
|
10737
|
+
resolve();
|
|
10738
|
+
});
|
|
10739
|
+
});
|
|
10740
|
+
bound = true;
|
|
10741
|
+
break;
|
|
10742
|
+
} catch {
|
|
10743
|
+
}
|
|
10744
|
+
}
|
|
10745
|
+
if (!bound) {
|
|
10746
|
+
await new Promise(
|
|
10747
|
+
(resolve) => sock.bind(0, "0.0.0.0", () => resolve())
|
|
10748
|
+
);
|
|
10749
|
+
}
|
|
10711
10750
|
if (this.opts.mode === "direct") {
|
|
10712
10751
|
this.remote = { host: this.opts.host, port: this.opts.port };
|
|
10713
10752
|
this.clientId = this.opts.clientId;
|
|
@@ -11127,7 +11166,24 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11127
11166
|
BCUDP_DISCOVERY_PORT_LOCAL_ANY,
|
|
11128
11167
|
BCUDP_DISCOVERY_PORT_LOCAL_UID
|
|
11129
11168
|
];
|
|
11130
|
-
const
|
|
11169
|
+
const broadcastHosts = ["255.255.255.255"];
|
|
11170
|
+
const ifaces = (0, import_node_os.networkInterfaces)();
|
|
11171
|
+
for (const name of Object.keys(ifaces)) {
|
|
11172
|
+
const entries = ifaces[name];
|
|
11173
|
+
if (!entries) continue;
|
|
11174
|
+
for (const addr2 of entries) {
|
|
11175
|
+
if (addr2.family === "IPv4" && !addr2.internal && addr2.cidr) {
|
|
11176
|
+
const ipParts = addr2.address.split(".").map(Number);
|
|
11177
|
+
const maskParts = addr2.netmask.split(".").map(Number);
|
|
11178
|
+
if (ipParts.length === 4 && maskParts.length === 4) {
|
|
11179
|
+
const bcast = ipParts.map((octet, i) => octet | ~maskParts[i] & 255).join(".");
|
|
11180
|
+
if (!broadcastHosts.includes(bcast)) {
|
|
11181
|
+
broadcastHosts.push(bcast);
|
|
11182
|
+
}
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11131
11187
|
const directHost = (this.opts.directHost ?? "").trim();
|
|
11132
11188
|
const localMode = opts?.localMode ?? "local-broadcast";
|
|
11133
11189
|
const directFirstWindowMs = localMode === "local-direct" && directHost ? 3e3 : 0;
|
|
@@ -11154,6 +11210,7 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11154
11210
|
)
|
|
11155
11211
|
);
|
|
11156
11212
|
}, discoveryTimeout);
|
|
11213
|
+
this.discoveryTimers.push(timeout);
|
|
11157
11214
|
let retryTimer;
|
|
11158
11215
|
let retryCount = 0;
|
|
11159
11216
|
let discoveredSid;
|
|
@@ -11330,11 +11387,11 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11330
11387
|
if (directHost) {
|
|
11331
11388
|
if (directFirstWindowMs > 0 && elapsedMs < directFirstWindowMs)
|
|
11332
11389
|
return [directHost];
|
|
11333
|
-
return [directHost,
|
|
11390
|
+
return [directHost, ...broadcastHosts];
|
|
11334
11391
|
}
|
|
11335
|
-
return
|
|
11392
|
+
return broadcastHosts;
|
|
11336
11393
|
}
|
|
11337
|
-
return
|
|
11394
|
+
return broadcastHosts;
|
|
11338
11395
|
})();
|
|
11339
11396
|
for (const host of Array.from(new Set(hosts))) {
|
|
11340
11397
|
for (const port of ports) {
|
|
@@ -11342,8 +11399,7 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11342
11399
|
sock.send(packet, port, host);
|
|
11343
11400
|
retryCount++;
|
|
11344
11401
|
this.emit("debug", "discovery_send", { retryCount, host, port });
|
|
11345
|
-
} catch
|
|
11346
|
-
this.emit("error", e instanceof Error ? e : new Error(String(e)));
|
|
11402
|
+
} catch {
|
|
11347
11403
|
}
|
|
11348
11404
|
}
|
|
11349
11405
|
}
|
|
@@ -11352,6 +11408,7 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11352
11408
|
retryTimer = (0, import_node_timers.setInterval)(() => {
|
|
11353
11409
|
sendDiscovery();
|
|
11354
11410
|
}, retryInterval);
|
|
11411
|
+
this.discoveryTimers.push(retryTimer);
|
|
11355
11412
|
});
|
|
11356
11413
|
this.clientId = reply.cid;
|
|
11357
11414
|
this.cameraId = reply.did;
|
|
@@ -11668,6 +11725,10 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11668
11725
|
this.ackTimer = void 0;
|
|
11669
11726
|
this.resendTimer = void 0;
|
|
11670
11727
|
this.hbTimer = void 0;
|
|
11728
|
+
for (const t of this.discoveryTimers) {
|
|
11729
|
+
clearInterval(t);
|
|
11730
|
+
}
|
|
11731
|
+
this.discoveryTimers = [];
|
|
11671
11732
|
const s = this.sock;
|
|
11672
11733
|
this.sock = void 0;
|
|
11673
11734
|
if (!s) return;
|
|
@@ -17434,6 +17495,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17434
17495
|
* - "replay:XXX" - dedicated per replay session
|
|
17435
17496
|
*/
|
|
17436
17497
|
socketPool = /* @__PURE__ */ new Map();
|
|
17498
|
+
/**
|
|
17499
|
+
* Consecutive stream-start (cmdId=3) timeout counter per socket tag.
|
|
17500
|
+
* When a streaming socket has N consecutive timeouts, the socket is force-closed
|
|
17501
|
+
* so the next attempt creates a fresh connection. Resets on success.
|
|
17502
|
+
*/
|
|
17503
|
+
consecutiveStreamTimeouts = /* @__PURE__ */ new Map();
|
|
17504
|
+
static MAX_CONSECUTIVE_STREAM_TIMEOUTS = 3;
|
|
17437
17505
|
/** BaichuanClientOptions to use when creating new sockets */
|
|
17438
17506
|
clientOptions;
|
|
17439
17507
|
/**
|
|
@@ -17588,14 +17656,20 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17588
17656
|
if (!xml) return;
|
|
17589
17657
|
const channel = frame.header.channelId;
|
|
17590
17658
|
const battery = this.parseBatteryInfoXml(xml, channel);
|
|
17591
|
-
if (battery.batteryPercent
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
});
|
|
17659
|
+
if (battery.batteryPercent === void 0 && battery.chargeStatus === void 0 && battery.adapterStatus === void 0) {
|
|
17660
|
+
return;
|
|
17661
|
+
}
|
|
17662
|
+
const key = `${battery.batteryPercent ?? ""}|${battery.chargeStatus ?? ""}|${battery.adapterStatus ?? ""}`;
|
|
17663
|
+
if (this.lastBatteryPushKey.get(channel) === key) {
|
|
17664
|
+
return;
|
|
17598
17665
|
}
|
|
17666
|
+
this.lastBatteryPushKey.set(channel, key);
|
|
17667
|
+
this.dispatchSimpleEvent({
|
|
17668
|
+
type: "battery",
|
|
17669
|
+
channel,
|
|
17670
|
+
timestamp: Date.now(),
|
|
17671
|
+
battery
|
|
17672
|
+
});
|
|
17599
17673
|
} catch (e) {
|
|
17600
17674
|
this.logger.debug?.(
|
|
17601
17675
|
"[ReolinkBaichuanApi] Error parsing battery push",
|
|
@@ -17761,6 +17835,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17761
17835
|
deviceCapabilitiesCache = /* @__PURE__ */ new Map();
|
|
17762
17836
|
static CAPABILITIES_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
17763
17837
|
// 5 minutes
|
|
17838
|
+
/**
|
|
17839
|
+
* Dedupe key for battery push events (cmd_id 252), per channel.
|
|
17840
|
+
* Cameras emit BatteryInfoList frequently while streaming (every few
|
|
17841
|
+
* seconds). We only forward an event when the meaningful fields change
|
|
17842
|
+
* (percent, chargeStatus, adapterStatus) to avoid flooding SSE/MQTT
|
|
17843
|
+
* consumers and the UI event log.
|
|
17844
|
+
*/
|
|
17845
|
+
lastBatteryPushKey = /* @__PURE__ */ new Map();
|
|
17764
17846
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17765
17847
|
// SOCKET POOL CONSTANTS
|
|
17766
17848
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -18077,6 +18159,20 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
18077
18159
|
const prefix = basename.substring(0, 10).toUpperCase();
|
|
18078
18160
|
return prefix.includes("S") ? "subStream" : "mainStream";
|
|
18079
18161
|
}
|
|
18162
|
+
/**
|
|
18163
|
+
* Stream profiles that the device explicitly rejected (response_code 400).
|
|
18164
|
+
* Keyed by `"ch:profile"` (e.g. `"0:ext"`). Once a profile is in this set
|
|
18165
|
+
* it is excluded from `buildVideoStreamOptions()` results and no further
|
|
18166
|
+
* start attempts are made until the API instance is recreated.
|
|
18167
|
+
*/
|
|
18168
|
+
_rejectedStreamProfiles = /* @__PURE__ */ new Set();
|
|
18169
|
+
/**
|
|
18170
|
+
* Check whether a stream profile was rejected by the device at runtime
|
|
18171
|
+
* (e.g. ext returned response_code 400).
|
|
18172
|
+
*/
|
|
18173
|
+
isStreamProfileRejected(channel, profile) {
|
|
18174
|
+
return this._rejectedStreamProfiles.has(`${channel}:${profile}`);
|
|
18175
|
+
}
|
|
18080
18176
|
/**
|
|
18081
18177
|
* Cache for buildVideoStreamOptions.
|
|
18082
18178
|
*
|
|
@@ -23201,6 +23297,16 @@ ${stderr}`)
|
|
|
23201
23297
|
}
|
|
23202
23298
|
if (!frame) frame = await targetClient.sendFrame(baseParams);
|
|
23203
23299
|
if (frame.header.responseCode !== 200) {
|
|
23300
|
+
if (frame.header.responseCode === 400) {
|
|
23301
|
+
const rejKey = `${ch}:${profile}`;
|
|
23302
|
+
if (!this._rejectedStreamProfiles.has(rejKey)) {
|
|
23303
|
+
this._rejectedStreamProfiles.add(rejKey);
|
|
23304
|
+
this.videoStreamOptionsCache.clear();
|
|
23305
|
+
this.logger?.warn?.(
|
|
23306
|
+
`[ReolinkBaichuanApi] Stream profile rejected by device: channel=${ch} profile=${profile} (response_code 400). This profile will be excluded from available streams. The camera may not support this stream profile with the current firmware.`
|
|
23307
|
+
);
|
|
23308
|
+
}
|
|
23309
|
+
}
|
|
23204
23310
|
throw new Error(
|
|
23205
23311
|
`Video stream request rejected (response_code ${frame.header.responseCode}). Expected response_code 200, camera returned ${frame.header.responseCode}`
|
|
23206
23312
|
);
|
|
@@ -23209,6 +23315,7 @@ ${stderr}`)
|
|
|
23209
23315
|
`${ch}:${profile}:${variant}`,
|
|
23210
23316
|
frame.header.msgNum
|
|
23211
23317
|
);
|
|
23318
|
+
this.resetStreamTimeoutCounter(targetClient);
|
|
23212
23319
|
return;
|
|
23213
23320
|
} catch (error) {
|
|
23214
23321
|
lastError = error;
|
|
@@ -23223,6 +23330,10 @@ ${stderr}`)
|
|
|
23223
23330
|
}
|
|
23224
23331
|
}
|
|
23225
23332
|
}
|
|
23333
|
+
const isTimeout = lastError instanceof Error && lastError.message?.includes("timeout");
|
|
23334
|
+
if (isTimeout) {
|
|
23335
|
+
this.trackStreamTimeout(targetClient);
|
|
23336
|
+
}
|
|
23226
23337
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
23227
23338
|
}
|
|
23228
23339
|
/**
|
|
@@ -23682,6 +23793,18 @@ ${stderr}`)
|
|
|
23682
23793
|
notifyD2cDisc() {
|
|
23683
23794
|
const now = Date.now();
|
|
23684
23795
|
this.lastD2cDiscAtMs = now;
|
|
23796
|
+
const streamingTags = Array.from(this.socketPool.keys()).filter(
|
|
23797
|
+
(tag) => tag.startsWith("streaming:")
|
|
23798
|
+
);
|
|
23799
|
+
if (streamingTags.length > 0) {
|
|
23800
|
+
this.logger?.log?.(
|
|
23801
|
+
`[D2C_DISC] Force-closing ${streamingTags.length} streaming socket(s): ${streamingTags.join(", ")}`
|
|
23802
|
+
);
|
|
23803
|
+
for (const tag of streamingTags) {
|
|
23804
|
+
this.forceClosePooledSocket(tag, this.logger).catch(() => {
|
|
23805
|
+
});
|
|
23806
|
+
}
|
|
23807
|
+
}
|
|
23685
23808
|
const immediateCooldownUntil = now + _ReolinkBaichuanApi.D2C_DISC_IMMEDIATE_COOLDOWN_MS;
|
|
23686
23809
|
const existing = this.socketPoolCooldowns.get(this.host);
|
|
23687
23810
|
if (!existing || existing.cooldownUntil < immediateCooldownUntil) {
|
|
@@ -23714,6 +23837,43 @@ ${stderr}`)
|
|
|
23714
23837
|
}
|
|
23715
23838
|
}
|
|
23716
23839
|
}
|
|
23840
|
+
/**
|
|
23841
|
+
* Find the socket pool tag for a given BaichuanClient instance.
|
|
23842
|
+
* Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
|
|
23843
|
+
*/
|
|
23844
|
+
findSocketTagForClient(client) {
|
|
23845
|
+
for (const [tag, entry] of this.socketPool) {
|
|
23846
|
+
if (entry.client === client) return tag;
|
|
23847
|
+
}
|
|
23848
|
+
return void 0;
|
|
23849
|
+
}
|
|
23850
|
+
/**
|
|
23851
|
+
* Reset the consecutive stream-start timeout counter for a streaming socket.
|
|
23852
|
+
* Called on successful stream start.
|
|
23853
|
+
*/
|
|
23854
|
+
resetStreamTimeoutCounter(client) {
|
|
23855
|
+
const tag = this.findSocketTagForClient(client);
|
|
23856
|
+
if (tag) this.consecutiveStreamTimeouts.delete(tag);
|
|
23857
|
+
}
|
|
23858
|
+
/**
|
|
23859
|
+
* Track a stream-start timeout on a streaming socket.
|
|
23860
|
+
* After MAX_CONSECUTIVE_STREAM_TIMEOUTS consecutive timeouts, force-close the
|
|
23861
|
+
* socket so the next attempt creates a fresh connection.
|
|
23862
|
+
*/
|
|
23863
|
+
trackStreamTimeout(client) {
|
|
23864
|
+
const tag = this.findSocketTagForClient(client);
|
|
23865
|
+
if (!tag || !tag.startsWith("streaming:")) return;
|
|
23866
|
+
const count = (this.consecutiveStreamTimeouts.get(tag) ?? 0) + 1;
|
|
23867
|
+
this.consecutiveStreamTimeouts.set(tag, count);
|
|
23868
|
+
if (count >= _ReolinkBaichuanApi.MAX_CONSECUTIVE_STREAM_TIMEOUTS) {
|
|
23869
|
+
this.logger?.warn?.(
|
|
23870
|
+
`[SocketPool] ${count} consecutive stream timeouts on tag=${tag}, force-closing socket`
|
|
23871
|
+
);
|
|
23872
|
+
this.consecutiveStreamTimeouts.delete(tag);
|
|
23873
|
+
this.forceClosePooledSocket(tag, this.logger).catch(() => {
|
|
23874
|
+
});
|
|
23875
|
+
}
|
|
23876
|
+
}
|
|
23717
23877
|
/**
|
|
23718
23878
|
* Best-effort sleeping inference for battery/BCUDP cameras.
|
|
23719
23879
|
*
|
|
@@ -25222,6 +25382,8 @@ ${xml}`
|
|
|
25222
25382
|
for (const metadata of params.metadatas) {
|
|
25223
25383
|
const profile = metadata.profile;
|
|
25224
25384
|
if (isMultiFocal && profile === "ext") continue;
|
|
25385
|
+
if (this._rejectedStreamProfiles.has(`${params.channel}:${profile}`))
|
|
25386
|
+
continue;
|
|
25225
25387
|
if (params.includeRtsp && profile !== "ext") {
|
|
25226
25388
|
const streamName = profile === "main" ? "main" : "sub";
|
|
25227
25389
|
pushRtsp({
|
|
@@ -27949,16 +28111,16 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
|
27949
28111
|
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("EHOSTUNREACH") || message.includes("ENETUNREACH") || message.includes("socket hang up") || message.includes("TCP connection timeout") || message.includes("Baichuan socket closed") || message.includes("timeout waiting for nonce") || message.includes("expected encryption info") || message.includes("ECONNRESET") || message.includes("EPIPE");
|
|
27950
28112
|
}
|
|
27951
28113
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
28114
|
+
const { exec } = await import("child_process");
|
|
28115
|
+
const platform2 = process.platform;
|
|
28116
|
+
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
28117
|
+
// macOS: -W is in milliseconds (Linux: seconds)
|
|
28118
|
+
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
28119
|
+
) : (
|
|
28120
|
+
// Linux/BSD-ish: -W is in seconds on most distros
|
|
28121
|
+
`ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
|
|
28122
|
+
);
|
|
27952
28123
|
return new Promise((resolve) => {
|
|
27953
|
-
const { exec } = require("child_process");
|
|
27954
|
-
const platform2 = process.platform;
|
|
27955
|
-
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
27956
|
-
// macOS: -W is in milliseconds (Linux: seconds)
|
|
27957
|
-
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
27958
|
-
) : (
|
|
27959
|
-
// Linux/BSD-ish: -W is in seconds on most distros
|
|
27960
|
-
`ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
|
|
27961
|
-
);
|
|
27962
28124
|
exec(pingCmd, (error) => {
|
|
27963
28125
|
resolve(!error);
|
|
27964
28126
|
});
|