@camstack/system 1.2.115 → 1.2.117
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/ipv6-lan.d.ts +26 -0
- package/dist/builtins/local-network/local-hostname.d.ts +15 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +59 -21
- package/dist/builtins/local-network/local-network.addon.js +369 -50
- package/dist/builtins/local-network/local-network.addon.mjs +365 -51
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-Drb54SZD.mjs → dist-D4Vmj8iw.mjs} +151 -9
- package/dist/{dist-iAwSA2_f.js → dist-DMEGlqg6.js} +151 -9
- package/dist/http/lan-http-bind.d.ts +30 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +30 -472
- package/dist/index.mjs +11 -468
- package/dist/lan-http-bind-DmgpFP6_.js +890 -0
- package/dist/lan-http-bind-jKrj6OjQ.mjs +752 -0
- package/dist/{retired-settings-keys-Dz5-7gjt.mjs → retired-settings-keys--aM_vGF9.mjs} +1 -1
- package/dist/{retired-settings-keys-OfhqQio4.js → retired-settings-keys-DET4YkAU.js} +1 -1
- package/dist/tls/cert-manager.d.ts +11 -0
- package/dist/tls/index.d.ts +3 -1
- package/dist/tls/tls-status.d.ts +16 -0
- package/dist/tls/uploaded-cert.d.ts +14 -0
- package/package.json +1 -1
|
@@ -1,5 +1,149 @@
|
|
|
1
|
-
import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, Y as localNetworkCapability, yt as BaseAddon } from "../../dist-D4Vmj8iw.mjs";
|
|
2
|
+
import { c as readExtraSans, d as writeExtraSans, f as writeTlsMode, g as reissueTlsLeaf, l as readTlsAccessStatus, m as ensureTlsCert, o as readLanHttpState, p as validateUploadedTls, r as applyLanHttp, t as DEFAULT_LAN_HTTP_PORT, u as readTlsMode } from "../../lan-http-bind-jKrj6OjQ.mjs";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
2
5
|
import * as os from "node:os";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { isIP } from "node:net";
|
|
8
|
+
//#region src/builtins/local-network/local-hostname.ts
|
|
9
|
+
var DNS_NAME = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])$/;
|
|
10
|
+
/**
|
|
11
|
+
* Parse the operator-typed local hostname.
|
|
12
|
+
*
|
|
13
|
+
* Empty / whitespace → `null` (advertise the raw LAN IP). A DNS name is
|
|
14
|
+
* lowercased. A URL, port suffix, path, or bare IP is rejected so it cannot
|
|
15
|
+
* leak into a SAN or a candidate URL.
|
|
16
|
+
*/
|
|
17
|
+
function parseLocalHostname(raw) {
|
|
18
|
+
const trimmed = raw.trim();
|
|
19
|
+
if (trimmed === "") return {
|
|
20
|
+
ok: true,
|
|
21
|
+
value: null
|
|
22
|
+
};
|
|
23
|
+
if (/[\s/:?#]/.test(trimmed) || trimmed.includes("\\")) return {
|
|
24
|
+
ok: false,
|
|
25
|
+
error: "Enter a DNS name such as camstack.lan, without a scheme or port."
|
|
26
|
+
};
|
|
27
|
+
const lowered = trimmed.toLowerCase();
|
|
28
|
+
if (isIP(lowered) !== 0) return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: "Use the address allowlist for IPs — this field is a DNS name."
|
|
31
|
+
};
|
|
32
|
+
if (!DNS_NAME.test(lowered)) return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: "Enter a DNS name such as camstack.lan, without a scheme or port."
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
ok: true,
|
|
38
|
+
value: lowered
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/builtins/local-network/ipv6-lan.ts
|
|
43
|
+
/**
|
|
44
|
+
* IPv6 helpers for LAN advertisement: prefix matching (privacy addresses
|
|
45
|
+
* rotate inside a /64), Linux `/proc/net/if_inet6` flags, and the gate
|
|
46
|
+
* that keeps temporary GUA off the connection candidate list.
|
|
47
|
+
*/
|
|
48
|
+
var IFA_F_TEMPORARY = 1;
|
|
49
|
+
var IFA_F_DEPRECATED = 32;
|
|
50
|
+
var IFA_F_TENTATIVE = 64;
|
|
51
|
+
/** First 64 bits, canonical hex groups, or null if the address is not IPv6. */
|
|
52
|
+
function ipv6Prefix64Key(address) {
|
|
53
|
+
const groups = parseIpv6Groups(address);
|
|
54
|
+
if (groups === null) return null;
|
|
55
|
+
return groups.slice(0, 4).map((g) => g.toString(16).padStart(4, "0")).join(":");
|
|
56
|
+
}
|
|
57
|
+
/** 32-char lowercase hex (same encoding as `/proc/net/if_inet6`). */
|
|
58
|
+
function ipv6ToHex32(address) {
|
|
59
|
+
const groups = parseIpv6Groups(address);
|
|
60
|
+
if (groups === null) return null;
|
|
61
|
+
return groups.map((g) => g.toString(16).padStart(4, "0")).join("");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse Linux `if_inet6` into hex32 → flags. Unknown / malformed lines
|
|
65
|
+
* are skipped. Keys match {@link ipv6ToHex32}.
|
|
66
|
+
*/
|
|
67
|
+
function parseProcIfInet6(text) {
|
|
68
|
+
const out = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const line of text.split("\n")) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (trimmed === "") continue;
|
|
72
|
+
const parts = trimmed.split(/\s+/);
|
|
73
|
+
const hex = parts[0]?.toLowerCase();
|
|
74
|
+
const flagsRaw = parts[4];
|
|
75
|
+
if (!hex || hex.length !== 32 || flagsRaw === void 0) continue;
|
|
76
|
+
const flags = Number.parseInt(flagsRaw, 16);
|
|
77
|
+
if (!Number.isFinite(flags)) continue;
|
|
78
|
+
out.set(hex, flags);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Whether this IPv6 should be auto-seeded / advertised.
|
|
84
|
+
* `flags === undefined` means the platform did not tell us (macOS, tests
|
|
85
|
+
* without a proc snapshot) — keep every non-link-local plausible address.
|
|
86
|
+
*
|
|
87
|
+
* On Linux, drop addresses marked TEMPORARY / DEPRECATED / TENTATIVE, and
|
|
88
|
+
* `flags === 0` which is how this kernel reports RFC 4941 privacy temps
|
|
89
|
+
* (the stable GUA is PERMANENT|NODAD). Do **not** require PERMANENT —
|
|
90
|
+
* stateful DHCPv6 GUA often has only NODAD (0x02).
|
|
91
|
+
*/
|
|
92
|
+
function shouldAdvertiseIpv6(address, flags) {
|
|
93
|
+
const a = address.toLowerCase().split("%")[0] ?? address;
|
|
94
|
+
if (a === "::" || a === "::1") return false;
|
|
95
|
+
if (a.startsWith("fe80:")) return false;
|
|
96
|
+
if (a.startsWith("ff")) return false;
|
|
97
|
+
if (flags === void 0) return true;
|
|
98
|
+
if ((flags & IFA_F_TEMPORARY) !== 0) return false;
|
|
99
|
+
if ((flags & IFA_F_DEPRECATED) !== 0) return false;
|
|
100
|
+
if ((flags & IFA_F_TENTATIVE) !== 0) return false;
|
|
101
|
+
if (flags === 0) return false;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
function ipv6AllowlistMatches(address, allowed) {
|
|
105
|
+
if (allowed.includes(address)) return true;
|
|
106
|
+
const key = ipv6Prefix64Key(address);
|
|
107
|
+
if (key === null) return false;
|
|
108
|
+
for (const entry of allowed) {
|
|
109
|
+
if (!entry.includes(":")) continue;
|
|
110
|
+
if (ipv6Prefix64Key(entry) === key) return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function parseIpv6Groups(address) {
|
|
115
|
+
const raw = address.toLowerCase().split("%")[0];
|
|
116
|
+
if (!raw || raw.includes(".")) return null;
|
|
117
|
+
const sides = raw.split("::");
|
|
118
|
+
if (sides.length > 2) return null;
|
|
119
|
+
const parsePart = (part) => {
|
|
120
|
+
if (part === void 0 || part === "") return [];
|
|
121
|
+
const groups = part.split(":");
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const g of groups) {
|
|
124
|
+
if (g === "") return null;
|
|
125
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
126
|
+
out.push(Number.parseInt(g, 16));
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
};
|
|
130
|
+
if (sides.length === 1) {
|
|
131
|
+
const groups = parsePart(sides[0]);
|
|
132
|
+
if (groups === null || groups.length !== 8) return null;
|
|
133
|
+
return groups;
|
|
134
|
+
}
|
|
135
|
+
const left = parsePart(sides[0]);
|
|
136
|
+
const right = parsePart(sides[1]);
|
|
137
|
+
if (left === null || right === null) return null;
|
|
138
|
+
const fill = 8 - left.length - right.length;
|
|
139
|
+
if (fill < 0) return null;
|
|
140
|
+
return [
|
|
141
|
+
...left,
|
|
142
|
+
...Array(fill).fill(0),
|
|
143
|
+
...right
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
3
147
|
//#region src/builtins/local-network/local-network.addon.ts
|
|
4
148
|
/**
|
|
5
149
|
* local-network — hub-only system cap implementation.
|
|
@@ -25,6 +169,8 @@ import * as os from "node:os";
|
|
|
25
169
|
*/
|
|
26
170
|
var MESH_PROVIDER_ADDON_IDS = ["tailscale-client"];
|
|
27
171
|
var POLL_INTERVAL_MS = 3e4;
|
|
172
|
+
/** Drop rotated IPv6 / dead extras from the persisted allowlist. */
|
|
173
|
+
var ALLOWLIST_RECONCILE_INTERVAL_MS = 3600 * 1e3;
|
|
28
174
|
/**
|
|
29
175
|
* Compiled-in boot default — mirrors `CONFIG_DEFAULTS.server.port` in
|
|
30
176
|
* `server/backend/src/boot/boot-config.ts`. Used only when neither the live
|
|
@@ -33,6 +179,8 @@ var POLL_INTERVAL_MS = 3e4;
|
|
|
33
179
|
var DEFAULT_HUB_PORT = 4443;
|
|
34
180
|
var LocalNetworkAddon = class extends BaseAddon {
|
|
35
181
|
pollTimer = null;
|
|
182
|
+
allowlistTimer = null;
|
|
183
|
+
allowlistReconcilePending = false;
|
|
36
184
|
lastSnapshotKey = "";
|
|
37
185
|
/** Optional public hostname tracked from `NetworkTunnelStarted`/
|
|
38
186
|
* `Stopped` events on the bus. */
|
|
@@ -48,7 +196,10 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
48
196
|
super({
|
|
49
197
|
allowedAddresses: [],
|
|
50
198
|
bootSeeded: false,
|
|
51
|
-
notificationBaseUrl: ""
|
|
199
|
+
notificationBaseUrl: "",
|
|
200
|
+
httpEnabled: true,
|
|
201
|
+
httpPort: DEFAULT_LAN_HTTP_PORT,
|
|
202
|
+
localHostname: ""
|
|
52
203
|
});
|
|
53
204
|
}
|
|
54
205
|
/**
|
|
@@ -75,6 +226,7 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
75
226
|
});
|
|
76
227
|
this.ctx.logger.info("local-network: first-boot auto-seed", { meta: { addresses: seed } });
|
|
77
228
|
}
|
|
229
|
+
await this.persistReconciledAllowlist();
|
|
78
230
|
const provider = {
|
|
79
231
|
list: async () => ({
|
|
80
232
|
interfaces: this.enumerate(),
|
|
@@ -94,8 +246,15 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
94
246
|
source: localPort.source
|
|
95
247
|
} });
|
|
96
248
|
await this.reconcileMeshEndpoint();
|
|
249
|
+
const httpState = readLanHttpState();
|
|
97
250
|
return {
|
|
98
|
-
endpoints: buildEndpoints(interfaces, localPort.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint
|
|
251
|
+
endpoints: buildEndpoints(interfaces, localPort.port, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint, {
|
|
252
|
+
enabled: this.config.httpEnabled !== false,
|
|
253
|
+
listening: httpState.listening,
|
|
254
|
+
httpPort: this.resolvedHttpPort(),
|
|
255
|
+
httpsPort: localPort.port,
|
|
256
|
+
localHostname: this.config.localHostname || null
|
|
257
|
+
}),
|
|
99
258
|
localPort
|
|
100
259
|
};
|
|
101
260
|
},
|
|
@@ -130,11 +289,42 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
130
289
|
dropped: addresses.length - cleaned.length
|
|
131
290
|
} });
|
|
132
291
|
return { success: true };
|
|
292
|
+
},
|
|
293
|
+
getTlsStatus: async () => this.snapshotTls(false),
|
|
294
|
+
regenerateCertificate: async (_input) => {
|
|
295
|
+
if (readTlsMode(this.tlsDataDir()) === "uploaded") throw new Error("Cannot regenerate an uploaded certificate — revert to generated first.");
|
|
296
|
+
const extraSans = [...readExtraSans(this.tlsDataDir())];
|
|
297
|
+
await reissueTlsLeaf(this.tlsDataDir(), { extraSans });
|
|
298
|
+
return this.snapshotTls(true);
|
|
299
|
+
},
|
|
300
|
+
uploadCertificate: async ({ certPem, keyPem, caPem }) => {
|
|
301
|
+
const valid = validateUploadedTls(certPem, keyPem);
|
|
302
|
+
if (!valid.ok) throw new Error(valid.error);
|
|
303
|
+
const dir = join(this.tlsDataDir(), "tls");
|
|
304
|
+
await mkdir(dir, { recursive: true });
|
|
305
|
+
await writeFile(join(dir, "camstack.crt"), certPem.endsWith("\n") ? certPem : `${certPem}\n`);
|
|
306
|
+
await writeFile(join(dir, "camstack.key"), keyPem.endsWith("\n") ? keyPem : `${keyPem}\n`, { mode: 384 });
|
|
307
|
+
if (caPem && caPem.trim() !== "") await writeFile(join(dir, "camstack-ca.crt"), caPem.endsWith("\n") ? caPem : `${caPem}\n`);
|
|
308
|
+
writeTlsMode(this.tlsDataDir(), "uploaded");
|
|
309
|
+
return this.snapshotTls(true);
|
|
310
|
+
},
|
|
311
|
+
downloadCa: async () => {
|
|
312
|
+
return { pem: this.snapshotTls(false).caCertPem ?? "" };
|
|
313
|
+
},
|
|
314
|
+
revertToGeneratedCertificate: async () => {
|
|
315
|
+
writeTlsMode(this.tlsDataDir(), "generated");
|
|
316
|
+
const extraSans = [...readExtraSans(this.tlsDataDir())];
|
|
317
|
+
await ensureTlsCert(this.tlsDataDir(), { extraSans });
|
|
318
|
+
return this.snapshotTls(true);
|
|
133
319
|
}
|
|
134
320
|
};
|
|
135
321
|
this.lastSnapshotKey = snapshotKey(this.enumerate());
|
|
136
322
|
this.pollTimer = setInterval(() => this.detectChanges(), POLL_INTERVAL_MS);
|
|
137
323
|
this.pollTimer.unref?.();
|
|
324
|
+
this.allowlistTimer = setInterval(() => {
|
|
325
|
+
this.persistReconciledAllowlist();
|
|
326
|
+
}, ALLOWLIST_RECONCILE_INTERVAL_MS);
|
|
327
|
+
this.allowlistTimer.unref?.();
|
|
138
328
|
this.ctx.eventBus?.subscribe({ category: EventCategory.NetworkTunnelStarted }, (event) => {
|
|
139
329
|
const data = event.data ?? {};
|
|
140
330
|
if (typeof data.url === "string") try {
|
|
@@ -153,16 +343,67 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
153
343
|
} : null);
|
|
154
344
|
});
|
|
155
345
|
this.ctx.logger.info("local-network initialized", { meta: { interfaceCount: this.enumerate().length } });
|
|
346
|
+
try {
|
|
347
|
+
await this.applyLanAccessFromSettings();
|
|
348
|
+
} catch (err) {
|
|
349
|
+
this.ctx.logger.warn("local-network: LAN HTTP not applied at boot", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
350
|
+
}
|
|
156
351
|
return [{
|
|
157
352
|
capability: localNetworkCapability,
|
|
158
353
|
provider
|
|
159
354
|
}];
|
|
160
355
|
}
|
|
356
|
+
globalSettingsSchema() {
|
|
357
|
+
return this.schema({ sections: [{
|
|
358
|
+
id: "local-access",
|
|
359
|
+
title: "LAN HTTP",
|
|
360
|
+
description: "Native apps on this Wi-Fi use http:// and skip the private certificate. The tunnel and this admin UI stay on HTTPS. Turn off once a publicly-trusted certificate covers the LAN name.",
|
|
361
|
+
columns: 1,
|
|
362
|
+
fields: [
|
|
363
|
+
this.field({
|
|
364
|
+
type: "boolean",
|
|
365
|
+
key: "httpEnabled",
|
|
366
|
+
label: "Enable HTTP on the LAN",
|
|
367
|
+
style: "switch",
|
|
368
|
+
default: true
|
|
369
|
+
}),
|
|
370
|
+
this.field({
|
|
371
|
+
type: "number",
|
|
372
|
+
key: "httpPort",
|
|
373
|
+
label: "HTTP port",
|
|
374
|
+
description: "Dedicated listener. Must not equal the HTTPS port (4443 by default).",
|
|
375
|
+
min: 1,
|
|
376
|
+
max: 65535,
|
|
377
|
+
step: 1,
|
|
378
|
+
default: DEFAULT_LAN_HTTP_PORT,
|
|
379
|
+
showWhen: {
|
|
380
|
+
field: "httpEnabled",
|
|
381
|
+
equals: true
|
|
382
|
+
}
|
|
383
|
+
}),
|
|
384
|
+
this.field({
|
|
385
|
+
type: "text",
|
|
386
|
+
key: "localHostname",
|
|
387
|
+
label: "Local hostname",
|
|
388
|
+
description: "Advertised to the viewer instead of the raw LAN IP, and added to the certificate SAN. Requires this name to resolve on the LAN (router DNS, mDNS, or a hosts file). Leave empty to keep using the IP.",
|
|
389
|
+
maxLength: 253,
|
|
390
|
+
placeholder: "camstack.lan"
|
|
391
|
+
})
|
|
392
|
+
]
|
|
393
|
+
}] });
|
|
394
|
+
}
|
|
395
|
+
async onConfigChanged() {
|
|
396
|
+
await this.applyLanAccessFromSettings();
|
|
397
|
+
}
|
|
161
398
|
async onShutdown() {
|
|
162
399
|
if (this.pollTimer) {
|
|
163
400
|
clearInterval(this.pollTimer);
|
|
164
401
|
this.pollTimer = null;
|
|
165
402
|
}
|
|
403
|
+
if (this.allowlistTimer) {
|
|
404
|
+
clearInterval(this.allowlistTimer);
|
|
405
|
+
this.allowlistTimer = null;
|
|
406
|
+
}
|
|
166
407
|
}
|
|
167
408
|
/**
|
|
168
409
|
* Other hub addons (e.g. cloudflare-tunnel) signal the active public
|
|
@@ -226,6 +467,42 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
226
467
|
}
|
|
227
468
|
}
|
|
228
469
|
}
|
|
470
|
+
resolvedHttpPort() {
|
|
471
|
+
const port = this.config.httpPort;
|
|
472
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : DEFAULT_LAN_HTTP_PORT;
|
|
473
|
+
}
|
|
474
|
+
tlsDataDir() {
|
|
475
|
+
return this.ctx.nodeDataDir ?? this.ctx.dataDir;
|
|
476
|
+
}
|
|
477
|
+
snapshotTls(restartRequired) {
|
|
478
|
+
const status = readTlsAccessStatus(this.tlsDataDir(), restartRequired);
|
|
479
|
+
return {
|
|
480
|
+
...status,
|
|
481
|
+
sans: [...status.sans]
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Bind or unbind the LAN HTTP sibling from the addon's own settings, and
|
|
486
|
+
* keep the leaf SAN in sync with `localHostname`. This is the settings
|
|
487
|
+
* side-effect path — there is no second persist API.
|
|
488
|
+
*/
|
|
489
|
+
async applyLanAccessFromSettings() {
|
|
490
|
+
const parsed = parseLocalHostname(this.config.localHostname ?? "");
|
|
491
|
+
if (!parsed.ok) throw new Error(parsed.error);
|
|
492
|
+
const hostname = parsed.value ?? "";
|
|
493
|
+
const httpsPort = (await this.resolveAdvertisedListenPort(null)).port;
|
|
494
|
+
const httpPort = this.resolvedHttpPort();
|
|
495
|
+
if (this.config.httpEnabled !== false && httpPort === httpsPort) throw new Error(`HTTP port ${httpPort} cannot equal the HTTPS listen port`);
|
|
496
|
+
await applyLanHttp({
|
|
497
|
+
enabled: this.config.httpEnabled !== false,
|
|
498
|
+
port: httpPort,
|
|
499
|
+
host: "0.0.0.0"
|
|
500
|
+
});
|
|
501
|
+
if (hostname !== "") {
|
|
502
|
+
writeExtraSans(this.tlsDataDir(), [hostname]);
|
|
503
|
+
if (readTlsMode(this.tlsDataDir()) === "generated") await ensureTlsCert(this.tlsDataDir(), { extraSans: [hostname] });
|
|
504
|
+
} else writeExtraSans(this.tlsDataDir(), []);
|
|
505
|
+
}
|
|
229
506
|
/**
|
|
230
507
|
* The port this hub is actually listening on, plus the provenance of the
|
|
231
508
|
* number. The boot loader merges the `CAMSTACK_PORT` env override into the
|
|
@@ -260,7 +537,26 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
260
537
|
}
|
|
261
538
|
}
|
|
262
539
|
enumerate() {
|
|
263
|
-
return enumerateOsInterfaces(os.networkInterfaces());
|
|
540
|
+
return enumerateOsInterfaces(os.networkInterfaces(), readLinuxIpv6Flags());
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Replace rotated IPv6 and drop addresses that no longer exist, without
|
|
544
|
+
* clearing operator extras that are still live (docker/vpn opt-in).
|
|
545
|
+
*/
|
|
546
|
+
async persistReconciledAllowlist(interfaces = this.enumerate()) {
|
|
547
|
+
if (this.allowlistReconcilePending) return;
|
|
548
|
+
this.allowlistReconcilePending = true;
|
|
549
|
+
try {
|
|
550
|
+
const next = reconcileAllowlist(this.config.allowedAddresses, interfaces);
|
|
551
|
+
if (sameAddressSet(this.config.allowedAddresses, next)) return;
|
|
552
|
+
await this.updateGlobalSettings({
|
|
553
|
+
allowedAddresses: next,
|
|
554
|
+
bootSeeded: true
|
|
555
|
+
});
|
|
556
|
+
this.ctx.logger.info("local-network: allowlist reconciled", { meta: { addresses: next } });
|
|
557
|
+
} finally {
|
|
558
|
+
this.allowlistReconcilePending = false;
|
|
559
|
+
}
|
|
264
560
|
}
|
|
265
561
|
detectChanges() {
|
|
266
562
|
const interfaces = this.enumerate();
|
|
@@ -271,6 +567,7 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
271
567
|
key
|
|
272
568
|
} });
|
|
273
569
|
this.lastSnapshotKey = key;
|
|
570
|
+
this.persistReconciledAllowlist(interfaces);
|
|
274
571
|
this.ctx.eventBus?.emit({
|
|
275
572
|
id: `local-network-changed-${Date.now()}`,
|
|
276
573
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -319,7 +616,7 @@ function validPort(value) {
|
|
|
319
616
|
if (value === null) return null;
|
|
320
617
|
return Number.isInteger(value) && value >= 1 && value <= 65535 ? value : null;
|
|
321
618
|
}
|
|
322
|
-
function enumerateOsInterfaces(ifaces) {
|
|
619
|
+
function enumerateOsInterfaces(ifaces, ipv6Flags) {
|
|
323
620
|
const raw = [];
|
|
324
621
|
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
325
622
|
if (!addrs) continue;
|
|
@@ -339,8 +636,12 @@ function enumerateOsInterfaces(ifaces) {
|
|
|
339
636
|
const classified = raw.map((r) => {
|
|
340
637
|
const kind = classifyKind(r.name, r.address, r.internal);
|
|
341
638
|
const reachableKind = kind === "lan" || kind === "wifi";
|
|
342
|
-
const
|
|
343
|
-
const
|
|
639
|
+
const hex = r.family === "IPv6" ? ipv6ToHex32(r.address) : null;
|
|
640
|
+
const flags = hex !== null ? ipv6Flags?.get(hex) : void 0;
|
|
641
|
+
const rangeOk = isPlausibleAutoSeed(r.address, r.family);
|
|
642
|
+
const stableV6 = r.family !== "IPv6" || shouldAdvertiseIpv6(r.address, flags);
|
|
643
|
+
const plausible = !r.internal && reachableKind && rangeOk && stableV6;
|
|
644
|
+
const plausibleReason = plausible ? "" : r.family === "IPv6" && rangeOk && !stableV6 ? "IPv6 temporary / privacy address — rotates and is not advertised." : explainNonPlausible({
|
|
344
645
|
kind,
|
|
345
646
|
family: r.family,
|
|
346
647
|
address: r.address,
|
|
@@ -369,13 +670,14 @@ function enumerateOsInterfaces(ifaces) {
|
|
|
369
670
|
/**
|
|
370
671
|
* Filter the interface list by the operator's allowlist. Empty
|
|
371
672
|
* allowlist = no-op (every interface passes); otherwise only entries
|
|
372
|
-
* whose `address` matches an allowlist entry remain.
|
|
373
|
-
*
|
|
673
|
+
* whose `address` matches an allowlist entry remain. IPv6 matches the
|
|
674
|
+
* allowlisted /64 so a rotated privacy address does not hide the live
|
|
675
|
+
* GUA. Loopback always survives so the SDK keeps `127.0.0.1` as the
|
|
676
|
+
* last-resort fallback.
|
|
374
677
|
*/
|
|
375
678
|
function applyAllowlist(interfaces, allowed) {
|
|
376
679
|
if (allowed.length === 0) return interfaces;
|
|
377
|
-
|
|
378
|
-
return interfaces.filter((i) => i.kind === "loopback" || set.has(i.address));
|
|
680
|
+
return interfaces.filter((i) => i.kind === "loopback" || ipv6AllowlistMatches(i.address, allowed) || allowed.includes(i.address));
|
|
379
681
|
}
|
|
380
682
|
/**
|
|
381
683
|
* Rank interfaces and pick the auto-selected outbound one. See the
|
|
@@ -399,31 +701,13 @@ function pickPreferred(interfaces) {
|
|
|
399
701
|
return prefixLen(b.netmask) - prefixLen(a.netmask);
|
|
400
702
|
})[0] ?? null;
|
|
401
703
|
}
|
|
402
|
-
|
|
403
|
-
* Build the ordered candidate URL list. Priority schema:
|
|
404
|
-
* 0 — preferred LAN IPv4
|
|
405
|
-
* 10+ — other LAN IPv4
|
|
406
|
-
* 100 — public tunnel hostname (always HTTPS)
|
|
407
|
-
* 150 — mesh endpoint (Tailscale MagicDNS / 100.x, always HTTPS)
|
|
408
|
-
* 200+ — LAN IPv6
|
|
409
|
-
* 1000 — loopback (last resort)
|
|
410
|
-
*
|
|
411
|
-
* The mesh endpoint ranks just below a true public funnel (a Funnel is
|
|
412
|
-
* reachable by ANY client; the mesh needs the peer on the same tailnet)
|
|
413
|
-
* but above the IPv6 / loopback fallbacks — giving remote, mesh-joined
|
|
414
|
-
* clients a working candidate when no public tunnel is up.
|
|
415
|
-
*
|
|
416
|
-
* `scheme` controls LAN + loopback URLs. Browsers running over HTTPS
|
|
417
|
-
* block `http://` candidates as mixed content, so callers loaded over
|
|
418
|
-
* HTTPS should pass `scheme: 'https'` even when probing a LAN IP — the
|
|
419
|
-
* hub's cert manager already issues a SAN-multi cert covering local
|
|
420
|
-
* interfaces. The public tunnel + mesh endpoint always emit `https://`
|
|
421
|
-
* (the tunnel edge / the hub itself terminates TLS).
|
|
422
|
-
*/
|
|
423
|
-
function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostname, scheme = "http", meshEndpoint = null) {
|
|
704
|
+
function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostname, scheme = "http", meshEndpoint = null, lanHttp = null) {
|
|
424
705
|
const out = [];
|
|
425
706
|
let priority = 0;
|
|
426
707
|
const preferred = pickPreferred(interfaces);
|
|
708
|
+
const httpLan = lanHttp?.enabled === true && lanHttp.listening === true;
|
|
709
|
+
const lanScheme = httpLan ? "http" : scheme;
|
|
710
|
+
const lanPort = httpLan ? lanHttp.httpPort : port;
|
|
427
711
|
const emit = (iface, kind, label, baseUrl, pri) => {
|
|
428
712
|
out.push({
|
|
429
713
|
label,
|
|
@@ -435,13 +719,29 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
435
719
|
priority: pri
|
|
436
720
|
});
|
|
437
721
|
};
|
|
438
|
-
if (
|
|
722
|
+
if (httpLan && lanHttp.localHostname) out.push({
|
|
723
|
+
label: "Local name",
|
|
724
|
+
baseUrl: `${lanScheme}://${lanHttp.localHostname}:${lanPort}`,
|
|
725
|
+
kind: "lan-ipv4",
|
|
726
|
+
interfaceKind: "lan",
|
|
727
|
+
plausible: true,
|
|
728
|
+
plausibleReason: "",
|
|
729
|
+
priority: priority++
|
|
730
|
+
});
|
|
731
|
+
if (preferred) emit(preferred, "lan-ipv4", `${formatKind(preferred.kind)} — ${preferred.name}`, `${lanScheme}://${preferred.address}:${lanPort}`, priority++);
|
|
439
732
|
for (const iface of interfaces) {
|
|
440
733
|
if (iface.internal || iface.family !== "IPv4") continue;
|
|
441
734
|
if (iface.kind === "loopback") continue;
|
|
442
735
|
if (preferred && iface.name === preferred.name && iface.address === preferred.address) continue;
|
|
443
736
|
if (iface.address.startsWith("169.254.")) continue;
|
|
444
|
-
emit(iface, "lan-ipv4", `${formatKind(iface.kind)} — ${iface.name}`, `${
|
|
737
|
+
emit(iface, "lan-ipv4", `${formatKind(iface.kind)} — ${iface.name}`, `${lanScheme}://${iface.address}:${lanPort}`, 10 + priority++);
|
|
738
|
+
}
|
|
739
|
+
if (!ipv4Only) for (const iface of interfaces) {
|
|
740
|
+
if (iface.internal || iface.family !== "IPv6") continue;
|
|
741
|
+
if (iface.kind === "loopback") continue;
|
|
742
|
+
if (iface.address.toLowerCase().startsWith("fe80:")) continue;
|
|
743
|
+
if (!iface.plausible) continue;
|
|
744
|
+
emit(iface, "lan-ipv6", `${formatKind(iface.kind)} — ${iface.name} (IPv6)`, `${lanScheme}://[${iface.address}]:${lanPort}`, 10 + priority++);
|
|
445
745
|
}
|
|
446
746
|
if (publicHostname) out.push({
|
|
447
747
|
label: "Public tunnel",
|
|
@@ -461,15 +761,6 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
461
761
|
plausibleReason: "",
|
|
462
762
|
priority: 150
|
|
463
763
|
});
|
|
464
|
-
if (!ipv4Only) {
|
|
465
|
-
let v6prio = 200;
|
|
466
|
-
for (const iface of interfaces) {
|
|
467
|
-
if (iface.internal || iface.family !== "IPv6") continue;
|
|
468
|
-
if (iface.kind === "loopback") continue;
|
|
469
|
-
if (iface.address.startsWith("fe80:")) continue;
|
|
470
|
-
emit(iface, "lan-ipv6", `${formatKind(iface.kind)} — ${iface.name} (IPv6)`, `${scheme}://[${iface.address}]:${port}`, v6prio++);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
764
|
if (includeLoopback) out.push({
|
|
474
765
|
label: "Loopback",
|
|
475
766
|
baseUrl: `${scheme}://127.0.0.1:${port}`,
|
|
@@ -487,16 +778,14 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
487
778
|
*
|
|
488
779
|
* - **IPv4**: skip link-local (`169.254.*`), keep the rest.
|
|
489
780
|
* - **IPv6**: skip link-local (`fe80::*`), unspecified, and
|
|
490
|
-
* multicast. Keep ULAs (`fc00::/7`
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
* prune them from the Network Addresses tab if the rotating IPs
|
|
494
|
-
* become a nuisance.
|
|
781
|
+
* multicast. Keep ULAs (`fc00::/7`) and Global Unicast (`2000::/3`).
|
|
782
|
+
* Privacy-extension temporary addresses are skipped when Linux flags
|
|
783
|
+
* mark them non-permanent (`plausible === false`).
|
|
495
784
|
*
|
|
496
785
|
* Skips docker/vpn/loopback/other entirely — those stay opt-in.
|
|
497
786
|
*/
|
|
498
787
|
function autoSeedAllowlist(interfaces) {
|
|
499
|
-
return [...new Set(interfaces.filter((i) => !i.internal && (i.kind === "lan" || i.kind === "wifi") && isPlausibleAutoSeed(i.address, i.family)).map((i) => i.address))];
|
|
788
|
+
return [...new Set(interfaces.filter((i) => !i.internal && (i.kind === "lan" || i.kind === "wifi") && isPlausibleAutoSeed(i.address, i.family) && (i.family !== "IPv6" || i.plausible)).map((i) => i.address))];
|
|
500
789
|
}
|
|
501
790
|
/**
|
|
502
791
|
* Per-interface tooltip text surfaced on the "Unlikely usable" badge.
|
|
@@ -585,5 +874,30 @@ function formatKind(kind) {
|
|
|
585
874
|
case "other": return "Other";
|
|
586
875
|
}
|
|
587
876
|
}
|
|
877
|
+
/**
|
|
878
|
+
* Rewrite a persisted allowlist against the live interface set:
|
|
879
|
+
* current auto-seed (LAN/Wi-Fi, stable IPv6) plus still-live operator
|
|
880
|
+
* extras (docker/vpn). Empty stays empty — that is "unrestricted".
|
|
881
|
+
*/
|
|
882
|
+
function reconcileAllowlist(current, interfaces) {
|
|
883
|
+
if (current.length === 0) return [];
|
|
884
|
+
const live = new Set(interfaces.map((i) => i.address));
|
|
885
|
+
const seed = autoSeedAllowlist(interfaces);
|
|
886
|
+
const seedSet = new Set(seed);
|
|
887
|
+
const extras = current.filter((a) => live.has(a) && !seedSet.has(a) && !a.includes(":"));
|
|
888
|
+
return [...new Set([...seed, ...extras])];
|
|
889
|
+
}
|
|
890
|
+
function sameAddressSet(a, b) {
|
|
891
|
+
if (a.length !== b.length) return false;
|
|
892
|
+
const set = new Set(a);
|
|
893
|
+
return b.every((addr) => set.has(addr));
|
|
894
|
+
}
|
|
895
|
+
function readLinuxIpv6Flags() {
|
|
896
|
+
try {
|
|
897
|
+
return parseProcIfInet6(readFileSync("/proc/net/if_inet6", "utf8"));
|
|
898
|
+
} catch {
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
588
902
|
//#endregion
|
|
589
|
-
export { LocalNetworkAddon, applyAllowlist, autoSeedAllowlist, buildEndpoints, classifyKind, enumerateOsInterfaces, explainNonPlausible, isPlausibleAutoSeed, pickPreferred, prefixLen, resolveAdvertisedPort };
|
|
903
|
+
export { ALLOWLIST_RECONCILE_INTERVAL_MS, LocalNetworkAddon, applyAllowlist, autoSeedAllowlist, buildEndpoints, classifyKind, enumerateOsInterfaces, explainNonPlausible, ipv6Prefix64Key, isPlausibleAutoSeed, parseProcIfInet6, pickPreferred, prefixLen, reconcileAllowlist, resolveAdvertisedPort, shouldAdvertiseIpv6 };
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DMEGlqg6.js");
|
|
7
7
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
8
8
|
/**
|
|
9
9
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { X as logDestinationCapability, yt as BaseAddon } from "../../dist-D4Vmj8iw.mjs";
|
|
2
2
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
3
3
|
/**
|
|
4
4
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DMEGlqg6.js");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
let node_util = require("node:util");
|
|
9
9
|
let node_os = require("node:os");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { $ as metricsProviderCapability, Gt as EventCategory, Mt as createEvent, yt as BaseAddon } from "../../dist-D4Vmj8iw.mjs";
|
|
2
2
|
import { execFile, execFileSync } from "node:child_process";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import * as os from "node:os";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DMEGlqg6.js");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
let node_util = require("node:util");
|
|
9
9
|
let node_os = require("node:os");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { Gt as EventCategory, L as enumerateInferenceDevices, Pt as emitReadiness, ct as scoreRuntimes, rt as platformProbeCapability, vt as errMsg, yt as BaseAddon } from "../../dist-
|
|
2
|
+
import { Gt as EventCategory, L as enumerateInferenceDevices, Pt as emitReadiness, ct as scoreRuntimes, rt as platformProbeCapability, vt as errMsg, yt as BaseAddon } from "../../dist-D4Vmj8iw.mjs";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import * as os from "node:os";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DMEGlqg6.js");
|
|
7
7
|
//#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
|
|
8
8
|
/**
|
|
9
9
|
* Reconcile the durable `enabledProviders` set against authoritative
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as EventCategory, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Gt as EventCategory, yt as BaseAddon } from "../../dist-D4Vmj8iw.mjs";
|
|
2
2
|
//#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
|
|
3
3
|
/**
|
|
4
4
|
* Reconcile the durable `enabledProviders` set against authoritative
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DMEGlqg6.js");
|
|
7
7
|
let zod = require("zod");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-
|
|
1
|
+
import { Ct as DeviceFeature, Gt as EventCategory, M as deriveBatteryPresence, Rt as nodePin, T as batteryCapability, Tt as DeviceType, i as BatteryStatusSchema, mt as streamQualityLabel, ut as snapshotCapability, vt as errMsg, w as bareAddonId, yt as BaseAddon } from "../../dist-D4Vmj8iw.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { signExpiringUrl, verifyExpiringUrl } from "@camstack/types/node";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DMEGlqg6.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let node_fs_promises = require("node:fs/promises");
|
|
9
9
|
let node_path = require("node:path");
|