@camstack/addon-provider-reolink 1.1.13 → 1.1.15
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/addon.js +312 -40
- package/dist/addon.mjs +312 -40
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -4655,7 +4655,7 @@ function _instanceof(cls, params = {}) {
|
|
|
4655
4655
|
return inst;
|
|
4656
4656
|
}
|
|
4657
4657
|
//#endregion
|
|
4658
|
-
//#region ../types/dist/sleep-
|
|
4658
|
+
//#region ../types/dist/sleep-MHm--th-.mjs
|
|
4659
4659
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4660
4660
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4661
4661
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -5917,6 +5917,7 @@ var CamStreamKindSchema = _enum([
|
|
|
5917
5917
|
"pull-rtsp",
|
|
5918
5918
|
"pull-rtmp",
|
|
5919
5919
|
"pull-http",
|
|
5920
|
+
"pull-flv",
|
|
5920
5921
|
"pull-rfc4571",
|
|
5921
5922
|
"push-annexb",
|
|
5922
5923
|
"derived"
|
|
@@ -16291,7 +16292,7 @@ var AddBrokerInputSchema = object({
|
|
|
16291
16292
|
});
|
|
16292
16293
|
var AddBrokerResultSchema = object({ id: string() });
|
|
16293
16294
|
var IdInputSchema = object({ id: string() });
|
|
16294
|
-
var TestResultSchema = discriminatedUnion("ok", [object({
|
|
16295
|
+
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
16295
16296
|
ok: literal(true),
|
|
16296
16297
|
latencyMs: number()
|
|
16297
16298
|
}), object({
|
|
@@ -16314,7 +16315,7 @@ var StatusSchema = object({
|
|
|
16314
16315
|
brokerCount: number(),
|
|
16315
16316
|
embeddedRunning: boolean()
|
|
16316
16317
|
});
|
|
16317
|
-
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
16318
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
16318
16319
|
var NetworkEndpointSchema = object({
|
|
16319
16320
|
url: string(),
|
|
16320
16321
|
hostname: string(),
|
|
@@ -16348,23 +16349,198 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
16348
16349
|
sourcePort: number().optional()
|
|
16349
16350
|
});
|
|
16350
16351
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
16351
|
-
|
|
16352
|
-
|
|
16352
|
+
/**
|
|
16353
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
16354
|
+
*
|
|
16355
|
+
* Apprise-derived model (see
|
|
16356
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
16357
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
16358
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
16359
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
16360
|
+
* message to what the kind supports — callers never special-case a service.
|
|
16361
|
+
*
|
|
16362
|
+
* DESIGN DECISIONS (locked):
|
|
16363
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
16364
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
16365
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
16366
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
16367
|
+
* alternative would fork the UI per addon and cannot host the
|
|
16368
|
+
* discovery→adopt flow.
|
|
16369
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
16370
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
16371
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
16372
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
16373
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
16374
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
16375
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
16376
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
16377
|
+
* base64 fallback needed.
|
|
16378
|
+
*
|
|
16379
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
16380
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
16381
|
+
* admin "Integrations" page.
|
|
16382
|
+
*/
|
|
16383
|
+
/**
|
|
16384
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
16385
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
16386
|
+
*/
|
|
16387
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
16388
|
+
"image",
|
|
16389
|
+
"video",
|
|
16390
|
+
"gif",
|
|
16391
|
+
"audio",
|
|
16392
|
+
"icon"
|
|
16393
|
+
]);
|
|
16394
|
+
/**
|
|
16395
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
16396
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
16397
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
16398
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
16399
|
+
*/
|
|
16400
|
+
var AttachmentSchema = object({
|
|
16401
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
16402
|
+
url: string().optional(),
|
|
16403
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
16404
|
+
mime: string().optional(),
|
|
16405
|
+
name: string().optional()
|
|
16406
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
16407
|
+
var NotificationFormatSchema = _enum([
|
|
16408
|
+
"text",
|
|
16409
|
+
"markdown",
|
|
16410
|
+
"html"
|
|
16411
|
+
]);
|
|
16412
|
+
/** A single tap-through action button. */
|
|
16413
|
+
var NotificationActionSchema = object({
|
|
16414
|
+
id: string(),
|
|
16415
|
+
label: string(),
|
|
16416
|
+
url: string().optional()
|
|
16417
|
+
});
|
|
16418
|
+
/**
|
|
16419
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
16420
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
16421
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
16422
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
16423
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
16424
|
+
* `priority` for that one target.
|
|
16425
|
+
*/
|
|
16426
|
+
var NotificationSchema = object({
|
|
16353
16427
|
body: string(),
|
|
16354
|
-
|
|
16428
|
+
title: string().optional(),
|
|
16429
|
+
format: NotificationFormatSchema.default("text"),
|
|
16430
|
+
priority: number().int().min(1).max(5).default(3),
|
|
16431
|
+
level: string().optional(),
|
|
16432
|
+
attachments: array(AttachmentSchema).optional(),
|
|
16433
|
+
clickUrl: string().optional(),
|
|
16434
|
+
actions: array(NotificationActionSchema).optional(),
|
|
16435
|
+
sound: string().optional(),
|
|
16436
|
+
ttl: number().optional(),
|
|
16437
|
+
tag: string().optional(),
|
|
16355
16438
|
deviceId: number().optional(),
|
|
16356
16439
|
eventId: string().optional(),
|
|
16357
|
-
priority: _enum([
|
|
16358
|
-
"low",
|
|
16359
|
-
"normal",
|
|
16360
|
-
"high",
|
|
16361
|
-
"critical"
|
|
16362
|
-
]).default("normal"),
|
|
16363
16440
|
metadata: record(string(), unknown()).optional()
|
|
16364
|
-
})
|
|
16441
|
+
});
|
|
16442
|
+
/** One declared native severity/priority level for a kind. */
|
|
16443
|
+
var TargetKindLevelSchema = object({
|
|
16444
|
+
id: string(),
|
|
16445
|
+
label: string(),
|
|
16446
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
16447
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
16448
|
+
flags: object({
|
|
16449
|
+
critical: boolean().optional(),
|
|
16450
|
+
silent: boolean().optional(),
|
|
16451
|
+
noPush: boolean().optional()
|
|
16452
|
+
}).optional(),
|
|
16453
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
16454
|
+
requires: array(string()).optional(),
|
|
16455
|
+
description: string().optional()
|
|
16456
|
+
});
|
|
16457
|
+
/** The full capability block consulted before dispatch. */
|
|
16458
|
+
var TargetKindCapsSchema = object({
|
|
16459
|
+
attachments: object({
|
|
16460
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
16461
|
+
mode: _enum([
|
|
16462
|
+
"url",
|
|
16463
|
+
"bytes",
|
|
16464
|
+
"both"
|
|
16465
|
+
]),
|
|
16466
|
+
max: number().int().nonnegative(),
|
|
16467
|
+
maxBytes: number().int().positive().optional()
|
|
16468
|
+
}),
|
|
16469
|
+
/** Max action buttons (0 = none). */
|
|
16470
|
+
actions: number().int().nonnegative(),
|
|
16471
|
+
levels: array(TargetKindLevelSchema),
|
|
16472
|
+
format: array(NotificationFormatSchema),
|
|
16473
|
+
clickUrl: boolean(),
|
|
16474
|
+
sound: boolean(),
|
|
16475
|
+
ttl: boolean(),
|
|
16476
|
+
bodyMaxLen: number().int().positive()
|
|
16477
|
+
});
|
|
16478
|
+
/**
|
|
16479
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
16480
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
16481
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
16482
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
16483
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
16484
|
+
*/
|
|
16485
|
+
var ConfigSchemaPassthrough = unknown();
|
|
16486
|
+
var TargetKindSchema = object({
|
|
16487
|
+
kind: string(),
|
|
16488
|
+
label: string(),
|
|
16489
|
+
icon: string(),
|
|
16490
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16491
|
+
addonId: string(),
|
|
16492
|
+
configSchema: ConfigSchemaPassthrough,
|
|
16493
|
+
supportsDiscovery: boolean(),
|
|
16494
|
+
caps: TargetKindCapsSchema
|
|
16495
|
+
});
|
|
16496
|
+
/**
|
|
16497
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
16498
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
16499
|
+
* round-trip a stored secret to the UI.
|
|
16500
|
+
*/
|
|
16501
|
+
var TargetSchema = object({
|
|
16502
|
+
id: string(),
|
|
16503
|
+
name: string(),
|
|
16504
|
+
kind: string(),
|
|
16505
|
+
addonId: string(),
|
|
16506
|
+
enabled: boolean(),
|
|
16507
|
+
config: record(string(), unknown())
|
|
16508
|
+
});
|
|
16509
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
16510
|
+
var DiscoveredTargetSchema = object({
|
|
16511
|
+
kind: string(),
|
|
16512
|
+
suggestedName: string(),
|
|
16513
|
+
config: record(string(), unknown())
|
|
16514
|
+
});
|
|
16515
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
16516
|
+
var RenderedAsSchema = object({
|
|
16517
|
+
level: string(),
|
|
16518
|
+
format: NotificationFormatSchema,
|
|
16519
|
+
attachmentsSent: number().int().nonnegative(),
|
|
16520
|
+
actionsSent: number().int().nonnegative(),
|
|
16521
|
+
truncated: boolean(),
|
|
16522
|
+
dropped: array(string())
|
|
16523
|
+
});
|
|
16524
|
+
var SendResultSchema = object({
|
|
16365
16525
|
success: boolean(),
|
|
16366
|
-
error: string().optional()
|
|
16367
|
-
|
|
16526
|
+
error: string().optional(),
|
|
16527
|
+
renderedAs: RenderedAsSchema.optional()
|
|
16528
|
+
});
|
|
16529
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
16530
|
+
var TestResultSchema = SendResultSchema;
|
|
16531
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
|
|
16532
|
+
kind: string(),
|
|
16533
|
+
config: record(string(), unknown()).optional()
|
|
16534
|
+
}), array(DiscoveredTargetSchema)), method(object({
|
|
16535
|
+
targetId: string(),
|
|
16536
|
+
notification: NotificationSchema
|
|
16537
|
+
}), SendResultSchema, { kind: "mutation" }), method(object({
|
|
16538
|
+
targetId: string(),
|
|
16539
|
+
sample: NotificationSchema.optional()
|
|
16540
|
+
}), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
|
|
16541
|
+
targetId: string(),
|
|
16542
|
+
enabled: boolean()
|
|
16543
|
+
}), _void(), { kind: "mutation" });
|
|
16368
16544
|
/**
|
|
16369
16545
|
* Zod schemas for persisted record types.
|
|
16370
16546
|
*
|
|
@@ -22731,13 +22907,49 @@ Object.freeze({
|
|
|
22731
22907
|
addonId: null,
|
|
22732
22908
|
access: "create"
|
|
22733
22909
|
},
|
|
22910
|
+
"notificationOutput.deleteTarget": {
|
|
22911
|
+
capName: "notification-output",
|
|
22912
|
+
capScope: "system",
|
|
22913
|
+
addonId: null,
|
|
22914
|
+
access: "delete"
|
|
22915
|
+
},
|
|
22916
|
+
"notificationOutput.discoverTargets": {
|
|
22917
|
+
capName: "notification-output",
|
|
22918
|
+
capScope: "system",
|
|
22919
|
+
addonId: null,
|
|
22920
|
+
access: "view"
|
|
22921
|
+
},
|
|
22922
|
+
"notificationOutput.listTargetKinds": {
|
|
22923
|
+
capName: "notification-output",
|
|
22924
|
+
capScope: "system",
|
|
22925
|
+
addonId: null,
|
|
22926
|
+
access: "view"
|
|
22927
|
+
},
|
|
22928
|
+
"notificationOutput.listTargets": {
|
|
22929
|
+
capName: "notification-output",
|
|
22930
|
+
capScope: "system",
|
|
22931
|
+
addonId: null,
|
|
22932
|
+
access: "view"
|
|
22933
|
+
},
|
|
22734
22934
|
"notificationOutput.send": {
|
|
22735
22935
|
capName: "notification-output",
|
|
22736
22936
|
capScope: "system",
|
|
22737
22937
|
addonId: null,
|
|
22738
22938
|
access: "create"
|
|
22739
22939
|
},
|
|
22740
|
-
"notificationOutput.
|
|
22940
|
+
"notificationOutput.setTargetEnabled": {
|
|
22941
|
+
capName: "notification-output",
|
|
22942
|
+
capScope: "system",
|
|
22943
|
+
addonId: null,
|
|
22944
|
+
access: "create"
|
|
22945
|
+
},
|
|
22946
|
+
"notificationOutput.testTarget": {
|
|
22947
|
+
capName: "notification-output",
|
|
22948
|
+
capScope: "system",
|
|
22949
|
+
addonId: null,
|
|
22950
|
+
access: "create"
|
|
22951
|
+
},
|
|
22952
|
+
"notificationOutput.upsertTarget": {
|
|
22741
22953
|
capName: "notification-output",
|
|
22742
22954
|
capScope: "system",
|
|
22743
22955
|
addonId: null,
|
|
@@ -215236,7 +215448,7 @@ function isNativeObjectForwardingEnabled(capState) {
|
|
|
215236
215448
|
}
|
|
215237
215449
|
//#endregion
|
|
215238
215450
|
//#region src/stream-routing.ts
|
|
215239
|
-
var KIND_RE = /^(native|rtsp|rtmp):(.+)$/;
|
|
215451
|
+
var KIND_RE = /^(native|rtsp|rtmp|flv):(.+)$/;
|
|
215240
215452
|
var CH_PROFILE_RE = /^ch(\d+)-(main|sub|ext)$/;
|
|
215241
215453
|
var PROFILE_ONLY_RE = /^(main|sub|ext)$/;
|
|
215242
215454
|
/**
|
|
@@ -215286,6 +215498,7 @@ function kindLabel(kind) {
|
|
|
215286
215498
|
case "native": return "Native";
|
|
215287
215499
|
case "rtsp": return "RTSP";
|
|
215288
215500
|
case "rtmp": return "RTMP";
|
|
215501
|
+
case "flv": return "FLV";
|
|
215289
215502
|
}
|
|
215290
215503
|
}
|
|
215291
215504
|
/**
|
|
@@ -219172,6 +219385,23 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
219172
219385
|
}
|
|
219173
219386
|
}
|
|
219174
219387
|
/**
|
|
219388
|
+
* Synthesize the HTTP-FLV pull URL for a (channel, profile) pair. FLV is
|
|
219389
|
+
* served off the Reolink Baichuan media port (default 1935, app `bcs`) as
|
|
219390
|
+
* `channel<channel>_<profile>.bcs` — the same shape Frigate/go2rtc use. The
|
|
219391
|
+
* lib does NOT surface this URL, so we build it from the SAME connection
|
|
219392
|
+
* creds the RTSP/native paths use (`host`/`username`/`password` on the
|
|
219393
|
+
* device config), URL-encoding the credentials.
|
|
219394
|
+
*
|
|
219395
|
+
* Returns `null` when the host isn't locally resolvable (hub-child cameras
|
|
219396
|
+
* borrow the parent's socket and hold no own `host`), so the caller skips
|
|
219397
|
+
* FLV publication for those devices rather than emitting a broken URL.
|
|
219398
|
+
*/
|
|
219399
|
+
buildFlvUrl(channel, profile) {
|
|
219400
|
+
const host = this.config.get("host");
|
|
219401
|
+
if (typeof host !== "string" || host.length === 0) return null;
|
|
219402
|
+
return `http://${host}/flv?port=1935&app=bcs&stream=channel${channel}_${profile}.bcs&user=${encodeURIComponent(String(this.config.get("username") ?? ""))}&password=${encodeURIComponent(String(this.config.get("password") ?? ""))}`;
|
|
219403
|
+
}
|
|
219404
|
+
/**
|
|
219175
219405
|
* Heavy path: talk to the camera and assemble the descriptors. Only ever
|
|
219176
219406
|
* entered when there is no cached catalog (see `buildStreamCatalog`).
|
|
219177
219407
|
*
|
|
@@ -219257,6 +219487,27 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
219257
219487
|
};
|
|
219258
219488
|
enqueueAlt("rtsp", streamOptions.rtspStreams);
|
|
219259
219489
|
enqueueAlt("rtmp", streamOptions.rtmpStreams);
|
|
219490
|
+
for (const s of streamOptions.nativeStreams) {
|
|
219491
|
+
const channel = s.channel ?? (isChild ? ownChannel : 0);
|
|
219492
|
+
const id = buildCamStreamId("flv", channel, s.profile, channelCount);
|
|
219493
|
+
if (desired.has(id)) continue;
|
|
219494
|
+
const flvUrl = this.buildFlvUrl(channel, s.profile);
|
|
219495
|
+
if (!flvUrl) break;
|
|
219496
|
+
const m = s.metadata;
|
|
219497
|
+
desired.set(id, {
|
|
219498
|
+
camStreamId: id,
|
|
219499
|
+
kind: "pull-flv",
|
|
219500
|
+
label: streamLabel(s, "flv"),
|
|
219501
|
+
url: flvUrl,
|
|
219502
|
+
autoEligible: false,
|
|
219503
|
+
...m && m.width > 0 && m.height > 0 ? { resolution: {
|
|
219504
|
+
width: m.width,
|
|
219505
|
+
height: m.height
|
|
219506
|
+
} } : {},
|
|
219507
|
+
...m && m.frameRate > 0 ? { fps: m.frameRate } : {},
|
|
219508
|
+
...normalizeCodecName(m?.videoEncType) ? { codec: normalizeCodecName(m?.videoEncType) } : {}
|
|
219509
|
+
});
|
|
219510
|
+
}
|
|
219260
219511
|
if (desired.size === 0) {
|
|
219261
219512
|
this.ctx.logger.warn("buildStreamCatalog: camera reports no streams — empty catalog", { tags: { deviceId: this.id } });
|
|
219262
219513
|
return [];
|
|
@@ -222977,6 +223228,47 @@ function buildCreationFormSchema() {
|
|
|
222977
223228
|
] };
|
|
222978
223229
|
}
|
|
222979
223230
|
//#endregion
|
|
223231
|
+
//#region src/reolink-discovery-map.ts
|
|
223232
|
+
/**
|
|
223233
|
+
* Flatten a host (IP / hostname) into a flat row-key slug — shared by `generateStableId` and the
|
|
223234
|
+
* discovery candidate mapping so a host-keyed camera is detected as already onboarded on re-scan.
|
|
223235
|
+
*/
|
|
223236
|
+
function slugifyReolinkHost(host) {
|
|
223237
|
+
return host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
223238
|
+
}
|
|
223239
|
+
/**
|
|
223240
|
+
* Map discovered Reolink hosts to adoption {@link DiscoveryCandidate}s, de-duplicated by host (the
|
|
223241
|
+
* same camera can answer on more than one discovery method — UDP broadcast, ONVIF, HTTP scan). The
|
|
223242
|
+
* first responder for a host wins. Pure: no I/O.
|
|
223243
|
+
*
|
|
223244
|
+
* The authoritative stableId is `mac-<mac>` (learned during autodetect at adopt time), which discovery
|
|
223245
|
+
* can't produce — so a re-scan of a MAC-keyed camera may still show as addable. The `host-` key still
|
|
223246
|
+
* lets host-added cameras be detected as onboarded on re-scan.
|
|
223247
|
+
*/
|
|
223248
|
+
function mapReolinkDiscoveryToCandidates(devices, credentials) {
|
|
223249
|
+
const username = credentials.username?.trim() ?? "";
|
|
223250
|
+
const password = credentials.password ?? "";
|
|
223251
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
223252
|
+
for (const d of devices) if (!byHost.has(d.host)) byHost.set(d.host, d);
|
|
223253
|
+
return [...byHost.values()].map((d) => {
|
|
223254
|
+
const displayName = d.name ?? d.model ?? d.host;
|
|
223255
|
+
return {
|
|
223256
|
+
stableId: `host-${slugifyReolinkHost(d.host)}`,
|
|
223257
|
+
type: DeviceType.Camera,
|
|
223258
|
+
suggestedName: displayName,
|
|
223259
|
+
prefilledConfig: {
|
|
223260
|
+
name: displayName,
|
|
223261
|
+
host: d.host,
|
|
223262
|
+
transport: "auto",
|
|
223263
|
+
...d.httpPort !== void 0 ? { port: d.httpPort } : {},
|
|
223264
|
+
...d.uid ? { uid: d.uid } : {},
|
|
223265
|
+
...username ? { username } : {},
|
|
223266
|
+
...password ? { password } : {}
|
|
223267
|
+
}
|
|
223268
|
+
};
|
|
223269
|
+
});
|
|
223270
|
+
}
|
|
223271
|
+
//#endregion
|
|
222980
223272
|
//#region src/autodetect-cache.ts
|
|
222981
223273
|
var DEFAULT_TTL_MS = 6e4;
|
|
222982
223274
|
var AutodetectCache = class {
|
|
@@ -223489,11 +223781,6 @@ function isMeaningfulIdentifier(s) {
|
|
|
223489
223781
|
if (!s || s.length < 6) return false;
|
|
223490
223782
|
return new Set(s.toLowerCase().split("").filter((c) => c !== "0" && c !== "f")).size >= 1;
|
|
223491
223783
|
}
|
|
223492
|
-
/** Flatten a host (IP / hostname) into a flat row-key slug — shared by `generateStableId` and the
|
|
223493
|
-
* discovery candidate mapping so a host-keyed camera is detected as already onboarded on re-scan. */
|
|
223494
|
-
function slugifyReolinkHost(host) {
|
|
223495
|
-
return host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
223496
|
-
}
|
|
223497
223784
|
/**
|
|
223498
223785
|
* Patch `detection.deviceInfo` + `hostNetworkInfo` in-place with the
|
|
223499
223786
|
* post-login HOST identifiers. Two distinct gaps to fill:
|
|
@@ -223788,24 +224075,9 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
|
|
|
223788
224075
|
networkCidr: networkCidr || "local",
|
|
223789
224076
|
enableOnvif
|
|
223790
224077
|
} });
|
|
223791
|
-
|
|
223792
|
-
|
|
223793
|
-
|
|
223794
|
-
const displayName = d.name ?? d.model ?? d.host;
|
|
223795
|
-
return {
|
|
223796
|
-
stableId: `host-${slugifyReolinkHost(d.host)}`,
|
|
223797
|
-
type: DeviceType.Camera,
|
|
223798
|
-
suggestedName: displayName,
|
|
223799
|
-
prefilledConfig: {
|
|
223800
|
-
name: displayName,
|
|
223801
|
-
host: d.host,
|
|
223802
|
-
transport: "auto",
|
|
223803
|
-
...d.httpPort !== void 0 ? { port: d.httpPort } : {},
|
|
223804
|
-
...d.uid ? { uid: d.uid } : {},
|
|
223805
|
-
...username ? { username } : {},
|
|
223806
|
-
...password ? { password } : {}
|
|
223807
|
-
}
|
|
223808
|
-
};
|
|
224078
|
+
return mapReolinkDiscoveryToCandidates(devices, {
|
|
224079
|
+
username,
|
|
224080
|
+
password
|
|
223809
224081
|
});
|
|
223810
224082
|
}
|
|
223811
224083
|
async adoptDiscoveredDevice(input) {
|
package/dist/addon.mjs
CHANGED
|
@@ -4650,7 +4650,7 @@ function _instanceof(cls, params = {}) {
|
|
|
4650
4650
|
return inst;
|
|
4651
4651
|
}
|
|
4652
4652
|
//#endregion
|
|
4653
|
-
//#region ../types/dist/sleep-
|
|
4653
|
+
//#region ../types/dist/sleep-MHm--th-.mjs
|
|
4654
4654
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4655
4655
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4656
4656
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -5912,6 +5912,7 @@ var CamStreamKindSchema = _enum([
|
|
|
5912
5912
|
"pull-rtsp",
|
|
5913
5913
|
"pull-rtmp",
|
|
5914
5914
|
"pull-http",
|
|
5915
|
+
"pull-flv",
|
|
5915
5916
|
"pull-rfc4571",
|
|
5916
5917
|
"push-annexb",
|
|
5917
5918
|
"derived"
|
|
@@ -16286,7 +16287,7 @@ var AddBrokerInputSchema = object({
|
|
|
16286
16287
|
});
|
|
16287
16288
|
var AddBrokerResultSchema = object({ id: string() });
|
|
16288
16289
|
var IdInputSchema = object({ id: string() });
|
|
16289
|
-
var TestResultSchema = discriminatedUnion("ok", [object({
|
|
16290
|
+
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
16290
16291
|
ok: literal(true),
|
|
16291
16292
|
latencyMs: number()
|
|
16292
16293
|
}), object({
|
|
@@ -16309,7 +16310,7 @@ var StatusSchema = object({
|
|
|
16309
16310
|
brokerCount: number(),
|
|
16310
16311
|
embeddedRunning: boolean()
|
|
16311
16312
|
});
|
|
16312
|
-
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
16313
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
16313
16314
|
var NetworkEndpointSchema = object({
|
|
16314
16315
|
url: string(),
|
|
16315
16316
|
hostname: string(),
|
|
@@ -16343,23 +16344,198 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
16343
16344
|
sourcePort: number().optional()
|
|
16344
16345
|
});
|
|
16345
16346
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
16346
|
-
|
|
16347
|
-
|
|
16347
|
+
/**
|
|
16348
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
16349
|
+
*
|
|
16350
|
+
* Apprise-derived model (see
|
|
16351
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
16352
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
16353
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
16354
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
16355
|
+
* message to what the kind supports — callers never special-case a service.
|
|
16356
|
+
*
|
|
16357
|
+
* DESIGN DECISIONS (locked):
|
|
16358
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
16359
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
16360
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
16361
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
16362
|
+
* alternative would fork the UI per addon and cannot host the
|
|
16363
|
+
* discovery→adopt flow.
|
|
16364
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
16365
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
16366
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
16367
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
16368
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
16369
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
16370
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
16371
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
16372
|
+
* base64 fallback needed.
|
|
16373
|
+
*
|
|
16374
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
16375
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
16376
|
+
* admin "Integrations" page.
|
|
16377
|
+
*/
|
|
16378
|
+
/**
|
|
16379
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
16380
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
16381
|
+
*/
|
|
16382
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
16383
|
+
"image",
|
|
16384
|
+
"video",
|
|
16385
|
+
"gif",
|
|
16386
|
+
"audio",
|
|
16387
|
+
"icon"
|
|
16388
|
+
]);
|
|
16389
|
+
/**
|
|
16390
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
16391
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
16392
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
16393
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
16394
|
+
*/
|
|
16395
|
+
var AttachmentSchema = object({
|
|
16396
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
16397
|
+
url: string().optional(),
|
|
16398
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
16399
|
+
mime: string().optional(),
|
|
16400
|
+
name: string().optional()
|
|
16401
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
16402
|
+
var NotificationFormatSchema = _enum([
|
|
16403
|
+
"text",
|
|
16404
|
+
"markdown",
|
|
16405
|
+
"html"
|
|
16406
|
+
]);
|
|
16407
|
+
/** A single tap-through action button. */
|
|
16408
|
+
var NotificationActionSchema = object({
|
|
16409
|
+
id: string(),
|
|
16410
|
+
label: string(),
|
|
16411
|
+
url: string().optional()
|
|
16412
|
+
});
|
|
16413
|
+
/**
|
|
16414
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
16415
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
16416
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
16417
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
16418
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
16419
|
+
* `priority` for that one target.
|
|
16420
|
+
*/
|
|
16421
|
+
var NotificationSchema = object({
|
|
16348
16422
|
body: string(),
|
|
16349
|
-
|
|
16423
|
+
title: string().optional(),
|
|
16424
|
+
format: NotificationFormatSchema.default("text"),
|
|
16425
|
+
priority: number().int().min(1).max(5).default(3),
|
|
16426
|
+
level: string().optional(),
|
|
16427
|
+
attachments: array(AttachmentSchema).optional(),
|
|
16428
|
+
clickUrl: string().optional(),
|
|
16429
|
+
actions: array(NotificationActionSchema).optional(),
|
|
16430
|
+
sound: string().optional(),
|
|
16431
|
+
ttl: number().optional(),
|
|
16432
|
+
tag: string().optional(),
|
|
16350
16433
|
deviceId: number().optional(),
|
|
16351
16434
|
eventId: string().optional(),
|
|
16352
|
-
priority: _enum([
|
|
16353
|
-
"low",
|
|
16354
|
-
"normal",
|
|
16355
|
-
"high",
|
|
16356
|
-
"critical"
|
|
16357
|
-
]).default("normal"),
|
|
16358
16435
|
metadata: record(string(), unknown()).optional()
|
|
16359
|
-
})
|
|
16436
|
+
});
|
|
16437
|
+
/** One declared native severity/priority level for a kind. */
|
|
16438
|
+
var TargetKindLevelSchema = object({
|
|
16439
|
+
id: string(),
|
|
16440
|
+
label: string(),
|
|
16441
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
16442
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
16443
|
+
flags: object({
|
|
16444
|
+
critical: boolean().optional(),
|
|
16445
|
+
silent: boolean().optional(),
|
|
16446
|
+
noPush: boolean().optional()
|
|
16447
|
+
}).optional(),
|
|
16448
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
16449
|
+
requires: array(string()).optional(),
|
|
16450
|
+
description: string().optional()
|
|
16451
|
+
});
|
|
16452
|
+
/** The full capability block consulted before dispatch. */
|
|
16453
|
+
var TargetKindCapsSchema = object({
|
|
16454
|
+
attachments: object({
|
|
16455
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
16456
|
+
mode: _enum([
|
|
16457
|
+
"url",
|
|
16458
|
+
"bytes",
|
|
16459
|
+
"both"
|
|
16460
|
+
]),
|
|
16461
|
+
max: number().int().nonnegative(),
|
|
16462
|
+
maxBytes: number().int().positive().optional()
|
|
16463
|
+
}),
|
|
16464
|
+
/** Max action buttons (0 = none). */
|
|
16465
|
+
actions: number().int().nonnegative(),
|
|
16466
|
+
levels: array(TargetKindLevelSchema),
|
|
16467
|
+
format: array(NotificationFormatSchema),
|
|
16468
|
+
clickUrl: boolean(),
|
|
16469
|
+
sound: boolean(),
|
|
16470
|
+
ttl: boolean(),
|
|
16471
|
+
bodyMaxLen: number().int().positive()
|
|
16472
|
+
});
|
|
16473
|
+
/**
|
|
16474
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
16475
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
16476
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
16477
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
16478
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
16479
|
+
*/
|
|
16480
|
+
var ConfigSchemaPassthrough = unknown();
|
|
16481
|
+
var TargetKindSchema = object({
|
|
16482
|
+
kind: string(),
|
|
16483
|
+
label: string(),
|
|
16484
|
+
icon: string(),
|
|
16485
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16486
|
+
addonId: string(),
|
|
16487
|
+
configSchema: ConfigSchemaPassthrough,
|
|
16488
|
+
supportsDiscovery: boolean(),
|
|
16489
|
+
caps: TargetKindCapsSchema
|
|
16490
|
+
});
|
|
16491
|
+
/**
|
|
16492
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
16493
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
16494
|
+
* round-trip a stored secret to the UI.
|
|
16495
|
+
*/
|
|
16496
|
+
var TargetSchema = object({
|
|
16497
|
+
id: string(),
|
|
16498
|
+
name: string(),
|
|
16499
|
+
kind: string(),
|
|
16500
|
+
addonId: string(),
|
|
16501
|
+
enabled: boolean(),
|
|
16502
|
+
config: record(string(), unknown())
|
|
16503
|
+
});
|
|
16504
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
16505
|
+
var DiscoveredTargetSchema = object({
|
|
16506
|
+
kind: string(),
|
|
16507
|
+
suggestedName: string(),
|
|
16508
|
+
config: record(string(), unknown())
|
|
16509
|
+
});
|
|
16510
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
16511
|
+
var RenderedAsSchema = object({
|
|
16512
|
+
level: string(),
|
|
16513
|
+
format: NotificationFormatSchema,
|
|
16514
|
+
attachmentsSent: number().int().nonnegative(),
|
|
16515
|
+
actionsSent: number().int().nonnegative(),
|
|
16516
|
+
truncated: boolean(),
|
|
16517
|
+
dropped: array(string())
|
|
16518
|
+
});
|
|
16519
|
+
var SendResultSchema = object({
|
|
16360
16520
|
success: boolean(),
|
|
16361
|
-
error: string().optional()
|
|
16362
|
-
|
|
16521
|
+
error: string().optional(),
|
|
16522
|
+
renderedAs: RenderedAsSchema.optional()
|
|
16523
|
+
});
|
|
16524
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
16525
|
+
var TestResultSchema = SendResultSchema;
|
|
16526
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
|
|
16527
|
+
kind: string(),
|
|
16528
|
+
config: record(string(), unknown()).optional()
|
|
16529
|
+
}), array(DiscoveredTargetSchema)), method(object({
|
|
16530
|
+
targetId: string(),
|
|
16531
|
+
notification: NotificationSchema
|
|
16532
|
+
}), SendResultSchema, { kind: "mutation" }), method(object({
|
|
16533
|
+
targetId: string(),
|
|
16534
|
+
sample: NotificationSchema.optional()
|
|
16535
|
+
}), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
|
|
16536
|
+
targetId: string(),
|
|
16537
|
+
enabled: boolean()
|
|
16538
|
+
}), _void(), { kind: "mutation" });
|
|
16363
16539
|
/**
|
|
16364
16540
|
* Zod schemas for persisted record types.
|
|
16365
16541
|
*
|
|
@@ -22726,13 +22902,49 @@ Object.freeze({
|
|
|
22726
22902
|
addonId: null,
|
|
22727
22903
|
access: "create"
|
|
22728
22904
|
},
|
|
22905
|
+
"notificationOutput.deleteTarget": {
|
|
22906
|
+
capName: "notification-output",
|
|
22907
|
+
capScope: "system",
|
|
22908
|
+
addonId: null,
|
|
22909
|
+
access: "delete"
|
|
22910
|
+
},
|
|
22911
|
+
"notificationOutput.discoverTargets": {
|
|
22912
|
+
capName: "notification-output",
|
|
22913
|
+
capScope: "system",
|
|
22914
|
+
addonId: null,
|
|
22915
|
+
access: "view"
|
|
22916
|
+
},
|
|
22917
|
+
"notificationOutput.listTargetKinds": {
|
|
22918
|
+
capName: "notification-output",
|
|
22919
|
+
capScope: "system",
|
|
22920
|
+
addonId: null,
|
|
22921
|
+
access: "view"
|
|
22922
|
+
},
|
|
22923
|
+
"notificationOutput.listTargets": {
|
|
22924
|
+
capName: "notification-output",
|
|
22925
|
+
capScope: "system",
|
|
22926
|
+
addonId: null,
|
|
22927
|
+
access: "view"
|
|
22928
|
+
},
|
|
22729
22929
|
"notificationOutput.send": {
|
|
22730
22930
|
capName: "notification-output",
|
|
22731
22931
|
capScope: "system",
|
|
22732
22932
|
addonId: null,
|
|
22733
22933
|
access: "create"
|
|
22734
22934
|
},
|
|
22735
|
-
"notificationOutput.
|
|
22935
|
+
"notificationOutput.setTargetEnabled": {
|
|
22936
|
+
capName: "notification-output",
|
|
22937
|
+
capScope: "system",
|
|
22938
|
+
addonId: null,
|
|
22939
|
+
access: "create"
|
|
22940
|
+
},
|
|
22941
|
+
"notificationOutput.testTarget": {
|
|
22942
|
+
capName: "notification-output",
|
|
22943
|
+
capScope: "system",
|
|
22944
|
+
addonId: null,
|
|
22945
|
+
access: "create"
|
|
22946
|
+
},
|
|
22947
|
+
"notificationOutput.upsertTarget": {
|
|
22736
22948
|
capName: "notification-output",
|
|
22737
22949
|
capScope: "system",
|
|
22738
22950
|
addonId: null,
|
|
@@ -215216,7 +215428,7 @@ function isNativeObjectForwardingEnabled(capState) {
|
|
|
215216
215428
|
}
|
|
215217
215429
|
//#endregion
|
|
215218
215430
|
//#region src/stream-routing.ts
|
|
215219
|
-
var KIND_RE = /^(native|rtsp|rtmp):(.+)$/;
|
|
215431
|
+
var KIND_RE = /^(native|rtsp|rtmp|flv):(.+)$/;
|
|
215220
215432
|
var CH_PROFILE_RE = /^ch(\d+)-(main|sub|ext)$/;
|
|
215221
215433
|
var PROFILE_ONLY_RE = /^(main|sub|ext)$/;
|
|
215222
215434
|
/**
|
|
@@ -215266,6 +215478,7 @@ function kindLabel(kind) {
|
|
|
215266
215478
|
case "native": return "Native";
|
|
215267
215479
|
case "rtsp": return "RTSP";
|
|
215268
215480
|
case "rtmp": return "RTMP";
|
|
215481
|
+
case "flv": return "FLV";
|
|
215269
215482
|
}
|
|
215270
215483
|
}
|
|
215271
215484
|
/**
|
|
@@ -219152,6 +219365,23 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
219152
219365
|
}
|
|
219153
219366
|
}
|
|
219154
219367
|
/**
|
|
219368
|
+
* Synthesize the HTTP-FLV pull URL for a (channel, profile) pair. FLV is
|
|
219369
|
+
* served off the Reolink Baichuan media port (default 1935, app `bcs`) as
|
|
219370
|
+
* `channel<channel>_<profile>.bcs` — the same shape Frigate/go2rtc use. The
|
|
219371
|
+
* lib does NOT surface this URL, so we build it from the SAME connection
|
|
219372
|
+
* creds the RTSP/native paths use (`host`/`username`/`password` on the
|
|
219373
|
+
* device config), URL-encoding the credentials.
|
|
219374
|
+
*
|
|
219375
|
+
* Returns `null` when the host isn't locally resolvable (hub-child cameras
|
|
219376
|
+
* borrow the parent's socket and hold no own `host`), so the caller skips
|
|
219377
|
+
* FLV publication for those devices rather than emitting a broken URL.
|
|
219378
|
+
*/
|
|
219379
|
+
buildFlvUrl(channel, profile) {
|
|
219380
|
+
const host = this.config.get("host");
|
|
219381
|
+
if (typeof host !== "string" || host.length === 0) return null;
|
|
219382
|
+
return `http://${host}/flv?port=1935&app=bcs&stream=channel${channel}_${profile}.bcs&user=${encodeURIComponent(String(this.config.get("username") ?? ""))}&password=${encodeURIComponent(String(this.config.get("password") ?? ""))}`;
|
|
219383
|
+
}
|
|
219384
|
+
/**
|
|
219155
219385
|
* Heavy path: talk to the camera and assemble the descriptors. Only ever
|
|
219156
219386
|
* entered when there is no cached catalog (see `buildStreamCatalog`).
|
|
219157
219387
|
*
|
|
@@ -219237,6 +219467,27 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
219237
219467
|
};
|
|
219238
219468
|
enqueueAlt("rtsp", streamOptions.rtspStreams);
|
|
219239
219469
|
enqueueAlt("rtmp", streamOptions.rtmpStreams);
|
|
219470
|
+
for (const s of streamOptions.nativeStreams) {
|
|
219471
|
+
const channel = s.channel ?? (isChild ? ownChannel : 0);
|
|
219472
|
+
const id = buildCamStreamId("flv", channel, s.profile, channelCount);
|
|
219473
|
+
if (desired.has(id)) continue;
|
|
219474
|
+
const flvUrl = this.buildFlvUrl(channel, s.profile);
|
|
219475
|
+
if (!flvUrl) break;
|
|
219476
|
+
const m = s.metadata;
|
|
219477
|
+
desired.set(id, {
|
|
219478
|
+
camStreamId: id,
|
|
219479
|
+
kind: "pull-flv",
|
|
219480
|
+
label: streamLabel(s, "flv"),
|
|
219481
|
+
url: flvUrl,
|
|
219482
|
+
autoEligible: false,
|
|
219483
|
+
...m && m.width > 0 && m.height > 0 ? { resolution: {
|
|
219484
|
+
width: m.width,
|
|
219485
|
+
height: m.height
|
|
219486
|
+
} } : {},
|
|
219487
|
+
...m && m.frameRate > 0 ? { fps: m.frameRate } : {},
|
|
219488
|
+
...normalizeCodecName(m?.videoEncType) ? { codec: normalizeCodecName(m?.videoEncType) } : {}
|
|
219489
|
+
});
|
|
219490
|
+
}
|
|
219240
219491
|
if (desired.size === 0) {
|
|
219241
219492
|
this.ctx.logger.warn("buildStreamCatalog: camera reports no streams — empty catalog", { tags: { deviceId: this.id } });
|
|
219242
219493
|
return [];
|
|
@@ -222957,6 +223208,47 @@ function buildCreationFormSchema() {
|
|
|
222957
223208
|
] };
|
|
222958
223209
|
}
|
|
222959
223210
|
//#endregion
|
|
223211
|
+
//#region src/reolink-discovery-map.ts
|
|
223212
|
+
/**
|
|
223213
|
+
* Flatten a host (IP / hostname) into a flat row-key slug — shared by `generateStableId` and the
|
|
223214
|
+
* discovery candidate mapping so a host-keyed camera is detected as already onboarded on re-scan.
|
|
223215
|
+
*/
|
|
223216
|
+
function slugifyReolinkHost(host) {
|
|
223217
|
+
return host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
223218
|
+
}
|
|
223219
|
+
/**
|
|
223220
|
+
* Map discovered Reolink hosts to adoption {@link DiscoveryCandidate}s, de-duplicated by host (the
|
|
223221
|
+
* same camera can answer on more than one discovery method — UDP broadcast, ONVIF, HTTP scan). The
|
|
223222
|
+
* first responder for a host wins. Pure: no I/O.
|
|
223223
|
+
*
|
|
223224
|
+
* The authoritative stableId is `mac-<mac>` (learned during autodetect at adopt time), which discovery
|
|
223225
|
+
* can't produce — so a re-scan of a MAC-keyed camera may still show as addable. The `host-` key still
|
|
223226
|
+
* lets host-added cameras be detected as onboarded on re-scan.
|
|
223227
|
+
*/
|
|
223228
|
+
function mapReolinkDiscoveryToCandidates(devices, credentials) {
|
|
223229
|
+
const username = credentials.username?.trim() ?? "";
|
|
223230
|
+
const password = credentials.password ?? "";
|
|
223231
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
223232
|
+
for (const d of devices) if (!byHost.has(d.host)) byHost.set(d.host, d);
|
|
223233
|
+
return [...byHost.values()].map((d) => {
|
|
223234
|
+
const displayName = d.name ?? d.model ?? d.host;
|
|
223235
|
+
return {
|
|
223236
|
+
stableId: `host-${slugifyReolinkHost(d.host)}`,
|
|
223237
|
+
type: DeviceType.Camera,
|
|
223238
|
+
suggestedName: displayName,
|
|
223239
|
+
prefilledConfig: {
|
|
223240
|
+
name: displayName,
|
|
223241
|
+
host: d.host,
|
|
223242
|
+
transport: "auto",
|
|
223243
|
+
...d.httpPort !== void 0 ? { port: d.httpPort } : {},
|
|
223244
|
+
...d.uid ? { uid: d.uid } : {},
|
|
223245
|
+
...username ? { username } : {},
|
|
223246
|
+
...password ? { password } : {}
|
|
223247
|
+
}
|
|
223248
|
+
};
|
|
223249
|
+
});
|
|
223250
|
+
}
|
|
223251
|
+
//#endregion
|
|
222960
223252
|
//#region src/autodetect-cache.ts
|
|
222961
223253
|
var DEFAULT_TTL_MS = 6e4;
|
|
222962
223254
|
var AutodetectCache = class {
|
|
@@ -223469,11 +223761,6 @@ function isMeaningfulIdentifier(s) {
|
|
|
223469
223761
|
if (!s || s.length < 6) return false;
|
|
223470
223762
|
return new Set(s.toLowerCase().split("").filter((c) => c !== "0" && c !== "f")).size >= 1;
|
|
223471
223763
|
}
|
|
223472
|
-
/** Flatten a host (IP / hostname) into a flat row-key slug — shared by `generateStableId` and the
|
|
223473
|
-
* discovery candidate mapping so a host-keyed camera is detected as already onboarded on re-scan. */
|
|
223474
|
-
function slugifyReolinkHost(host) {
|
|
223475
|
-
return host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
223476
|
-
}
|
|
223477
223764
|
/**
|
|
223478
223765
|
* Patch `detection.deviceInfo` + `hostNetworkInfo` in-place with the
|
|
223479
223766
|
* post-login HOST identifiers. Two distinct gaps to fill:
|
|
@@ -223768,24 +224055,9 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
|
|
|
223768
224055
|
networkCidr: networkCidr || "local",
|
|
223769
224056
|
enableOnvif
|
|
223770
224057
|
} });
|
|
223771
|
-
|
|
223772
|
-
|
|
223773
|
-
|
|
223774
|
-
const displayName = d.name ?? d.model ?? d.host;
|
|
223775
|
-
return {
|
|
223776
|
-
stableId: `host-${slugifyReolinkHost(d.host)}`,
|
|
223777
|
-
type: DeviceType.Camera,
|
|
223778
|
-
suggestedName: displayName,
|
|
223779
|
-
prefilledConfig: {
|
|
223780
|
-
name: displayName,
|
|
223781
|
-
host: d.host,
|
|
223782
|
-
transport: "auto",
|
|
223783
|
-
...d.httpPort !== void 0 ? { port: d.httpPort } : {},
|
|
223784
|
-
...d.uid ? { uid: d.uid } : {},
|
|
223785
|
-
...username ? { username } : {},
|
|
223786
|
-
...password ? { password } : {}
|
|
223787
|
-
}
|
|
223788
|
-
};
|
|
224058
|
+
return mapReolinkDiscoveryToCandidates(devices, {
|
|
224059
|
+
username,
|
|
224060
|
+
password
|
|
223789
224061
|
});
|
|
223790
224062
|
}
|
|
223791
224063
|
async adoptDiscoveredDevice(input) {
|