@camstack/addon-provider-reolink 1.2.215 → 1.2.216

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.216] - 2026-10-31
11
+
12
+ ### Changed
13
+
14
+ - The NVR / Home Hub can be added as a Gateway device (reboot, web interface link) next to its channel cameras.
15
+
10
16
  ## [1.2.215] - 2026-10-31
11
17
 
12
18
  ### Changed
package/dist/addon.js CHANGED
@@ -203955,6 +203955,14 @@ var ReolinkHubSession = class {
203955
203955
  get isOnline() {
203956
203956
  return this.#online;
203957
203957
  }
203958
+ /** The integration's display name. */
203959
+ get name() {
203960
+ return this.#deps.name;
203961
+ }
203962
+ /** Restart the hub itself (not a camera behind it). */
203963
+ async reboot() {
203964
+ await (await this.getApi()).reboot();
203965
+ }
203958
203966
  /** The last channel list, for the adoption surface. */
203959
203967
  channelSnapshot() {
203960
203968
  return this.#snapshot;
@@ -204413,6 +204421,28 @@ function toDiscoveredChild(channel, adoptedDeviceId) {
204413
204421
  adoptedDeviceId
204414
204422
  };
204415
204423
  }
204424
+ var GATEWAY_SUFFIX = ":gateway";
204425
+ /** The `childNativeId` of the hub box itself, one per integration. */
204426
+ function hubGatewayNativeId(integrationId) {
204427
+ return `${integrationId}${GATEWAY_SUFFIX}`;
204428
+ }
204429
+ /**
204430
+ * The hub (NVR / Home Hub) as an adoption candidate: a {@link DeviceType.Gateway} that is a
204431
+ * sibling of its channel cameras, never their parent. Only what the library really exposes:
204432
+ * `reboot` (Baichuan `Reboot`) and the unit's web page.
204433
+ */
204434
+ function toDiscoveredGateway(input) {
204435
+ return {
204436
+ childNativeId: hubGatewayNativeId(input.integrationId),
204437
+ name: input.name,
204438
+ type: DeviceType.Gateway,
204439
+ status: input.online ? "online" : "offline",
204440
+ metadata: { manufacturer: "Reolink" },
204441
+ alreadyAdopted: input.adoptedDeviceId !== null,
204442
+ adoptedDeviceId: input.adoptedDeviceId,
204443
+ capabilities: [rebootCapability.name, deviceAdminLinkCapability.name]
204444
+ };
204445
+ }
204416
204446
  /**
204417
204447
  * The per-integration registry the cameras reach their session through (the shape of
204418
204448
  * `veluxGateways` / `enpalConnections`). It also holds the children, so a child that activates
@@ -217687,7 +217717,13 @@ function buildReolinkAdoptionProvider(deps) {
217687
217717
  const session = getSession(integrationId);
217688
217718
  if (session === null) return [];
217689
217719
  const adopted = await listAdopted();
217690
- return session.channelSnapshot().channels.map((channel) => toDiscoveredChild(channel, findAdoptedForChannel(adopted, integrationId, channel)));
217720
+ const gateways = await deps.listAdoptedGateways();
217721
+ return [toDiscoveredGateway({
217722
+ integrationId,
217723
+ name: session.name,
217724
+ online: session.isOnline,
217725
+ adoptedDeviceId: gateways.find((g) => g.integrationId === integrationId)?.id ?? null
217726
+ }), ...session.channelSnapshot().channels.map((channel) => toDiscoveredChild(channel, findAdoptedForChannel(adopted, integrationId, channel)))];
217691
217727
  }
217692
217728
  async function status(integrationId) {
217693
217729
  const all = await candidates(integrationId);
@@ -217748,6 +217784,19 @@ function buildReolinkAdoptionProvider(deps) {
217748
217784
  const adopted = [];
217749
217785
  let failures = 0;
217750
217786
  for (const childNativeId of childNativeIds) try {
217787
+ if (childNativeId === hubGatewayNativeId(integrationId)) {
217788
+ const known = (await deps.listAdoptedGateways()).find((g) => g.integrationId === integrationId);
217789
+ const { deviceId } = known !== void 0 ? { deviceId: known.id } : await deps.adoptGateway({
217790
+ integrationId,
217791
+ name: perCandidate?.[childNativeId]?.name ?? session.name
217792
+ });
217793
+ adopted.push({
217794
+ childNativeId,
217795
+ parentDeviceId: deviceId,
217796
+ accessoryDeviceIds: []
217797
+ });
217798
+ continue;
217799
+ }
217751
217800
  const channel = session.channelSnapshot().channels.find((c) => c.childNativeId === childNativeId);
217752
217801
  if (channel === void 0) {
217753
217802
  logger.warn("reolink adopt: the hub lists no such channel — skipping", { meta: {
@@ -217794,7 +217843,7 @@ function buildReolinkAdoptionProvider(deps) {
217794
217843
  },
217795
217844
  resync: async ({ camDeviceId }) => {
217796
217845
  const cfg = await deps.findDeviceConfig(camDeviceId);
217797
- if (cfg === null || typeof cfg["integrationId"] !== "string") throw new Error(`reolink resync: device ${camDeviceId} is not an NVR-adopted camera`);
217846
+ if (cfg === null || typeof cfg["integrationId"] !== "string") throw new Error(`reolink resync: device ${camDeviceId} is not an NVR-adopted device`);
217798
217847
  return {
217799
217848
  changed: false,
217800
217849
  rebuiltChildren: 0
@@ -218032,6 +218081,86 @@ async function retireMigratedHubs(retirements, ports) {
218032
218081
  return retired;
218033
218082
  }
218034
218083
  //#endregion
218084
+ //#region src/reolink-gateway-device.ts
218085
+ var reolinkGatewayDeviceSchema = object({
218086
+ integrationId: string().min(1).describe("Integration (hub session) the device belongs to"),
218087
+ /** The adoption candidate id (`<integration>:gateway`). */
218088
+ nativeId: string().min(1)
218089
+ });
218090
+ /**
218091
+ * The physical NVR / Home Hub, as a {@link DeviceType.Gateway}. Its connection and password
218092
+ * live on the INTEGRATION; this device is the box's presence in the device list, and carries
218093
+ * the actions the library really supports through existing caps:
218094
+ *
218095
+ * - `reboot` (Baichuan `Reboot`, no channel), advertised as {@link DeviceFeature.Rebootable};
218096
+ * - `device-admin-link`, the unit's own web page.
218097
+ *
218098
+ * Cameras are NOT children of it: they are adopted as their own top-level devices of the
218099
+ * same integration. The addon flips `online` when the hub socket changes.
218100
+ */
218101
+ var ReolinkGatewayDevice = class extends BaseDevice {
218102
+ features = [DeviceFeature.Rebootable];
218103
+ integrationId;
218104
+ constructor(ctx) {
218105
+ const cfg = reolinkGatewayDeviceSchema.parse(ctx.persistedConfig ?? {});
218106
+ super(ctx, reolinkGatewayDeviceSchema, { type: DeviceType.Gateway });
218107
+ this.integrationId = cfg.integrationId;
218108
+ this.online = reolinkHubSessions.get(this.integrationId)?.isOnline === true;
218109
+ this.registerRebootCap();
218110
+ this.registerAdminLinkCap();
218111
+ this.updateSourceInfo({
218112
+ id: cfg.nativeId,
218113
+ system: "reolink"
218114
+ });
218115
+ }
218116
+ registerRebootCap() {
218117
+ this.ctx.registerNativeCap(rebootCapability, { reboot: async ({ deviceId }) => {
218118
+ if (deviceId !== this.id) throw new Error(`ReolinkGatewayDevice: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
218119
+ const session = reolinkHubSessions.get(this.integrationId);
218120
+ if (session === null) {
218121
+ this.ctx.logger.warn("Reolink hub reboot refused: no live session", { tags: { deviceId: this.id } });
218122
+ throw new Error("The hub session is down, so it cannot be told to reboot.");
218123
+ }
218124
+ await session.reboot();
218125
+ this.ctx.logger.info("Reolink hub reboot requested", { tags: { deviceId: this.id } });
218126
+ return { success: true };
218127
+ } });
218128
+ }
218129
+ /**
218130
+ * The hub's web interface, on the hub host. The password is NOT placed in the URL; the page
218131
+ * asks for it.
218132
+ */
218133
+ registerAdminLinkCap() {
218134
+ this.ctx.registerNativeCap(deviceAdminLinkCapability, { getAdminLink: async ({ deviceId }) => {
218135
+ if (deviceId !== this.id) throw new Error(`ReolinkGatewayDevice: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
218136
+ const host = reolinkHubSessions.get(this.integrationId)?.relayEndpoint().host;
218137
+ if (host === void 0) {
218138
+ this.ctx.logger.debug("reolink admin link unavailable: integration not running", { tags: { deviceId: this.id } });
218139
+ return null;
218140
+ }
218141
+ const built = buildDeviceAdminUrl({
218142
+ host,
218143
+ port: null,
218144
+ scheme: "https",
218145
+ path: "/"
218146
+ });
218147
+ if (!built.ok) {
218148
+ this.ctx.logger.debug("reolink admin link unavailable", {
218149
+ tags: { deviceId: this.id },
218150
+ meta: { reason: built.reason }
218151
+ });
218152
+ return null;
218153
+ }
218154
+ return {
218155
+ url: built.url,
218156
+ label: "Reolink web interface",
218157
+ target: "integration",
218158
+ host
218159
+ };
218160
+ } });
218161
+ }
218162
+ };
218163
+ //#endregion
218035
218164
  //#region src/addon.ts
218036
218165
  /**
218037
218166
  * Adapt a camstack scoped logger to the lib's `Logger` shape (just
@@ -218195,7 +218324,10 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218195
218324
  secretStatePaths = ["emailPushAuthPassword"];
218196
218325
  addonId = REOLINK_ADDON_ID;
218197
218326
  providerName = "Reolink";
218198
- deviceClasses = { [DeviceType.Camera]: ReolinkCamera };
218327
+ deviceClasses = {
218328
+ [DeviceType.Camera]: ReolinkCamera,
218329
+ [DeviceType.Gateway]: ReolinkGatewayDevice
218330
+ };
218199
218331
  /**
218200
218332
  * Short-lived cache that bridges `testCreationField` and
218201
218333
  * `onCreateDevice` — Slice 15 socket reuse. Both code paths key off
@@ -218410,6 +218542,15 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218410
218542
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
218411
218543
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
218412
218544
  }
218545
+ /** The hub box follows its integration's socket. */
218546
+ markGatewaysOnline(integrationId, online) {
218547
+ const reg = this.ctx.kernel.deviceRegistry;
218548
+ if (!reg) return;
218549
+ for (const d of reg.getAllForAddon(this.addonId)) {
218550
+ if (!(d instanceof ReolinkGatewayDevice) || d.integrationId !== integrationId) continue;
218551
+ if (d.online !== online) d.markOnline(online);
218552
+ }
218553
+ }
218413
218554
  buildAdoptionProvider() {
218414
218555
  return buildReolinkAdoptionProvider({
218415
218556
  getSession: (integrationId) => this.hubManager?.get(integrationId) ?? null,
@@ -218455,6 +218596,29 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218455
218596
  accessoryDeviceIds: children.map((c) => c.id)
218456
218597
  };
218457
218598
  },
218599
+ listAdoptedGateways: async () => {
218600
+ const reg = this.ctx.kernel.deviceRegistry;
218601
+ if (!reg) return [];
218602
+ const out = [];
218603
+ for (const d of reg.getAllForAddon(this.addonId)) if (d instanceof ReolinkGatewayDevice) out.push({
218604
+ id: d.id,
218605
+ integrationId: d.integrationId
218606
+ });
218607
+ return out;
218608
+ },
218609
+ adoptGateway: async ({ integrationId, name }) => {
218610
+ const devices = this.ctx.kernel.devices;
218611
+ if (!devices) throw new Error("reolink adopt: kernel.devices unavailable");
218612
+ const nativeId = hubGatewayNativeId(integrationId);
218613
+ return { deviceId: (await devices.create(`${integrationId}-${nativeId}`, ReolinkGatewayDevice, {
218614
+ integrationId,
218615
+ nativeId
218616
+ }, null, {
218617
+ type: DeviceType.Gateway,
218618
+ name,
218619
+ integrationId
218620
+ })).id };
218621
+ },
218458
218622
  removeDevice: async (id) => {
218459
218623
  await this.ctx.kernel.devices?.remove(id);
218460
218624
  },
@@ -218660,7 +218824,10 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218660
218824
  this.listReolinkCameras();
218661
218825
  this.hubManager = createReolinkHubManager({
218662
218826
  logger: this.ctx.logger,
218663
- onConnectionChange: (integrationId, online) => this.ctx.logger.info(`Reolink NVR socket ${online ? "up" : "down"}`, { meta: { integrationId } })
218827
+ onConnectionChange: (integrationId, online) => {
218828
+ this.ctx.logger.info(`Reolink NVR socket ${online ? "up" : "down"}`, { meta: { integrationId } });
218829
+ this.markGatewaysOnline(integrationId, online);
218830
+ }
218664
218831
  }, reolinkHubSessions);
218665
218832
  await this.reconcileIntegrations();
218666
218833
  this.subscribeIntegrationLifecycle();
package/dist/addon.mjs CHANGED
@@ -203947,6 +203947,14 @@ var ReolinkHubSession = class {
203947
203947
  get isOnline() {
203948
203948
  return this.#online;
203949
203949
  }
203950
+ /** The integration's display name. */
203951
+ get name() {
203952
+ return this.#deps.name;
203953
+ }
203954
+ /** Restart the hub itself (not a camera behind it). */
203955
+ async reboot() {
203956
+ await (await this.getApi()).reboot();
203957
+ }
203950
203958
  /** The last channel list, for the adoption surface. */
203951
203959
  channelSnapshot() {
203952
203960
  return this.#snapshot;
@@ -204405,6 +204413,28 @@ function toDiscoveredChild(channel, adoptedDeviceId) {
204405
204413
  adoptedDeviceId
204406
204414
  };
204407
204415
  }
204416
+ var GATEWAY_SUFFIX = ":gateway";
204417
+ /** The `childNativeId` of the hub box itself, one per integration. */
204418
+ function hubGatewayNativeId(integrationId) {
204419
+ return `${integrationId}${GATEWAY_SUFFIX}`;
204420
+ }
204421
+ /**
204422
+ * The hub (NVR / Home Hub) as an adoption candidate: a {@link DeviceType.Gateway} that is a
204423
+ * sibling of its channel cameras, never their parent. Only what the library really exposes:
204424
+ * `reboot` (Baichuan `Reboot`) and the unit's web page.
204425
+ */
204426
+ function toDiscoveredGateway(input) {
204427
+ return {
204428
+ childNativeId: hubGatewayNativeId(input.integrationId),
204429
+ name: input.name,
204430
+ type: DeviceType.Gateway,
204431
+ status: input.online ? "online" : "offline",
204432
+ metadata: { manufacturer: "Reolink" },
204433
+ alreadyAdopted: input.adoptedDeviceId !== null,
204434
+ adoptedDeviceId: input.adoptedDeviceId,
204435
+ capabilities: [rebootCapability.name, deviceAdminLinkCapability.name]
204436
+ };
204437
+ }
204408
204438
  /**
204409
204439
  * The per-integration registry the cameras reach their session through (the shape of
204410
204440
  * `veluxGateways` / `enpalConnections`). It also holds the children, so a child that activates
@@ -217679,7 +217709,13 @@ function buildReolinkAdoptionProvider(deps) {
217679
217709
  const session = getSession(integrationId);
217680
217710
  if (session === null) return [];
217681
217711
  const adopted = await listAdopted();
217682
- return session.channelSnapshot().channels.map((channel) => toDiscoveredChild(channel, findAdoptedForChannel(adopted, integrationId, channel)));
217712
+ const gateways = await deps.listAdoptedGateways();
217713
+ return [toDiscoveredGateway({
217714
+ integrationId,
217715
+ name: session.name,
217716
+ online: session.isOnline,
217717
+ adoptedDeviceId: gateways.find((g) => g.integrationId === integrationId)?.id ?? null
217718
+ }), ...session.channelSnapshot().channels.map((channel) => toDiscoveredChild(channel, findAdoptedForChannel(adopted, integrationId, channel)))];
217683
217719
  }
217684
217720
  async function status(integrationId) {
217685
217721
  const all = await candidates(integrationId);
@@ -217740,6 +217776,19 @@ function buildReolinkAdoptionProvider(deps) {
217740
217776
  const adopted = [];
217741
217777
  let failures = 0;
217742
217778
  for (const childNativeId of childNativeIds) try {
217779
+ if (childNativeId === hubGatewayNativeId(integrationId)) {
217780
+ const known = (await deps.listAdoptedGateways()).find((g) => g.integrationId === integrationId);
217781
+ const { deviceId } = known !== void 0 ? { deviceId: known.id } : await deps.adoptGateway({
217782
+ integrationId,
217783
+ name: perCandidate?.[childNativeId]?.name ?? session.name
217784
+ });
217785
+ adopted.push({
217786
+ childNativeId,
217787
+ parentDeviceId: deviceId,
217788
+ accessoryDeviceIds: []
217789
+ });
217790
+ continue;
217791
+ }
217743
217792
  const channel = session.channelSnapshot().channels.find((c) => c.childNativeId === childNativeId);
217744
217793
  if (channel === void 0) {
217745
217794
  logger.warn("reolink adopt: the hub lists no such channel — skipping", { meta: {
@@ -217786,7 +217835,7 @@ function buildReolinkAdoptionProvider(deps) {
217786
217835
  },
217787
217836
  resync: async ({ camDeviceId }) => {
217788
217837
  const cfg = await deps.findDeviceConfig(camDeviceId);
217789
- if (cfg === null || typeof cfg["integrationId"] !== "string") throw new Error(`reolink resync: device ${camDeviceId} is not an NVR-adopted camera`);
217838
+ if (cfg === null || typeof cfg["integrationId"] !== "string") throw new Error(`reolink resync: device ${camDeviceId} is not an NVR-adopted device`);
217790
217839
  return {
217791
217840
  changed: false,
217792
217841
  rebuiltChildren: 0
@@ -218024,6 +218073,86 @@ async function retireMigratedHubs(retirements, ports) {
218024
218073
  return retired;
218025
218074
  }
218026
218075
  //#endregion
218076
+ //#region src/reolink-gateway-device.ts
218077
+ var reolinkGatewayDeviceSchema = object({
218078
+ integrationId: string().min(1).describe("Integration (hub session) the device belongs to"),
218079
+ /** The adoption candidate id (`<integration>:gateway`). */
218080
+ nativeId: string().min(1)
218081
+ });
218082
+ /**
218083
+ * The physical NVR / Home Hub, as a {@link DeviceType.Gateway}. Its connection and password
218084
+ * live on the INTEGRATION; this device is the box's presence in the device list, and carries
218085
+ * the actions the library really supports through existing caps:
218086
+ *
218087
+ * - `reboot` (Baichuan `Reboot`, no channel), advertised as {@link DeviceFeature.Rebootable};
218088
+ * - `device-admin-link`, the unit's own web page.
218089
+ *
218090
+ * Cameras are NOT children of it: they are adopted as their own top-level devices of the
218091
+ * same integration. The addon flips `online` when the hub socket changes.
218092
+ */
218093
+ var ReolinkGatewayDevice = class extends BaseDevice {
218094
+ features = [DeviceFeature.Rebootable];
218095
+ integrationId;
218096
+ constructor(ctx) {
218097
+ const cfg = reolinkGatewayDeviceSchema.parse(ctx.persistedConfig ?? {});
218098
+ super(ctx, reolinkGatewayDeviceSchema, { type: DeviceType.Gateway });
218099
+ this.integrationId = cfg.integrationId;
218100
+ this.online = reolinkHubSessions.get(this.integrationId)?.isOnline === true;
218101
+ this.registerRebootCap();
218102
+ this.registerAdminLinkCap();
218103
+ this.updateSourceInfo({
218104
+ id: cfg.nativeId,
218105
+ system: "reolink"
218106
+ });
218107
+ }
218108
+ registerRebootCap() {
218109
+ this.ctx.registerNativeCap(rebootCapability, { reboot: async ({ deviceId }) => {
218110
+ if (deviceId !== this.id) throw new Error(`ReolinkGatewayDevice: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
218111
+ const session = reolinkHubSessions.get(this.integrationId);
218112
+ if (session === null) {
218113
+ this.ctx.logger.warn("Reolink hub reboot refused: no live session", { tags: { deviceId: this.id } });
218114
+ throw new Error("The hub session is down, so it cannot be told to reboot.");
218115
+ }
218116
+ await session.reboot();
218117
+ this.ctx.logger.info("Reolink hub reboot requested", { tags: { deviceId: this.id } });
218118
+ return { success: true };
218119
+ } });
218120
+ }
218121
+ /**
218122
+ * The hub's web interface, on the hub host. The password is NOT placed in the URL; the page
218123
+ * asks for it.
218124
+ */
218125
+ registerAdminLinkCap() {
218126
+ this.ctx.registerNativeCap(deviceAdminLinkCapability, { getAdminLink: async ({ deviceId }) => {
218127
+ if (deviceId !== this.id) throw new Error(`ReolinkGatewayDevice: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
218128
+ const host = reolinkHubSessions.get(this.integrationId)?.relayEndpoint().host;
218129
+ if (host === void 0) {
218130
+ this.ctx.logger.debug("reolink admin link unavailable: integration not running", { tags: { deviceId: this.id } });
218131
+ return null;
218132
+ }
218133
+ const built = buildDeviceAdminUrl({
218134
+ host,
218135
+ port: null,
218136
+ scheme: "https",
218137
+ path: "/"
218138
+ });
218139
+ if (!built.ok) {
218140
+ this.ctx.logger.debug("reolink admin link unavailable", {
218141
+ tags: { deviceId: this.id },
218142
+ meta: { reason: built.reason }
218143
+ });
218144
+ return null;
218145
+ }
218146
+ return {
218147
+ url: built.url,
218148
+ label: "Reolink web interface",
218149
+ target: "integration",
218150
+ host
218151
+ };
218152
+ } });
218153
+ }
218154
+ };
218155
+ //#endregion
218027
218156
  //#region src/addon.ts
218028
218157
  /**
218029
218158
  * Adapt a camstack scoped logger to the lib's `Logger` shape (just
@@ -218187,7 +218316,10 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218187
218316
  secretStatePaths = ["emailPushAuthPassword"];
218188
218317
  addonId = REOLINK_ADDON_ID;
218189
218318
  providerName = "Reolink";
218190
- deviceClasses = { [DeviceType.Camera]: ReolinkCamera };
218319
+ deviceClasses = {
218320
+ [DeviceType.Camera]: ReolinkCamera,
218321
+ [DeviceType.Gateway]: ReolinkGatewayDevice
218322
+ };
218191
218323
  /**
218192
218324
  * Short-lived cache that bridges `testCreationField` and
218193
218325
  * `onCreateDevice` — Slice 15 socket reuse. Both code paths key off
@@ -218402,6 +218534,15 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218402
218534
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
218403
218535
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
218404
218536
  }
218537
+ /** The hub box follows its integration's socket. */
218538
+ markGatewaysOnline(integrationId, online) {
218539
+ const reg = this.ctx.kernel.deviceRegistry;
218540
+ if (!reg) return;
218541
+ for (const d of reg.getAllForAddon(this.addonId)) {
218542
+ if (!(d instanceof ReolinkGatewayDevice) || d.integrationId !== integrationId) continue;
218543
+ if (d.online !== online) d.markOnline(online);
218544
+ }
218545
+ }
218405
218546
  buildAdoptionProvider() {
218406
218547
  return buildReolinkAdoptionProvider({
218407
218548
  getSession: (integrationId) => this.hubManager?.get(integrationId) ?? null,
@@ -218447,6 +218588,29 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218447
218588
  accessoryDeviceIds: children.map((c) => c.id)
218448
218589
  };
218449
218590
  },
218591
+ listAdoptedGateways: async () => {
218592
+ const reg = this.ctx.kernel.deviceRegistry;
218593
+ if (!reg) return [];
218594
+ const out = [];
218595
+ for (const d of reg.getAllForAddon(this.addonId)) if (d instanceof ReolinkGatewayDevice) out.push({
218596
+ id: d.id,
218597
+ integrationId: d.integrationId
218598
+ });
218599
+ return out;
218600
+ },
218601
+ adoptGateway: async ({ integrationId, name }) => {
218602
+ const devices = this.ctx.kernel.devices;
218603
+ if (!devices) throw new Error("reolink adopt: kernel.devices unavailable");
218604
+ const nativeId = hubGatewayNativeId(integrationId);
218605
+ return { deviceId: (await devices.create(`${integrationId}-${nativeId}`, ReolinkGatewayDevice, {
218606
+ integrationId,
218607
+ nativeId
218608
+ }, null, {
218609
+ type: DeviceType.Gateway,
218610
+ name,
218611
+ integrationId
218612
+ })).id };
218613
+ },
218450
218614
  removeDevice: async (id) => {
218451
218615
  await this.ctx.kernel.devices?.remove(id);
218452
218616
  },
@@ -218652,7 +218816,10 @@ var ReolinkProviderAddon = class extends BaseDeviceProvider {
218652
218816
  this.listReolinkCameras();
218653
218817
  this.hubManager = createReolinkHubManager({
218654
218818
  logger: this.ctx.logger,
218655
- onConnectionChange: (integrationId, online) => this.ctx.logger.info(`Reolink NVR socket ${online ? "up" : "down"}`, { meta: { integrationId } })
218819
+ onConnectionChange: (integrationId, online) => {
218820
+ this.ctx.logger.info(`Reolink NVR socket ${online ? "up" : "down"}`, { meta: { integrationId } });
218821
+ this.markGatewaysOnline(integrationId, online);
218822
+ }
218656
218823
  }, reolinkHubSessions);
218657
218824
  await this.reconcileIntegrations();
218658
218825
  this.subscribeIntegrationLifecycle();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.215",
3
+ "version": "1.2.216",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",