@apocaliss92/nodelink-js 0.5.1 → 0.5.2
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/BaichuanVideoStream-OCLOM452.js +7 -0
- package/dist/{DiagnosticsTools-7BIWJDZS.js → DiagnosticsTools-K4MF2VXZ.js} +3 -3
- package/dist/{chunk-OJQLZETO.js → chunk-7HSTETZR.js} +409 -108
- package/dist/chunk-7HSTETZR.js.map +1 -0
- package/dist/{chunk-GVWJGQPT.js → chunk-MZUSWKF3.js} +5 -1
- package/dist/chunk-MZUSWKF3.js.map +1 -0
- package/dist/{chunk-VOPEOB4H.js → chunk-XDVBNZGR.js} +2 -2
- package/dist/cli/rtsp-server.cjs +403 -102
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -3
- package/dist/index.cjs +410 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +42 -0
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/BaichuanVideoStream-NTIGPHYJ.js +0 -7
- package/dist/chunk-GVWJGQPT.js.map +0 -1
- package/dist/chunk-OJQLZETO.js.map +0 -1
- /package/dist/{BaichuanVideoStream-NTIGPHYJ.js.map → BaichuanVideoStream-OCLOM452.js.map} +0 -0
- /package/dist/{DiagnosticsTools-7BIWJDZS.js.map → DiagnosticsTools-K4MF2VXZ.js.map} +0 -0
- /package/dist/{chunk-VOPEOB4H.js.map → chunk-XDVBNZGR.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
34
34
|
function bcHeaderHasPayloadOffset(messageClass) {
|
|
35
35
|
return messageClass === BC_CLASS_MODERN_24 || messageClass === BC_CLASS_MODERN_24_ALT || messageClass === BC_CLASS_FILE_DOWNLOAD;
|
|
36
36
|
}
|
|
37
|
-
var BC_TCP_DEFAULT_PORT, BC_MAGIC, BC_MAGIC_REV, BC_XML_KEY, BC_AES_IV, BC_CLASS_LEGACY, BC_CLASS_MODERN_20, BC_CLASS_MODERN_24, BC_CLASS_MODERN_24_ALT, BC_CLASS_FILE_DOWNLOAD, BC_CMD_ID_LOGIN, BC_CMD_ID_LOGOUT, BC_CMD_ID_VIDEO, BC_CMD_ID_VIDEO_STOP, BC_CMD_ID_FILE_INFO_LIST_REPLAY, BC_CMD_ID_FILE_INFO_LIST_STOP, BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO, BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD, BC_CMD_ID_FILE_INFO_LIST_OPEN, BC_CMD_ID_FILE_INFO_LIST_GET, BC_CMD_ID_FILE_INFO_LIST_CLOSE, BC_CMD_ID_FIND_REC_VIDEO_OPEN, BC_CMD_ID_FIND_REC_VIDEO_GET, BC_CMD_ID_FIND_REC_VIDEO_CLOSE, BC_CMD_ID_COVER_PREVIEW, BC_CMD_ID_COVER_STANDALONE_458, BC_CMD_ID_COVER_STANDALONE_459, BC_CMD_ID_COVER_STANDALONE_460, BC_CMD_ID_COVER_STANDALONE_461, BC_CMD_ID_COVER_STANDALONE_462, BC_CMD_ID_COVER_RESPONSE, BC_CMD_ID_TALK_ABILITY, BC_CMD_ID_TALK_RESET, BC_CMD_ID_TALK_CONFIG, BC_CMD_ID_TALK, BC_CMD_ID_PTZ_CONTROL, BC_CMD_ID_PTZ_CONTROL_PRESET, BC_CMD_ID_GET_PTZ_PRESET, BC_CMD_ID_GET_PTZ_POSITION, BC_CMD_ID_GET_ZOOM_FOCUS, BC_CMD_ID_SET_ZOOM_FOCUS, BC_CMD_ID_GET_BATTERY_INFO_LIST, BC_CMD_ID_GET_BATTERY_INFO, BC_CMD_ID_UDP_KEEP_ALIVE, BC_CMD_ID_GET_PIR_INFO, BC_CMD_ID_SET_PIR_INFO, BC_CMD_ID_GET_MOTION_ALARM, BC_CMD_ID_SET_MOTION_ALARM, BC_CMD_ID_ALARM_EVENT_LIST, BC_CMD_ID_GET_AI_ALARM, BC_CMD_ID_SET_AI_ALARM, BC_CMD_ID_GET_AUDIO_ALARM, BC_CMD_ID_AUDIO_ALARM_PLAY, BC_CMD_ID_GET_WHITE_LED, BC_CMD_ID_SET_WHITE_LED_STATE, BC_CMD_ID_SET_WHITE_LED_TASK, BC_CMD_ID_FLOODLIGHT_STATUS_LIST, BC_CMD_ID_ABILITY_INFO, BC_CMD_ID_SUPPORT, BC_CMD_ID_PING, BC_CMD_ID_GET_SNAPSHOT, BC_CMD_ID_GET_UID, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_SET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_VERSION_INFO, BC_CMD_ID_GET_RECORD, BC_CMD_ID_GET_HDD_INFO_LIST, BC_CMD_ID_GET_WIFI_SIGNAL, BC_CMD_ID_GET_WIFI, BC_CMD_ID_GET_ONLINE_USER_LIST, BC_CMD_ID_GET_DAY_RECORDS, BC_CMD_ID_GET_STREAM_INFO_LIST, BC_CMD_ID_GET_LED_STATE, BC_CMD_ID_GET_EMAIL_TASK, BC_CMD_ID_GET_AUDIO_TASK, BC_CMD_ID_GET_AUDIO_CFG, BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_TIMELAPSE_CFG, BC_CMD_ID_GET_AI_DENOISE, BC_CMD_ID_GET_KIT_AP_CFG, BC_CMD_ID_GET_REC_ENC_CFG, BC_CMD_ID_GET_ACCESS_USER_LIST, BC_CMD_ID_GET_SLEEP_STATE, BC_CMD_ID_GET_VIDEO_INPUT, BC_CMD_ID_GET_SYSTEM_GENERAL, BC_CMD_ID_GET_SUPPORT, BC_CMD_ID_GET_AI_CFG, BC_CMD_ID_SET_AI_CFG, BC_CMD_ID_GET_SIREN_STATUS, BC_CMD_ID_SET_AUDIO_TASK, BC_CMD_ID_SET_VIDEO_INPUT, BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_ENC, BC_CMD_ID_SET_ENC, BC_CMD_ID_GET_PRIVACY_MASK, BC_CMD_ID_SET_PRIVACY_MASK, BC_CMD_ID_SET_AI_DENOISE, BC_CMD_ID_SET_LED_STATE, BC_CMD_ID_SET_AUDIO_CFG, BC_CMD_ID_SET_RECORD, BC_CMD_ID_SET_RECORD_CFG, BC_CMD_ID_SET_EMAIL_TASK, BC_CMD_ID_GET_PUSH_TASK, BC_CMD_ID_SET_PUSH_TASK, BC_CMD_ID_GET_AUTO_FOCUS, BC_CMD_ID_SET_AUTO_FOCUS, BC_CMD_ID_GET_EMAIL, BC_CMD_ID_SET_EMAIL, BC_CMD_ID_TEST_EMAIL, BC_CMD_ID_GET_NTP, BC_CMD_ID_SET_NTP, BC_CMD_ID_SET_SYSTEM_GENERAL, BC_CMD_ID_GET_DST, BC_CMD_ID_SET_DST, BC_CMD_ID_GET_AUTO_REBOOT, BC_CMD_ID_SET_AUTO_REBOOT, BC_CMD_ID_CMD_123, BC_CMD_ID_CMD_209, BC_CMD_ID_CMD_265, BC_CMD_ID_CMD_440, BC_CMD_ID_PUSH_VIDEO_INPUT, BC_CMD_ID_PUSH_SERIAL, BC_CMD_ID_PUSH_NET_INFO, BC_CMD_ID_PUSH_DINGDONG_LIST, BC_CMD_ID_PUSH_SLEEP_STATUS, BC_CMD_ID_PUSH_COORDINATE_POINT_LIST, BC_CMD_ID_DING_DONG_CTRL, BC_CMD_ID_GET_DING_DONG_LIST, BC_CMD_ID_DING_DONG_OPT, BC_CMD_ID_GET_DING_DONG_CFG, BC_CMD_ID_SET_DING_DONG_CFG, BC_CMD_ID_QUICK_REPLY_PLAY, BC_CMD_ID_GET_DING_DONG_SILENT, BC_CMD_ID_SET_DING_DONG_SILENT;
|
|
37
|
+
var BC_TCP_DEFAULT_PORT, BC_MAGIC, BC_MAGIC_REV, BC_XML_KEY, BC_AES_IV, BC_CLASS_LEGACY, BC_CLASS_MODERN_20, BC_CLASS_MODERN_24, BC_CLASS_MODERN_24_ALT, BC_CLASS_FILE_DOWNLOAD, BC_CMD_ID_LOGIN, BC_CMD_ID_LOGOUT, BC_CMD_ID_VIDEO, BC_CMD_ID_VIDEO_STOP, BC_CMD_ID_FILE_INFO_LIST_REPLAY, BC_CMD_ID_FILE_INFO_LIST_STOP, BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO, BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD, BC_CMD_ID_FILE_INFO_LIST_OPEN, BC_CMD_ID_FILE_INFO_LIST_GET, BC_CMD_ID_FILE_INFO_LIST_CLOSE, BC_CMD_ID_FIND_REC_VIDEO_OPEN, BC_CMD_ID_FIND_REC_VIDEO_GET, BC_CMD_ID_FIND_REC_VIDEO_CLOSE, BC_CMD_ID_COVER_PREVIEW, BC_CMD_ID_COVER_STANDALONE_458, BC_CMD_ID_COVER_STANDALONE_459, BC_CMD_ID_COVER_STANDALONE_460, BC_CMD_ID_COVER_STANDALONE_461, BC_CMD_ID_COVER_STANDALONE_462, BC_CMD_ID_COVER_RESPONSE, BC_CMD_ID_TALK_ABILITY, BC_CMD_ID_TALK_RESET, BC_CMD_ID_TALK_CONFIG, BC_CMD_ID_TALK, BC_CMD_ID_PTZ_CONTROL, BC_CMD_ID_PTZ_CONTROL_PRESET, BC_CMD_ID_GET_PTZ_PRESET, BC_CMD_ID_GET_PTZ_POSITION, BC_CMD_ID_GET_ZOOM_FOCUS, BC_CMD_ID_SET_ZOOM_FOCUS, BC_CMD_ID_GET_BATTERY_INFO_LIST, BC_CMD_ID_GET_BATTERY_INFO, BC_CMD_ID_UDP_KEEP_ALIVE, BC_CMD_ID_GET_PIR_INFO, BC_CMD_ID_SET_PIR_INFO, BC_CMD_ID_GET_MOTION_ALARM, BC_CMD_ID_SET_MOTION_ALARM, BC_CMD_ID_ALARM_EVENT_LIST, BC_CMD_ID_GET_AI_ALARM, BC_CMD_ID_SET_AI_ALARM, BC_CMD_ID_GET_AUDIO_ALARM, BC_CMD_ID_AUDIO_ALARM_PLAY, BC_CMD_ID_GET_WHITE_LED, BC_CMD_ID_SET_WHITE_LED_STATE, BC_CMD_ID_SET_WHITE_LED_TASK, BC_CMD_ID_FLOODLIGHT_STATUS_LIST, BC_CMD_ID_ABILITY_INFO, BC_CMD_ID_SUPPORT, BC_CMD_ID_PING, BC_CMD_ID_DEVICE_DETECT_CANDIDATES, BC_CMD_ID_DEVICE_DETECT, BC_CMD_ID_GET_SNAPSHOT, BC_CMD_ID_GET_UID, BC_CMD_ID_CHANNEL_INFO_ALL, BC_CMD_ID_GET_OSD_DATETIME, BC_CMD_ID_SET_OSD_DATETIME, BC_CMD_ID_GET_RECORD_CFG, BC_CMD_ID_GET_ABILITY_SUPPORT, BC_CMD_ID_GET_FTP_TASK, BC_CMD_ID_GET_VERSION_INFO, BC_CMD_ID_GET_RECORD, BC_CMD_ID_GET_HDD_INFO_LIST, BC_CMD_ID_GET_WIFI_SIGNAL, BC_CMD_ID_GET_WIFI, BC_CMD_ID_GET_ONLINE_USER_LIST, BC_CMD_ID_GET_DAY_RECORDS, BC_CMD_ID_GET_STREAM_INFO_LIST, BC_CMD_ID_GET_LED_STATE, BC_CMD_ID_GET_EMAIL_TASK, BC_CMD_ID_GET_AUDIO_TASK, BC_CMD_ID_GET_AUDIO_CFG, BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_TIMELAPSE_CFG, BC_CMD_ID_GET_AI_DENOISE, BC_CMD_ID_GET_KIT_AP_CFG, BC_CMD_ID_GET_REC_ENC_CFG, BC_CMD_ID_GET_ACCESS_USER_LIST, BC_CMD_ID_GET_SLEEP_STATE, BC_CMD_ID_GET_VIDEO_INPUT, BC_CMD_ID_GET_SYSTEM_GENERAL, BC_CMD_ID_GET_SUPPORT, BC_CMD_ID_GET_AI_CFG, BC_CMD_ID_SET_AI_CFG, BC_CMD_ID_GET_SIREN_STATUS, BC_CMD_ID_SET_AUDIO_TASK, BC_CMD_ID_SET_VIDEO_INPUT, BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD, BC_CMD_ID_GET_ENC, BC_CMD_ID_SET_ENC, BC_CMD_ID_GET_PRIVACY_MASK, BC_CMD_ID_SET_PRIVACY_MASK, BC_CMD_ID_SET_AI_DENOISE, BC_CMD_ID_SET_LED_STATE, BC_CMD_ID_SET_AUDIO_CFG, BC_CMD_ID_SET_RECORD, BC_CMD_ID_SET_RECORD_CFG, BC_CMD_ID_SET_EMAIL_TASK, BC_CMD_ID_GET_PUSH_TASK, BC_CMD_ID_SET_PUSH_TASK, BC_CMD_ID_GET_AUTO_FOCUS, BC_CMD_ID_SET_AUTO_FOCUS, BC_CMD_ID_GET_EMAIL, BC_CMD_ID_SET_EMAIL, BC_CMD_ID_TEST_EMAIL, BC_CMD_ID_GET_NTP, BC_CMD_ID_SET_NTP, BC_CMD_ID_SET_SYSTEM_GENERAL, BC_CMD_ID_GET_DST, BC_CMD_ID_SET_DST, BC_CMD_ID_GET_AUTO_REBOOT, BC_CMD_ID_SET_AUTO_REBOOT, BC_CMD_ID_CMD_123, BC_CMD_ID_CMD_209, BC_CMD_ID_CMD_265, BC_CMD_ID_CMD_440, BC_CMD_ID_PUSH_VIDEO_INPUT, BC_CMD_ID_PUSH_SERIAL, BC_CMD_ID_PUSH_NET_INFO, BC_CMD_ID_PUSH_DINGDONG_LIST, BC_CMD_ID_PUSH_SLEEP_STATUS, BC_CMD_ID_PUSH_COORDINATE_POINT_LIST, BC_CMD_ID_DING_DONG_CTRL, BC_CMD_ID_GET_DING_DONG_LIST, BC_CMD_ID_DING_DONG_OPT, BC_CMD_ID_GET_DING_DONG_CFG, BC_CMD_ID_SET_DING_DONG_CFG, BC_CMD_ID_QUICK_REPLY_PLAY, BC_CMD_ID_GET_DING_DONG_SILENT, BC_CMD_ID_SET_DING_DONG_SILENT;
|
|
38
38
|
var init_constants = __esm({
|
|
39
39
|
"src/protocol/constants.ts"() {
|
|
40
40
|
"use strict";
|
|
@@ -107,6 +107,8 @@ var init_constants = __esm({
|
|
|
107
107
|
BC_CMD_ID_ABILITY_INFO = 151;
|
|
108
108
|
BC_CMD_ID_SUPPORT = 199;
|
|
109
109
|
BC_CMD_ID_PING = 93;
|
|
110
|
+
BC_CMD_ID_DEVICE_DETECT_CANDIDATES = [31, 132, 150];
|
|
111
|
+
BC_CMD_ID_DEVICE_DETECT = null;
|
|
110
112
|
BC_CMD_ID_GET_SNAPSHOT = 109;
|
|
111
113
|
BC_CMD_ID_GET_UID = 114;
|
|
112
114
|
BC_CMD_ID_CHANNEL_INFO_ALL = 145;
|
|
@@ -8256,6 +8258,8 @@ __export(index_exports, {
|
|
|
8256
8258
|
BC_CMD_ID_COVER_STANDALONE_460: () => BC_CMD_ID_COVER_STANDALONE_460,
|
|
8257
8259
|
BC_CMD_ID_COVER_STANDALONE_461: () => BC_CMD_ID_COVER_STANDALONE_461,
|
|
8258
8260
|
BC_CMD_ID_COVER_STANDALONE_462: () => BC_CMD_ID_COVER_STANDALONE_462,
|
|
8261
|
+
BC_CMD_ID_DEVICE_DETECT: () => BC_CMD_ID_DEVICE_DETECT,
|
|
8262
|
+
BC_CMD_ID_DEVICE_DETECT_CANDIDATES: () => BC_CMD_ID_DEVICE_DETECT_CANDIDATES,
|
|
8259
8263
|
BC_CMD_ID_DING_DONG_CTRL: () => BC_CMD_ID_DING_DONG_CTRL,
|
|
8260
8264
|
BC_CMD_ID_DING_DONG_OPT: () => BC_CMD_ID_DING_DONG_OPT,
|
|
8261
8265
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE: () => BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|
|
@@ -9017,6 +9021,143 @@ function parseD2cHb(xml) {
|
|
|
9017
9021
|
return { cid: Number(cid), did: Number(did) };
|
|
9018
9022
|
}
|
|
9019
9023
|
|
|
9024
|
+
// src/cloud/server-binding.ts
|
|
9025
|
+
var REOLINK_API_V2_BASE = "https://apis.reolink.com/v2";
|
|
9026
|
+
var POSITIVE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9027
|
+
var NEGATIVE_TTL_MS = 30 * 1e3;
|
|
9028
|
+
var cache = /* @__PURE__ */ new Map();
|
|
9029
|
+
function readCache(uid, now) {
|
|
9030
|
+
const e = cache.get(uid);
|
|
9031
|
+
if (!e) return void 0;
|
|
9032
|
+
if (now >= e.expires) {
|
|
9033
|
+
cache.delete(uid);
|
|
9034
|
+
return void 0;
|
|
9035
|
+
}
|
|
9036
|
+
return e;
|
|
9037
|
+
}
|
|
9038
|
+
async function getServerBinding(uid, options = {}) {
|
|
9039
|
+
if (!uid || typeof uid !== "string") return void 0;
|
|
9040
|
+
const now = Date.now();
|
|
9041
|
+
const cached = readCache(uid, now);
|
|
9042
|
+
if (cached?.kind === "ok") return cached.response;
|
|
9043
|
+
if (cached?.kind === "err") return void 0;
|
|
9044
|
+
const language = options.language ?? "en";
|
|
9045
|
+
const baseUrl = options.baseUrl ?? REOLINK_API_V2_BASE;
|
|
9046
|
+
const timeoutMs = options.timeoutMs ?? 4e3;
|
|
9047
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
9048
|
+
const logger = options.logger;
|
|
9049
|
+
if (typeof fetchImpl !== "function") {
|
|
9050
|
+
logger?.debug?.(
|
|
9051
|
+
`[server-binding] global fetch unavailable; skipping cloud lookup`
|
|
9052
|
+
);
|
|
9053
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9054
|
+
return void 0;
|
|
9055
|
+
}
|
|
9056
|
+
const url = `${baseUrl}/devices/${encodeURIComponent(uid)}/server-binding?language=${encodeURIComponent(language)}`;
|
|
9057
|
+
const controller = new AbortController();
|
|
9058
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
9059
|
+
try {
|
|
9060
|
+
const res = await fetchImpl(url, {
|
|
9061
|
+
method: "GET",
|
|
9062
|
+
signal: controller.signal,
|
|
9063
|
+
headers: { Accept: "application/json" }
|
|
9064
|
+
});
|
|
9065
|
+
if (!res.ok) {
|
|
9066
|
+
logger?.debug?.(
|
|
9067
|
+
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText}`
|
|
9068
|
+
);
|
|
9069
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9070
|
+
return void 0;
|
|
9071
|
+
}
|
|
9072
|
+
const json = await res.json();
|
|
9073
|
+
const parsed = parseServerBindingResponse(json);
|
|
9074
|
+
if (!parsed) {
|
|
9075
|
+
logger?.debug?.(
|
|
9076
|
+
`[server-binding] ${uid}: response shape did not match expectations`
|
|
9077
|
+
);
|
|
9078
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9079
|
+
return void 0;
|
|
9080
|
+
}
|
|
9081
|
+
cache.set(uid, {
|
|
9082
|
+
kind: "ok",
|
|
9083
|
+
response: parsed,
|
|
9084
|
+
expires: now + POSITIVE_TTL_MS
|
|
9085
|
+
});
|
|
9086
|
+
const pick = parsed.availableZones.find(
|
|
9087
|
+
(z) => z.status === "active" && z.services.p2p?.server
|
|
9088
|
+
);
|
|
9089
|
+
const hint = pick?.services.p2p?.server ?? parsed.availableZones[0]?.services.p2p?.server;
|
|
9090
|
+
logger?.log?.(
|
|
9091
|
+
`[server-binding] ${uid}: ${parsed.availableZones.length} zone(s)${hint ? `, p2p hint=${hint}` : ""}`
|
|
9092
|
+
);
|
|
9093
|
+
return parsed;
|
|
9094
|
+
} catch (e) {
|
|
9095
|
+
logger?.debug?.(
|
|
9096
|
+
`[server-binding] ${uid}: ${e?.message ?? String(e)}`
|
|
9097
|
+
);
|
|
9098
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
9099
|
+
return void 0;
|
|
9100
|
+
} finally {
|
|
9101
|
+
clearTimeout(timer);
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
function pickP2pHostFromBinding(response) {
|
|
9105
|
+
if (!response) return void 0;
|
|
9106
|
+
const zones = response.availableZones;
|
|
9107
|
+
if (!zones || zones.length === 0) return void 0;
|
|
9108
|
+
const active = zones.find(
|
|
9109
|
+
(z) => z.status === "active" && z.services.p2p?.server
|
|
9110
|
+
);
|
|
9111
|
+
if (active?.services.p2p?.server) return active.services.p2p.server;
|
|
9112
|
+
const def = zones.find(
|
|
9113
|
+
(z) => z.status === "default" && z.services.p2p?.server
|
|
9114
|
+
);
|
|
9115
|
+
if (def?.services.p2p?.server) return def.services.p2p.server;
|
|
9116
|
+
const any = zones.find((z) => z.services.p2p?.server);
|
|
9117
|
+
return any?.services.p2p?.server;
|
|
9118
|
+
}
|
|
9119
|
+
function isString(v) {
|
|
9120
|
+
return typeof v === "string";
|
|
9121
|
+
}
|
|
9122
|
+
function parseServerBindingResponse(raw) {
|
|
9123
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
9124
|
+
const rawZones = raw.availableZones;
|
|
9125
|
+
if (!Array.isArray(rawZones)) return void 0;
|
|
9126
|
+
const zones = [];
|
|
9127
|
+
for (const r of rawZones) {
|
|
9128
|
+
if (!r || typeof r !== "object") continue;
|
|
9129
|
+
const rec = r;
|
|
9130
|
+
const id = rec.id;
|
|
9131
|
+
const name = rec.name;
|
|
9132
|
+
const status = rec.status;
|
|
9133
|
+
if (!isString(id) || !isString(name) || !isString(status)) continue;
|
|
9134
|
+
const servicesRaw = rec.services;
|
|
9135
|
+
const services = {};
|
|
9136
|
+
if (servicesRaw && typeof servicesRaw === "object") {
|
|
9137
|
+
const s = servicesRaw;
|
|
9138
|
+
for (const key of ["p2p", "cloud", "roms_ota", "alarm_push"]) {
|
|
9139
|
+
const v = s[key];
|
|
9140
|
+
if (v && typeof v === "object") {
|
|
9141
|
+
const server = v.server;
|
|
9142
|
+
if (isString(server) && server.length > 0) {
|
|
9143
|
+
services[key] = { server };
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
const locationsRaw = rec.locations;
|
|
9149
|
+
const locations = Array.isArray(locationsRaw) && locationsRaw.every(isString) ? locationsRaw : void 0;
|
|
9150
|
+
zones.push({
|
|
9151
|
+
id,
|
|
9152
|
+
name,
|
|
9153
|
+
status,
|
|
9154
|
+
services,
|
|
9155
|
+
...locations ? { locations } : {}
|
|
9156
|
+
});
|
|
9157
|
+
}
|
|
9158
|
+
return { availableZones: zones };
|
|
9159
|
+
}
|
|
9160
|
+
|
|
9020
9161
|
// src/bcudp/BcUdpStream.ts
|
|
9021
9162
|
var AckLatency = class {
|
|
9022
9163
|
currentValues = [];
|
|
@@ -9098,6 +9239,16 @@ var P2P_MAX_WAIT_MS = 15e3;
|
|
|
9098
9239
|
var P2P_RESEND_WAIT_MS = 500;
|
|
9099
9240
|
var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
9100
9241
|
opts;
|
|
9242
|
+
/**
|
|
9243
|
+
* Optional info-level logger for diagnostic milestones — set via
|
|
9244
|
+
* {@link BcUdpStream.setLogger} by `BaichuanClient` so the lib's
|
|
9245
|
+
* standard logger sink sees BCUDP / P2P progress (DNS resolutions,
|
|
9246
|
+
* outgoing UDP probes, timeouts with elapsed times) without the user
|
|
9247
|
+
* having to opt into the per-packet `debug` event firehose. Kept
|
|
9248
|
+
* separate from `emit('debug', ...)` because that channel is intended
|
|
9249
|
+
* for the per-packet debug trace and is gated by debugOptions.
|
|
9250
|
+
*/
|
|
9251
|
+
discoveryLogger;
|
|
9101
9252
|
sock;
|
|
9102
9253
|
remote;
|
|
9103
9254
|
mtu;
|
|
@@ -9141,6 +9292,17 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9141
9292
|
this.mtu = BCUDP_DEFAULT_MTU;
|
|
9142
9293
|
}
|
|
9143
9294
|
/** True if the underlying UDP socket is open and the remote peer is known. */
|
|
9295
|
+
/**
|
|
9296
|
+
* Attach an info-level logger for high-signal diagnostic milestones
|
|
9297
|
+
* (DNS resolution, outgoing UDP probe sends, P2P UID lookup wins/losses,
|
|
9298
|
+
* BCUDP local discovery timeouts). The lib's `BaichuanClient` calls
|
|
9299
|
+
* this immediately after constructing the stream so consumers get
|
|
9300
|
+
* actionable progress logs without enabling the per-packet debug trace.
|
|
9301
|
+
* Safe to call repeatedly; only the most recent logger is used.
|
|
9302
|
+
*/
|
|
9303
|
+
setLogger(logger) {
|
|
9304
|
+
this.discoveryLogger = logger;
|
|
9305
|
+
}
|
|
9144
9306
|
isConnected() {
|
|
9145
9307
|
return !!this.sock && !!this.remote && this.cameraId != null;
|
|
9146
9308
|
}
|
|
@@ -9258,20 +9420,60 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9258
9420
|
this.remote = { host: connected.rhost, port: connected.rport };
|
|
9259
9421
|
}
|
|
9260
9422
|
async p2pUidLookup(sock, uid) {
|
|
9423
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[P2P] ${msg}`);
|
|
9424
|
+
const shortUid = uid.length > 7 ? `${uid.slice(0, 5)}\u2026${uid.slice(-2)}` : uid;
|
|
9425
|
+
const t0 = Date.now();
|
|
9426
|
+
const hostnamesToTry = [];
|
|
9427
|
+
const binding = await getServerBinding(uid, {
|
|
9428
|
+
...this.discoveryLogger ? { logger: this.discoveryLogger } : {}
|
|
9429
|
+
}).catch(() => void 0);
|
|
9430
|
+
const hintedHost = pickP2pHostFromBinding(binding);
|
|
9431
|
+
if (hintedHost) {
|
|
9432
|
+
hostnamesToTry.push(hintedHost);
|
|
9433
|
+
log(
|
|
9434
|
+
`UID=${shortUid} cloud server-binding \u2192 hint=${hintedHost} (will try first)`
|
|
9435
|
+
);
|
|
9436
|
+
} else {
|
|
9437
|
+
log(
|
|
9438
|
+
`UID=${shortUid} cloud server-binding \u2192 no hint (apis.reolink.com unreachable / no zone match) \u2192 sweeping ${P2P_RELAY_HOSTNAMES.length} fallback hostnames`
|
|
9439
|
+
);
|
|
9440
|
+
}
|
|
9441
|
+
for (const host of P2P_RELAY_HOSTNAMES) {
|
|
9442
|
+
if (!hostnamesToTry.includes(host)) hostnamesToTry.push(host);
|
|
9443
|
+
}
|
|
9261
9444
|
const resolved = [];
|
|
9262
9445
|
const sinkholed = [];
|
|
9263
|
-
for (const host of
|
|
9446
|
+
for (const host of hostnamesToTry) {
|
|
9264
9447
|
try {
|
|
9265
9448
|
const answers = await import_promises.default.lookup(host, { family: 4, all: true });
|
|
9449
|
+
let publicCount = 0;
|
|
9450
|
+
let sinkCount = 0;
|
|
9266
9451
|
for (const a of answers) {
|
|
9267
9452
|
if (!a.address) continue;
|
|
9268
9453
|
if (isUnroutableForP2P(a.address)) {
|
|
9269
9454
|
sinkholed.push({ host, ip: a.address });
|
|
9455
|
+
sinkCount++;
|
|
9270
9456
|
continue;
|
|
9271
9457
|
}
|
|
9272
|
-
if (!resolved.includes(a.address))
|
|
9458
|
+
if (!resolved.includes(a.address)) {
|
|
9459
|
+
resolved.push(a.address);
|
|
9460
|
+
publicCount++;
|
|
9461
|
+
}
|
|
9273
9462
|
}
|
|
9274
|
-
|
|
9463
|
+
if (sinkCount > 0 && publicCount === 0) {
|
|
9464
|
+
log(
|
|
9465
|
+
`DNS ${host} \u2192 sinkhole (${sinkholed[sinkholed.length - 1]?.ip}) \u2014 DNS filter / /etc/hosts override`
|
|
9466
|
+
);
|
|
9467
|
+
} else if (publicCount > 0) {
|
|
9468
|
+
if (host === hintedHost) {
|
|
9469
|
+
log(`DNS ${host} \u2192 ${answers.find((a) => !isUnroutableForP2P(a.address))?.address} \u2713`);
|
|
9470
|
+
}
|
|
9471
|
+
}
|
|
9472
|
+
} catch (e) {
|
|
9473
|
+
log(`DNS ${host} \u2192 ENOTFOUND/timeout (${e?.code ?? "?"})`);
|
|
9474
|
+
}
|
|
9475
|
+
if (hintedHost && host === hintedHost && resolved.length > 0 && sinkholed.length === 0) {
|
|
9476
|
+
break;
|
|
9275
9477
|
}
|
|
9276
9478
|
}
|
|
9277
9479
|
if (resolved.length === 0) {
|
|
@@ -9285,11 +9487,22 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9285
9487
|
"P2P UID lookup failed: no p2p.reolink.com addresses resolved (DNS failure)"
|
|
9286
9488
|
);
|
|
9287
9489
|
}
|
|
9490
|
+
log(
|
|
9491
|
+
`Resolved ${resolved.length} P2P relay IP(s) (${resolved.slice(0, 3).join(", ")}${resolved.length > 3 ? "\u2026" : ""}). Sending C2M_Q probes (3s budget each, ${P2P_MAX_WAIT_MS}ms total)`
|
|
9492
|
+
);
|
|
9288
9493
|
const start = Date.now();
|
|
9289
9494
|
let lastErr;
|
|
9495
|
+
let attemptsMade = 0;
|
|
9290
9496
|
for (const ip of resolved) {
|
|
9291
9497
|
const remaining = P2P_MAX_WAIT_MS - (Date.now() - start);
|
|
9292
|
-
if (remaining <= 0)
|
|
9498
|
+
if (remaining <= 0) {
|
|
9499
|
+
log(
|
|
9500
|
+
`Aborting after ${attemptsMade} attempt(s) \u2014 total budget ${P2P_MAX_WAIT_MS}ms exhausted`
|
|
9501
|
+
);
|
|
9502
|
+
break;
|
|
9503
|
+
}
|
|
9504
|
+
attemptsMade++;
|
|
9505
|
+
const probeStart = Date.now();
|
|
9293
9506
|
try {
|
|
9294
9507
|
const res = await this.p2pUidLookupOne(
|
|
9295
9508
|
sock,
|
|
@@ -9297,11 +9510,20 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9297
9510
|
{ host: ip, port: P2P_LOOKUP_PORT },
|
|
9298
9511
|
Math.min(remaining, 3e3)
|
|
9299
9512
|
);
|
|
9513
|
+
log(
|
|
9514
|
+
`${ip}:${P2P_LOOKUP_PORT} replied in ${Date.now() - probeStart}ms \u2713 (total ${Date.now() - t0}ms)`
|
|
9515
|
+
);
|
|
9300
9516
|
return res;
|
|
9301
9517
|
} catch (e) {
|
|
9518
|
+
const ms = Date.now() - probeStart;
|
|
9519
|
+
const msg = e?.message ?? String(e);
|
|
9520
|
+
log(`${ip}:${P2P_LOOKUP_PORT} no reply after ${ms}ms (${msg})`);
|
|
9302
9521
|
lastErr = e instanceof Error ? e : new Error(String(e));
|
|
9303
9522
|
}
|
|
9304
9523
|
}
|
|
9524
|
+
log(
|
|
9525
|
+
`Exhausted all ${attemptsMade} relay candidate(s) after ${Date.now() - t0}ms \u2014 UID lookup failed`
|
|
9526
|
+
);
|
|
9305
9527
|
throw lastErr ?? new Error("P2P UID lookup failed");
|
|
9306
9528
|
}
|
|
9307
9529
|
async p2pUidLookupOne(sock, uid, dest, timeoutMs) {
|
|
@@ -9640,13 +9862,23 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9640
9862
|
const directHost = (this.opts.directHost ?? "").trim();
|
|
9641
9863
|
const localMode = opts?.localMode ?? "local-broadcast";
|
|
9642
9864
|
const directFirstWindowMs = localMode === "local-direct" && directHost ? 3e3 : 0;
|
|
9643
|
-
const discoveryTimeout =
|
|
9865
|
+
const discoveryTimeout = typeof this.opts.localDiscoveryTimeoutMs === "number" && this.opts.localDiscoveryTimeoutMs > 0 ? this.opts.localDiscoveryTimeoutMs : 15e3;
|
|
9644
9866
|
const retryInterval = 500;
|
|
9645
9867
|
const startMs = Date.now();
|
|
9646
9868
|
sock.setBroadcast(true);
|
|
9647
9869
|
const addr = sock.address();
|
|
9648
9870
|
const localPort = typeof addr === "string" ? 0 : addr.port;
|
|
9649
9871
|
const cid = Math.floor(Math.random() * 2147483647) | 0 || 82e3;
|
|
9872
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[BCUDP] ${msg}`);
|
|
9873
|
+
const shortUid = this.opts.uid.length > 7 ? `${this.opts.uid.slice(0, 5)}\u2026${this.opts.uid.slice(-2)}` : this.opts.uid;
|
|
9874
|
+
log(
|
|
9875
|
+
`local discovery: mode=${localMode} uid=${shortUid} ports=[${ports.join(", ")}] broadcasts=[${broadcastHosts.join(", ")}]${directHost ? ` direct=${directHost}` : ""} localBindPort=${localPort} timeout=${discoveryTimeout}ms`
|
|
9876
|
+
);
|
|
9877
|
+
let bytesSent = 0;
|
|
9878
|
+
let pktsRecv = 0;
|
|
9879
|
+
sock.on("message", () => {
|
|
9880
|
+
pktsRecv++;
|
|
9881
|
+
});
|
|
9650
9882
|
const xml = buildC2dC({
|
|
9651
9883
|
uid: this.opts.uid,
|
|
9652
9884
|
clientPort: localPort,
|
|
@@ -9657,6 +9889,9 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9657
9889
|
const timeout = setTimeout(() => {
|
|
9658
9890
|
if (retryTimer) clearInterval(retryTimer);
|
|
9659
9891
|
sock.off("message", onMsg);
|
|
9892
|
+
log(
|
|
9893
|
+
`local discovery timeout after ${discoveryTimeout}ms \u2014 sent=${bytesSent}B replies=${pktsRecv} (camera likely sleeping / off-LAN / firewall dropping replies)`
|
|
9894
|
+
);
|
|
9660
9895
|
reject(
|
|
9661
9896
|
new Error(
|
|
9662
9897
|
`BCUDP discovery timeout after ${discoveryTimeout}ms (camera may be sleeping or unreachable)`
|
|
@@ -9850,6 +10085,7 @@ var BcUdpStream = class extends import_node_events.EventEmitter {
|
|
|
9850
10085
|
for (const port of ports) {
|
|
9851
10086
|
try {
|
|
9852
10087
|
sock.send(packet, port, host);
|
|
10088
|
+
bytesSent += packet.length;
|
|
9853
10089
|
retryCount++;
|
|
9854
10090
|
this.emit("debug", "discovery_send", { retryCount, host, port });
|
|
9855
10091
|
} catch {
|
|
@@ -11389,6 +11625,7 @@ var BaichuanClient = class _BaichuanClient extends import_node_events2.EventEmit
|
|
|
11389
11625
|
sock.on("debug", (event, data) => {
|
|
11390
11626
|
this.logDebug(`udp_${event}`, data);
|
|
11391
11627
|
});
|
|
11628
|
+
sock.setLogger(this.logger);
|
|
11392
11629
|
await sock.connect();
|
|
11393
11630
|
const shortUid = this.opts.uid ? this.opts.uid.substring(0, 5) : "";
|
|
11394
11631
|
const udpDiscoveryMethod = this.opts.udpDiscoveryMethod ?? "local-direct";
|
|
@@ -40496,7 +40733,11 @@ async function discoverUidForHost(host, logger) {
|
|
|
40496
40733
|
function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
40497
40734
|
const message = e?.message || e?.toString?.() || "";
|
|
40498
40735
|
if (typeof message !== "string") return false;
|
|
40499
|
-
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("
|
|
40736
|
+
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("EHOSTDOWN") || message.includes("EHOSTUNREACH") || message.includes("ENETUNREACH") || message.includes("ENETDOWN") || message.includes("socket hang up") || message.includes("TCP connection timeout") || // Autodetect's own hard deadline on the TCP login attempt — see
|
|
40737
|
+
// `withTcpDeadline` in `autoDetectDeviceType`. Without this entry the
|
|
40738
|
+
// catch block would rethrow the deadline error instead of awaiting
|
|
40739
|
+
// the speculative UDP race.
|
|
40740
|
+
message.includes("TCP login deadline exceeded") || message.includes("Baichuan socket closed") || message.includes("timeout waiting for nonce") || message.includes("expected encryption info") || message.includes("ECONNRESET") || message.includes("EPIPE");
|
|
40500
40741
|
}
|
|
40501
40742
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
40502
40743
|
if (!host || typeof host !== "string") return false;
|
|
@@ -40644,6 +40885,7 @@ function attachErrorHandler(api, transport, inputs) {
|
|
|
40644
40885
|
}
|
|
40645
40886
|
async function autoDetectDeviceType(inputs) {
|
|
40646
40887
|
const { host, uid, logger } = inputs;
|
|
40888
|
+
const autodetectStartedAt = Date.now();
|
|
40647
40889
|
const mode = inputs.mode ?? "auto";
|
|
40648
40890
|
const maxRetriesRaw = inputs.maxRetries;
|
|
40649
40891
|
const maxRetries = Math.max(
|
|
@@ -40660,9 +40902,31 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40660
40902
|
const sleepMs3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
40661
40903
|
const shouldRetryTcp = (e) => {
|
|
40662
40904
|
const msg = fmtErr(e);
|
|
40663
|
-
if (msg.includes("ECONNREFUSED"))
|
|
40905
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("EHOSTDOWN") || msg.includes("EHOSTUNREACH") || msg.includes("ENETUNREACH") || msg.includes("ENETDOWN")) {
|
|
40906
|
+
return false;
|
|
40907
|
+
}
|
|
40664
40908
|
return isTcpFailureThatShouldFallbackToUdp(e) || msg.includes("timeout waiting for nonce") || msg.includes("expected encryption info") || msg.includes("Baichuan socket closed") || msg.includes("ECONNRESET") || msg.includes("EPIPE");
|
|
40665
40909
|
};
|
|
40910
|
+
const tcpDeadlineMs = typeof inputs.tcpConnectTimeoutMs === "number" && Number.isFinite(inputs.tcpConnectTimeoutMs) && inputs.tcpConnectTimeoutMs > 0 ? inputs.tcpConnectTimeoutMs : 8e3;
|
|
40911
|
+
const withTcpDeadline = async (op) => {
|
|
40912
|
+
let timer;
|
|
40913
|
+
const deadline = new Promise((_, reject) => {
|
|
40914
|
+
timer = setTimeout(
|
|
40915
|
+
() => reject(
|
|
40916
|
+
new Error(
|
|
40917
|
+
`TCP login deadline exceeded (${tcpDeadlineMs}ms) \u2014 host unreachable`
|
|
40918
|
+
)
|
|
40919
|
+
),
|
|
40920
|
+
tcpDeadlineMs
|
|
40921
|
+
);
|
|
40922
|
+
timer.unref?.();
|
|
40923
|
+
});
|
|
40924
|
+
try {
|
|
40925
|
+
return await Promise.race([op, deadline]);
|
|
40926
|
+
} finally {
|
|
40927
|
+
if (timer) clearTimeout(timer);
|
|
40928
|
+
}
|
|
40929
|
+
};
|
|
40666
40930
|
const shouldRetryUdp = (e) => {
|
|
40667
40931
|
const msg = fmtErr(e);
|
|
40668
40932
|
return msg.includes("Not running") || msg.includes("Baichuan UDP stream closed") || msg.includes("Baichuan socket closed") || msg.includes("ETIMEDOUT") || msg.toLowerCase().includes("timeout");
|
|
@@ -40815,6 +41079,127 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40815
41079
|
"Forced UDP autodetect failed for all methods."
|
|
40816
41080
|
);
|
|
40817
41081
|
}
|
|
41082
|
+
const detectOverUdpApi = async (udpApi, udpDiscoveryMethod, resolvedUid) => {
|
|
41083
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
41084
|
+
udpApi.getInfo(),
|
|
41085
|
+
udpApi.getDeviceCapabilities(),
|
|
41086
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
41087
|
+
]);
|
|
41088
|
+
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
41089
|
+
const model = deviceInfo.type?.trim();
|
|
41090
|
+
const normalizedModel = model ? model.trim() : void 0;
|
|
41091
|
+
const isMultifocalByModel = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
41092
|
+
const channelNumValue = typeof channelNum === "string" ? Number.parseInt(channelNum, 10) : channelNum;
|
|
41093
|
+
const hasDualLensChannelCount = (channelNumValue === 2 || channelNumValue === 3) && Number.isFinite(channelNumValue);
|
|
41094
|
+
const isMultifocal = isMultifocalByModel || hasDualLensChannelCount;
|
|
41095
|
+
const hasBattery = capabilities?.capabilities?.hasBattery === true;
|
|
41096
|
+
udpApi.setIdleDisconnect(hasBattery);
|
|
41097
|
+
if (isMultifocal) {
|
|
41098
|
+
const detectionMethod = isMultifocalByModel ? "model match" : "channelNum fallback";
|
|
41099
|
+
logger?.log?.(
|
|
41100
|
+
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum}, hasBattery=${hasBattery}).`
|
|
41101
|
+
);
|
|
41102
|
+
return {
|
|
41103
|
+
type: "multifocal",
|
|
41104
|
+
transport: "udp",
|
|
41105
|
+
uid: resolvedUid,
|
|
41106
|
+
udpDiscoveryMethod,
|
|
41107
|
+
deviceInfo,
|
|
41108
|
+
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41109
|
+
channelNum,
|
|
41110
|
+
hasBattery,
|
|
41111
|
+
api: udpApi
|
|
41112
|
+
};
|
|
41113
|
+
}
|
|
41114
|
+
const deviceType = hasBattery ? "battery-cam" : "udp-camera";
|
|
41115
|
+
logger?.log?.(
|
|
41116
|
+
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected ${deviceType} (hasBattery=${hasBattery}, model=${normalizedModel ?? "unknown"}).`
|
|
41117
|
+
);
|
|
41118
|
+
return {
|
|
41119
|
+
type: deviceType,
|
|
41120
|
+
transport: "udp",
|
|
41121
|
+
uid: resolvedUid,
|
|
41122
|
+
udpDiscoveryMethod,
|
|
41123
|
+
deviceInfo,
|
|
41124
|
+
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41125
|
+
channelNum: 1,
|
|
41126
|
+
hasBattery,
|
|
41127
|
+
api: udpApi
|
|
41128
|
+
};
|
|
41129
|
+
};
|
|
41130
|
+
const udpRaceAbort = new AbortController();
|
|
41131
|
+
const speculativeUdpRace = mode === "auto" ? (async () => {
|
|
41132
|
+
const resolvedUid = await speculativeUidPromise;
|
|
41133
|
+
const viableMethods = selectViableUdpMethods(Boolean(resolvedUid));
|
|
41134
|
+
return await runUdpMethodsParallel(
|
|
41135
|
+
viableMethods,
|
|
41136
|
+
async (m, isInnerAborted) => {
|
|
41137
|
+
const isAborted = () => udpRaceAbort.signal.aborted || isInnerAborted();
|
|
41138
|
+
if (isAborted()) {
|
|
41139
|
+
throw new Error(
|
|
41140
|
+
`UDP(${m}) speculative race aborted before start`
|
|
41141
|
+
);
|
|
41142
|
+
}
|
|
41143
|
+
logger?.log?.(
|
|
41144
|
+
`[AutoDetect] (race) Trying UDP discovery method: ${m}...`
|
|
41145
|
+
);
|
|
41146
|
+
const udpApi = await withRetries(
|
|
41147
|
+
`UDP(${m})`,
|
|
41148
|
+
maxRetries,
|
|
41149
|
+
async (attempt) => {
|
|
41150
|
+
const apiInputs = {
|
|
41151
|
+
...inputs,
|
|
41152
|
+
udpDiscoveryMethod: m
|
|
41153
|
+
};
|
|
41154
|
+
if (resolvedUid) apiInputs.uid = resolvedUid;
|
|
41155
|
+
const api = createBaichuanApi(apiInputs, "udp");
|
|
41156
|
+
try {
|
|
41157
|
+
await api.login();
|
|
41158
|
+
return api;
|
|
41159
|
+
} catch (e) {
|
|
41160
|
+
try {
|
|
41161
|
+
await api.close({
|
|
41162
|
+
reason: `autodetect:udp_failed:${m}:attempt_${attempt}`
|
|
41163
|
+
});
|
|
41164
|
+
} catch {
|
|
41165
|
+
}
|
|
41166
|
+
throw e;
|
|
41167
|
+
}
|
|
41168
|
+
},
|
|
41169
|
+
shouldRetryUdp,
|
|
41170
|
+
isAborted
|
|
41171
|
+
);
|
|
41172
|
+
if (isAborted()) {
|
|
41173
|
+
try {
|
|
41174
|
+
await udpApi.close({
|
|
41175
|
+
reason: "autodetect:udp_aborted_after_tcp_won"
|
|
41176
|
+
});
|
|
41177
|
+
} catch {
|
|
41178
|
+
}
|
|
41179
|
+
throw new Error(
|
|
41180
|
+
`UDP(${m}) speculative race aborted after login`
|
|
41181
|
+
);
|
|
41182
|
+
}
|
|
41183
|
+
return detectOverUdpApi(udpApi, m, resolvedUid ?? "");
|
|
41184
|
+
},
|
|
41185
|
+
"Speculative UDP race failed for all methods."
|
|
41186
|
+
);
|
|
41187
|
+
})() : void 0;
|
|
41188
|
+
speculativeUdpRace?.then(
|
|
41189
|
+
(udpResult) => {
|
|
41190
|
+
if (udpRaceAbort.signal.aborted && udpResult?.api) {
|
|
41191
|
+
udpResult.api.close({ reason: "autodetect:tcp_won_race" }).catch(() => void 0);
|
|
41192
|
+
}
|
|
41193
|
+
},
|
|
41194
|
+
() => void 0
|
|
41195
|
+
);
|
|
41196
|
+
const _tcpWin = (result) => {
|
|
41197
|
+
udpRaceAbort.abort();
|
|
41198
|
+
logger?.log?.(
|
|
41199
|
+
`[AutoDetect] DONE in ${Date.now() - autodetectStartedAt}ms via TCP \u2014 type=${result.type} model=${result.deviceInfo?.type ?? "?"} channels=${result.channelNum}`
|
|
41200
|
+
);
|
|
41201
|
+
return result;
|
|
41202
|
+
};
|
|
40818
41203
|
let tcpApi;
|
|
40819
41204
|
try {
|
|
40820
41205
|
logger?.log?.(`[AutoDetect] Trying TCP connection to ${host}...`);
|
|
@@ -40824,7 +41209,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40824
41209
|
async (attempt) => {
|
|
40825
41210
|
const api2 = createBaichuanApi(inputs, "tcp");
|
|
40826
41211
|
try {
|
|
40827
|
-
await api2.login();
|
|
41212
|
+
await withTcpDeadline(api2.login());
|
|
40828
41213
|
return api2;
|
|
40829
41214
|
} catch (e) {
|
|
40830
41215
|
try {
|
|
@@ -40936,7 +41321,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40936
41321
|
logger?.log?.(
|
|
40937
41322
|
`[AutoDetect] Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum})`
|
|
40938
41323
|
);
|
|
40939
|
-
return {
|
|
41324
|
+
return _tcpWin({
|
|
40940
41325
|
type: "multifocal",
|
|
40941
41326
|
transport: "tcp",
|
|
40942
41327
|
uid: effectiveUid || uid || "",
|
|
@@ -40944,13 +41329,13 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40944
41329
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
40945
41330
|
channelNum: effectiveChannelNum,
|
|
40946
41331
|
api
|
|
40947
|
-
};
|
|
41332
|
+
});
|
|
40948
41333
|
}
|
|
40949
41334
|
if (effectiveChannelNum > 1) {
|
|
40950
41335
|
logger?.log?.(
|
|
40951
41336
|
`[AutoDetect] Detected NVR (${effectiveChannelNum} channels)`
|
|
40952
41337
|
);
|
|
40953
|
-
return {
|
|
41338
|
+
return _tcpWin({
|
|
40954
41339
|
type: "nvr",
|
|
40955
41340
|
transport: "tcp",
|
|
40956
41341
|
uid: effectiveUid || uid || "",
|
|
@@ -40958,10 +41343,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40958
41343
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
40959
41344
|
channelNum: effectiveChannelNum,
|
|
40960
41345
|
api
|
|
40961
|
-
};
|
|
41346
|
+
});
|
|
40962
41347
|
}
|
|
40963
41348
|
logger?.log?.(`[AutoDetect] Detected regular camera (single channel)`);
|
|
40964
|
-
return {
|
|
41349
|
+
return _tcpWin({
|
|
40965
41350
|
type: "camera",
|
|
40966
41351
|
transport: "tcp",
|
|
40967
41352
|
uid: effectiveUid || uid || "",
|
|
@@ -40969,7 +41354,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40969
41354
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
40970
41355
|
channelNum: 1,
|
|
40971
41356
|
api
|
|
40972
|
-
};
|
|
41357
|
+
});
|
|
40973
41358
|
} catch (tcpError) {
|
|
40974
41359
|
if (mode === "tcp") {
|
|
40975
41360
|
throw tcpError;
|
|
@@ -40984,100 +41369,20 @@ async function autoDetectDeviceType(inputs) {
|
|
|
40984
41369
|
throw tcpError;
|
|
40985
41370
|
}
|
|
40986
41371
|
logger?.log?.(`[AutoDetect] TCP failed, trying UDP...`);
|
|
40987
|
-
|
|
40988
|
-
|
|
40989
|
-
|
|
40990
|
-
`[AutoDetect] UID discovery failed; only local-direct can run without a UID. If the camera is sleeping or on a different subnet, supply its UID to enable BCUDP P2P fallback (remote/relay/map) which can wake it via Reolink's servers.`
|
|
40991
|
-
);
|
|
40992
|
-
} else if (effectiveUid === void 0) {
|
|
40993
|
-
logger?.log?.(
|
|
40994
|
-
`[AutoDetect] UID resolved via concurrent broadcast discovery: ${normalizedUid}`
|
|
41372
|
+
if (!speculativeUdpRace) {
|
|
41373
|
+
throw new Error(
|
|
41374
|
+
`AutoDetect internal: speculative UDP race missing in mode=${mode}`
|
|
40995
41375
|
);
|
|
40996
41376
|
}
|
|
40997
41377
|
try {
|
|
40998
|
-
const
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
udpApi.getDeviceCapabilities(),
|
|
41002
|
-
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
41003
|
-
]);
|
|
41004
|
-
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
41005
|
-
const model = deviceInfo.type?.trim();
|
|
41006
|
-
const normalizedModel = model ? model.trim() : void 0;
|
|
41007
|
-
const isMultifocalByModel = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
41008
|
-
const channelNumValue = typeof channelNum === "string" ? Number.parseInt(channelNum, 10) : channelNum;
|
|
41009
|
-
const hasDualLensChannelCount = (channelNumValue === 2 || channelNumValue === 3) && Number.isFinite(channelNumValue);
|
|
41010
|
-
const isMultifocal = isMultifocalByModel || hasDualLensChannelCount;
|
|
41011
|
-
const hasBattery = capabilities?.capabilities?.hasBattery === true;
|
|
41012
|
-
udpApi.setIdleDisconnect(hasBattery);
|
|
41013
|
-
if (isMultifocal) {
|
|
41014
|
-
const detectionMethod = isMultifocalByModel ? "model match" : "channelNum fallback";
|
|
41015
|
-
logger?.log?.(
|
|
41016
|
-
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum}, hasBattery=${hasBattery}).`
|
|
41017
|
-
);
|
|
41018
|
-
return {
|
|
41019
|
-
type: "multifocal",
|
|
41020
|
-
transport: "udp",
|
|
41021
|
-
uid: normalizedUid ?? "",
|
|
41022
|
-
udpDiscoveryMethod,
|
|
41023
|
-
deviceInfo,
|
|
41024
|
-
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41025
|
-
channelNum,
|
|
41026
|
-
hasBattery,
|
|
41027
|
-
api: udpApi
|
|
41028
|
-
};
|
|
41029
|
-
}
|
|
41030
|
-
const deviceType = hasBattery ? "battery-cam" : "udp-camera";
|
|
41031
|
-
logger?.log?.(
|
|
41032
|
-
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected ${deviceType} (hasBattery=${hasBattery}, model=${normalizedModel ?? "unknown"}).`
|
|
41033
|
-
);
|
|
41034
|
-
return {
|
|
41035
|
-
type: deviceType,
|
|
41036
|
-
transport: "udp",
|
|
41037
|
-
uid: normalizedUid ?? "",
|
|
41038
|
-
udpDiscoveryMethod,
|
|
41039
|
-
deviceInfo,
|
|
41040
|
-
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
41041
|
-
channelNum: 1,
|
|
41042
|
-
hasBattery,
|
|
41043
|
-
api: udpApi
|
|
41044
|
-
};
|
|
41045
|
-
};
|
|
41046
|
-
const viableMethods = selectViableUdpMethods(Boolean(normalizedUid));
|
|
41047
|
-
return await runUdpMethodsParallel(
|
|
41048
|
-
viableMethods,
|
|
41049
|
-
async (m, isAborted) => {
|
|
41050
|
-
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
41051
|
-
const udpApi = await withRetries(
|
|
41052
|
-
`UDP(${m})`,
|
|
41053
|
-
maxRetries,
|
|
41054
|
-
async (attempt) => {
|
|
41055
|
-
const apiInputs = { ...inputs, udpDiscoveryMethod: m };
|
|
41056
|
-
if (normalizedUid) apiInputs.uid = normalizedUid;
|
|
41057
|
-
const api = createBaichuanApi(apiInputs, "udp");
|
|
41058
|
-
try {
|
|
41059
|
-
await api.login();
|
|
41060
|
-
return api;
|
|
41061
|
-
} catch (e) {
|
|
41062
|
-
try {
|
|
41063
|
-
await api.close({
|
|
41064
|
-
reason: `autodetect:udp_failed:${m}:attempt_${attempt}`
|
|
41065
|
-
});
|
|
41066
|
-
} catch {
|
|
41067
|
-
}
|
|
41068
|
-
throw e;
|
|
41069
|
-
}
|
|
41070
|
-
},
|
|
41071
|
-
shouldRetryUdp,
|
|
41072
|
-
isAborted
|
|
41073
|
-
);
|
|
41074
|
-
return detectOverUdpApi(udpApi, m);
|
|
41075
|
-
},
|
|
41076
|
-
"UDP discovery failed for all methods."
|
|
41378
|
+
const udpResult = await speculativeUdpRace;
|
|
41379
|
+
logger?.log?.(
|
|
41380
|
+
`[AutoDetect] DONE in ${Date.now() - autodetectStartedAt}ms via UDP \u2014 type=${udpResult.type} method=${udpResult.udpDiscoveryMethod ?? "n/a"} model=${udpResult.deviceInfo?.type ?? "?"} channels=${udpResult.channelNum}`
|
|
41077
41381
|
);
|
|
41382
|
+
return udpResult;
|
|
41078
41383
|
} catch (udpError) {
|
|
41079
41384
|
logger?.log?.(
|
|
41080
|
-
`[AutoDetect]
|
|
41385
|
+
`[AutoDetect] FAILED after ${Date.now() - autodetectStartedAt}ms \u2014 neither TCP nor UDP could reach the camera. TCP: ${tcpError?.message ?? tcpError}. UDP: ${udpError?.message ?? udpError}`
|
|
41081
41386
|
);
|
|
41082
41387
|
throw new Error(
|
|
41083
41388
|
`Failed to connect via both TCP and UDP. TCP: ${tcpError?.message || tcpError}, UDP: ${udpError?.message || udpError}`
|
|
@@ -42927,6 +43232,8 @@ function buildInitialStatus(config) {
|
|
|
42927
43232
|
BC_CMD_ID_COVER_STANDALONE_460,
|
|
42928
43233
|
BC_CMD_ID_COVER_STANDALONE_461,
|
|
42929
43234
|
BC_CMD_ID_COVER_STANDALONE_462,
|
|
43235
|
+
BC_CMD_ID_DEVICE_DETECT,
|
|
43236
|
+
BC_CMD_ID_DEVICE_DETECT_CANDIDATES,
|
|
42930
43237
|
BC_CMD_ID_DING_DONG_CTRL,
|
|
42931
43238
|
BC_CMD_ID_DING_DONG_OPT,
|
|
42932
43239
|
BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|