@camstack/system 1.2.358 → 1.2.360
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-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- 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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.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/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- 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 +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- 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/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/address-book.d.ts +69 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +43 -47
- package/dist/builtins/local-network/local-network.addon.js +366 -81
- package/dist/builtins/local-network/local-network.addon.mjs +367 -80
- 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 +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- 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/{child-cap-dispatch-C2dFiu87.mjs → child-cap-dispatch-StBHObCA.mjs} +1 -1
- package/dist/{child-cap-dispatch-BWk99kmA.js → child-cap-dispatch-T3Kdhbb3.js} +1 -1
- package/dist/{composition-sources-D8Hapyav.mjs → composition-sources-DASc7NBj.mjs} +1 -1
- package/dist/{composition-sources-BaHPH661.js → composition-sources-DFgayxCc.js} +1 -1
- package/dist/{dist-D3bhbMY3.mjs → dist-C9Rk6KVv.mjs} +381 -115
- package/dist/{dist-DzAHit0a.js → dist-Cv1lnnQq.js} +414 -118
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{retired-settings-keys-CkXoPjKy.js → retired-settings-keys-BGNqRNhu.js} +1 -1
- package/dist/{retired-settings-keys-B6AFmtlO.mjs → retired-settings-keys-BjG8bQVI.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,237 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-Cv1lnnQq.js");
|
|
4
4
|
const require_tls = require("../../tls-CCMX7MjX.js");
|
|
5
|
+
let zod = require("zod");
|
|
5
6
|
let node_os = require("node:os");
|
|
6
7
|
node_os = require_chunk.__toESM(node_os);
|
|
7
8
|
let node_fs = require("node:fs");
|
|
8
9
|
let node_net = require("node:net");
|
|
10
|
+
//#region src/builtins/local-network/address-book.ts
|
|
11
|
+
/**
|
|
12
|
+
* The Network page's address book, as pure functions: custom addresses plus
|
|
13
|
+
* the role flags carried by detected and custom addresses alike.
|
|
14
|
+
*
|
|
15
|
+
* ## Storage (D734)
|
|
16
|
+
*
|
|
17
|
+
* One key per role: `appConnectionUrls` (`appConnections`, many),
|
|
18
|
+
* `notificationTapUrl` (`notificationTaps`), `publicEndpointUrl`
|
|
19
|
+
* (`publicEndpoint`) and `internalUrl` (`internal`). A single-valued role is
|
|
20
|
+
* one URL — at most one address carries it, by construction. `''` / `[]` is
|
|
21
|
+
* automatic. The custom addresses are `customAddresses` (url + label).
|
|
22
|
+
*
|
|
23
|
+
* The keys of the earlier role names (`viewerBaseUrls`, `notificationBaseUrl`,
|
|
24
|
+
* `signInBaseUrl`, `agentBaseUrl`) are not read: a value stored under one is
|
|
25
|
+
* ignored, and that role is automatic until the operator flags it again
|
|
26
|
+
* (D734 — one install, re-set by hand, no migration code).
|
|
27
|
+
*
|
|
28
|
+
* ## A flag on an address that is in no row
|
|
29
|
+
*
|
|
30
|
+
* A flagged detected address can disappear (a DHCP move) while it still wins
|
|
31
|
+
* its role. {@link normalizeAddressBook} turns every single-valued flag that
|
|
32
|
+
* names neither a detected address, a custom one nor the app link into a
|
|
33
|
+
* custom address carrying that role, on every read; the next write persists
|
|
34
|
+
* the result — so the address shows as a custom row instead of vanishing.
|
|
35
|
+
*/
|
|
36
|
+
var CustomAddressListSchema = zod.z.array(zod.z.object({
|
|
37
|
+
baseUrl: zod.z.string(),
|
|
38
|
+
label: zod.z.string()
|
|
39
|
+
}));
|
|
40
|
+
/** The label a flag promoted to a custom address is given. */
|
|
41
|
+
var PROMOTED_LABEL = {
|
|
42
|
+
notificationTaps: "Notification taps address",
|
|
43
|
+
internal: "Internal address",
|
|
44
|
+
publicEndpoint: "Public endpoint"
|
|
45
|
+
};
|
|
46
|
+
var SINGLE_ROLES = [
|
|
47
|
+
"notificationTaps",
|
|
48
|
+
"internal",
|
|
49
|
+
"publicEndpoint"
|
|
50
|
+
];
|
|
51
|
+
function flagOf(value) {
|
|
52
|
+
return value === "" ? null : value;
|
|
53
|
+
}
|
|
54
|
+
/** Parse the stored custom list; a malformed store reads as empty, never throws. */
|
|
55
|
+
function parseCustomAddresses(raw) {
|
|
56
|
+
const parsed = CustomAddressListSchema.safeParse(raw ?? []);
|
|
57
|
+
return parsed.success ? parsed.data : [];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The address book as it stands: stored custom addresses, plus every
|
|
61
|
+
* single-valued flag that names neither a detected address, a custom one nor
|
|
62
|
+
* the app link, promoted to a custom address (see the module doc).
|
|
63
|
+
*/
|
|
64
|
+
function normalizeAddressBook(stored, detectedUrls) {
|
|
65
|
+
const flags = {
|
|
66
|
+
appConnections: stored.appConnectionUrls,
|
|
67
|
+
notificationTaps: flagOf(stored.notificationTapUrl),
|
|
68
|
+
internal: flagOf(stored.internalUrl),
|
|
69
|
+
publicEndpoint: flagOf(stored.publicEndpointUrl)
|
|
70
|
+
};
|
|
71
|
+
const custom = [...parseCustomAddresses(stored.customAddresses)];
|
|
72
|
+
for (const role of SINGLE_ROLES) {
|
|
73
|
+
const url = flags[role];
|
|
74
|
+
if (url === null || require_dist.isAppLinkBase(url) || detectedUrls.has(url)) continue;
|
|
75
|
+
if (custom.some((c) => c.baseUrl === url)) continue;
|
|
76
|
+
custom.push({
|
|
77
|
+
baseUrl: url,
|
|
78
|
+
label: PROMOTED_LABEL[role]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
custom,
|
|
83
|
+
flags
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** The stored-settings patch that persists `state`. */
|
|
87
|
+
function addressBookPatch(state) {
|
|
88
|
+
return {
|
|
89
|
+
customAddresses: state.custom,
|
|
90
|
+
appConnectionUrls: state.flags.appConnections,
|
|
91
|
+
notificationTapUrl: state.flags.notificationTaps ?? "",
|
|
92
|
+
publicEndpointUrl: state.flags.publicEndpoint ?? "",
|
|
93
|
+
internalUrl: state.flags.internal ?? ""
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function withCustomAddress(state, entry) {
|
|
97
|
+
const custom = state.custom.some((c) => c.baseUrl === entry.baseUrl) ? state.custom.map((c) => c.baseUrl === entry.baseUrl ? entry : c) : [...state.custom, entry];
|
|
98
|
+
return {
|
|
99
|
+
...state,
|
|
100
|
+
custom
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Drop a custom address and every flag it carried (those roles turn automatic). */
|
|
104
|
+
function withoutCustomAddress(state, baseUrl) {
|
|
105
|
+
const clear = (flag) => flag === baseUrl ? null : flag;
|
|
106
|
+
return {
|
|
107
|
+
custom: state.custom.filter((c) => c.baseUrl !== baseUrl),
|
|
108
|
+
flags: {
|
|
109
|
+
appConnections: state.flags.appConnections.filter((u) => u !== baseUrl),
|
|
110
|
+
notificationTaps: clear(state.flags.notificationTaps),
|
|
111
|
+
internal: clear(state.flags.internal),
|
|
112
|
+
publicEndpoint: clear(state.flags.publicEndpoint)
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Every role back to automatic for `role`. */
|
|
117
|
+
function withRoleAutomatic(state, role) {
|
|
118
|
+
return role === "appConnections" ? {
|
|
119
|
+
...state,
|
|
120
|
+
flags: {
|
|
121
|
+
...state.flags,
|
|
122
|
+
appConnections: []
|
|
123
|
+
}
|
|
124
|
+
} : {
|
|
125
|
+
...state,
|
|
126
|
+
flags: {
|
|
127
|
+
...state.flags,
|
|
128
|
+
[role]: null
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Flag or unflag `baseUrl` for `role`. A single-valued role flagged here moves
|
|
134
|
+
* off every other address (it is one URL); unflagging the address that carries
|
|
135
|
+
* it returns the role to automatic. The app link flagged for notification
|
|
136
|
+
* taps IS the automatic answer, stored as automatic — one representation.
|
|
137
|
+
* `appConnections` toggles inside the set; toggling while automatic starts
|
|
138
|
+
* from the automatic set (`autoApp`) so the other addresses keep racing.
|
|
139
|
+
*/
|
|
140
|
+
function withRoleFlag(state, role, baseUrl, enabled, autoApp) {
|
|
141
|
+
if (role === "appConnections") {
|
|
142
|
+
const without = (state.flags.appConnections.length === 0 ? autoApp : state.flags.appConnections).filter((u) => u !== baseUrl);
|
|
143
|
+
const appConnections = enabled ? [...without, baseUrl] : without;
|
|
144
|
+
return {
|
|
145
|
+
...state,
|
|
146
|
+
flags: {
|
|
147
|
+
...state.flags,
|
|
148
|
+
appConnections
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (role === "notificationTaps" && require_dist.isAppLinkBase(baseUrl)) return withRoleAutomatic(state, role);
|
|
153
|
+
if (enabled) return {
|
|
154
|
+
...state,
|
|
155
|
+
flags: {
|
|
156
|
+
...state.flags,
|
|
157
|
+
[role]: baseUrl
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
return state.flags[role] === baseUrl ? withRoleAutomatic(state, role) : state;
|
|
161
|
+
}
|
|
162
|
+
/** What a row may be flagged with. */
|
|
163
|
+
function eligibleRoles(endpoint) {
|
|
164
|
+
if (endpoint.kind === "loopback") return [];
|
|
165
|
+
if (endpoint.kind === "app-link") return ["notificationTaps"];
|
|
166
|
+
return [
|
|
167
|
+
"appConnections",
|
|
168
|
+
"notificationTaps",
|
|
169
|
+
"internal",
|
|
170
|
+
"publicEndpoint"
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
/** A custom address as a palette entry (`kind: 'custom'`). */
|
|
174
|
+
function customEndpoint(entry) {
|
|
175
|
+
return {
|
|
176
|
+
label: entry.label === "" ? "Custom address" : entry.label,
|
|
177
|
+
baseUrl: entry.baseUrl,
|
|
178
|
+
kind: "custom",
|
|
179
|
+
interfaceKind: "other",
|
|
180
|
+
plausible: true,
|
|
181
|
+
plausibleReason: "",
|
|
182
|
+
priority: 200
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The palette a client races: the detected endpoints plus the custom
|
|
187
|
+
* addresses flagged `appConnections` (a detected URL is never listed twice).
|
|
188
|
+
*/
|
|
189
|
+
function withAppConnectionCustomEndpoints(detected, state) {
|
|
190
|
+
const known = new Set(detected.map((e) => e.baseUrl));
|
|
191
|
+
const wanted = new Set(state.flags.appConnections);
|
|
192
|
+
const extra = state.custom.filter((c) => wanted.has(c.baseUrl) && !known.has(c.baseUrl)).map(customEndpoint);
|
|
193
|
+
return [...detected, ...extra];
|
|
194
|
+
}
|
|
195
|
+
/** Every role's answer — the resolvers from `@camstack/types`, nothing else. */
|
|
196
|
+
function resolveRoles(state, appPalette, httpsPalette) {
|
|
197
|
+
return require_dist.resolveHubAddressRoles(withAppConnectionCustomEndpoints(appPalette, state), httpsPalette, state.flags);
|
|
198
|
+
}
|
|
199
|
+
function flagsOn(state, baseUrl) {
|
|
200
|
+
const out = [];
|
|
201
|
+
if (state.flags.appConnections.includes(baseUrl)) out.push("appConnections");
|
|
202
|
+
for (const role of SINGLE_ROLES) if (state.flags[role] === baseUrl) out.push(role);
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
function inUseOn(roles, baseUrl) {
|
|
206
|
+
const out = [];
|
|
207
|
+
if (roles.appConnections.resolved.includes(baseUrl)) out.push("appConnections");
|
|
208
|
+
for (const role of SINGLE_ROLES) if (roles[role].resolved === baseUrl) out.push(role);
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
/** The Addresses table: app link, detected rows, then custom-only rows. */
|
|
212
|
+
function buildAddressBook(state, inputs) {
|
|
213
|
+
const roles = resolveRoles(state, inputs.rowsPalette, inputs.httpsPalette);
|
|
214
|
+
const customUrls = new Set(state.custom.map((c) => c.baseUrl));
|
|
215
|
+
const detectedUrls = new Set(inputs.rowsPalette.map((e) => e.baseUrl));
|
|
216
|
+
const row = (endpoint, detected) => ({
|
|
217
|
+
...endpoint,
|
|
218
|
+
detected,
|
|
219
|
+
custom: customUrls.has(endpoint.baseUrl),
|
|
220
|
+
flags: endpoint.kind === "app-link" ? [] : flagsOn(state, endpoint.baseUrl),
|
|
221
|
+
inUse: endpoint.kind === "app-link" ? roles.notificationTaps.resolved === "https://camstack.zentik.app" ? ["notificationTaps"] : [] : inUseOn(roles, endpoint.baseUrl),
|
|
222
|
+
eligible: eligibleRoles(endpoint)
|
|
223
|
+
});
|
|
224
|
+
return {
|
|
225
|
+
rows: [
|
|
226
|
+
row(inputs.appLink, false),
|
|
227
|
+
...inputs.rowsPalette.map((e) => row(e, true)),
|
|
228
|
+
...state.custom.filter((c) => !detectedUrls.has(c.baseUrl)).map((c) => row(customEndpoint(c), false))
|
|
229
|
+
],
|
|
230
|
+
roles,
|
|
231
|
+
appLinkHub: inputs.appLinkHub
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
9
235
|
//#region src/builtins/local-network/ipv6-lan.ts
|
|
10
236
|
/**
|
|
11
237
|
* IPv6 helpers for LAN advertisement: prefix matching (privacy addresses
|
|
@@ -197,72 +423,137 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
197
423
|
super({
|
|
198
424
|
allowedAddresses: [],
|
|
199
425
|
bootSeeded: false,
|
|
200
|
-
|
|
201
|
-
|
|
426
|
+
notificationTapUrl: "",
|
|
427
|
+
appConnectionUrls: [],
|
|
428
|
+
customAddresses: [],
|
|
202
429
|
httpEnabled: true,
|
|
203
430
|
httpPort: require_tls.DEFAULT_LAN_HTTP_PORT,
|
|
204
431
|
localHostname: "",
|
|
205
|
-
|
|
206
|
-
|
|
432
|
+
internalUrl: "",
|
|
433
|
+
publicEndpointUrl: ""
|
|
207
434
|
});
|
|
208
435
|
}
|
|
209
436
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
437
|
+
* The detected palettes, with the mesh reconciled once: `https` (what
|
|
438
|
+
* `internal` and `publicEndpoint` resolve over, and the app link's `hub=`)
|
|
439
|
+
* and `http` (what the app-connections race resolves over).
|
|
213
440
|
*/
|
|
214
|
-
async
|
|
441
|
+
async palettes() {
|
|
215
442
|
const port = (await this.resolveAdvertisedListenPort(null)).port;
|
|
216
|
-
|
|
443
|
+
await this.reconcileMeshEndpoint();
|
|
217
444
|
return {
|
|
218
|
-
|
|
219
|
-
|
|
445
|
+
http: this.endpointsNow(false, false, "http", port),
|
|
446
|
+
https: this.endpointsNow(false, false, "https", port)
|
|
220
447
|
};
|
|
221
448
|
}
|
|
449
|
+
/** The stored address book, normalised against what is detected now. */
|
|
450
|
+
addressState(detected) {
|
|
451
|
+
return normalizeAddressBook(this.config, new Set(detected.map((e) => e.baseUrl)));
|
|
452
|
+
}
|
|
453
|
+
/** The role flags alone — they do not depend on what is detected. */
|
|
454
|
+
storedFlags() {
|
|
455
|
+
return normalizeAddressBook(this.config, /* @__PURE__ */ new Set()).flags;
|
|
456
|
+
}
|
|
457
|
+
/** `getAddresses`: the Addresses table and every role's answer. */
|
|
458
|
+
async addressBook(scheme) {
|
|
459
|
+
const { http, https } = await this.palettes();
|
|
460
|
+
return buildAddressBook(this.addressState([...http, ...https]), {
|
|
461
|
+
rowsPalette: scheme === "http" ? http : https,
|
|
462
|
+
httpsPalette: https,
|
|
463
|
+
appLink: appLinkEndpoint(),
|
|
464
|
+
appLinkHub: require_dist.pickAppLinkHub(https)
|
|
465
|
+
});
|
|
466
|
+
}
|
|
222
467
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* 2026-10-06; before it AUTO was the tunnel, then the mesh). The app link is
|
|
226
|
-
* not a hub address: the NC's signed media and buttons keep their own hub
|
|
227
|
-
* ladder when it is flagged; only viewer taps take the app form.
|
|
468
|
+
* Apply one change to the address book and persist it, normalised (a flag
|
|
469
|
+
* naming an address in no row becomes a custom address in the same write).
|
|
228
470
|
*/
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
471
|
+
async changeAddressBook(change) {
|
|
472
|
+
const { http, https } = await this.palettes();
|
|
473
|
+
const detected = [...http, ...https];
|
|
474
|
+
const detectedUrls = new Set(detected.map((e) => e.baseUrl));
|
|
475
|
+
const changed = change(this.addressState(detected), {
|
|
476
|
+
detectedUrls,
|
|
477
|
+
autoApp: [...new Set(detected.filter(require_dist.isDefaultAppConnectionEndpoint).map((e) => e.baseUrl))]
|
|
478
|
+
});
|
|
479
|
+
const after = normalizeAddressBook({
|
|
480
|
+
...addressBookPatch(changed),
|
|
481
|
+
customAddresses: changed.custom
|
|
482
|
+
}, detectedUrls);
|
|
483
|
+
await this.updateGlobalSettings(addressBookPatch(after));
|
|
484
|
+
}
|
|
485
|
+
/** The deprecated `getHubAddresses` shape, from the role resolvers. */
|
|
486
|
+
async snapshotHubAddresses() {
|
|
487
|
+
const { roles } = await this.addressBook("https");
|
|
488
|
+
const legacy = (r) => ({
|
|
489
|
+
override: r.flagged,
|
|
490
|
+
resolved: r.resolved,
|
|
491
|
+
source: r.source === "flagged" ? "override" : r.source
|
|
492
|
+
});
|
|
493
|
+
return {
|
|
494
|
+
cluster: legacy(roles.internal),
|
|
495
|
+
browser: legacy(roles.publicEndpoint)
|
|
496
|
+
};
|
|
232
497
|
}
|
|
233
498
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
499
|
+
* Flag / unflag `baseUrl` for `role`, or (`baseUrl: null`) return the role
|
|
500
|
+
* to automatic. Validated: the address must be a row (detected, custom, or
|
|
501
|
+
* the app link for `notificationTaps`), never loopback, and
|
|
502
|
+
* `publicEndpoint` on plain http only when accepted. A refusal stores nothing.
|
|
237
503
|
*/
|
|
238
|
-
async
|
|
239
|
-
|
|
240
|
-
|
|
504
|
+
async setRole(role, baseUrl, enabled, acceptInsecureHttp) {
|
|
505
|
+
if (baseUrl === null || baseUrl.trim() === "" && role !== "appConnections") {
|
|
506
|
+
await this.changeAddressBook((state) => withRoleAutomatic(state, role));
|
|
507
|
+
this.ctx.logger.info("local-network: address role set to automatic", { meta: { role } });
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
let target = baseUrl.trim();
|
|
511
|
+
if (enabled) {
|
|
512
|
+
const check = require_dist.checkRoleAddress(role, target, { acceptInsecureHttp });
|
|
513
|
+
if (!check.ok) throw new Error(check.reason);
|
|
514
|
+
target = check.baseUrl;
|
|
515
|
+
}
|
|
516
|
+
await this.changeAddressBook((state, ctx) => {
|
|
517
|
+
const known = require_dist.isAppLinkBase(target) || ctx.detectedUrls.has(target) || state.custom.some((c) => c.baseUrl === target);
|
|
518
|
+
if (enabled && !known && role !== "notificationTaps") throw new Error(`${target} is not in the address list — add it as a custom address first.`);
|
|
519
|
+
return withRoleFlag(state, role, target, enabled, ctx.autoApp);
|
|
520
|
+
});
|
|
521
|
+
this.ctx.logger.info("local-network: address role set", { meta: {
|
|
522
|
+
role,
|
|
523
|
+
baseUrl: target,
|
|
524
|
+
enabled
|
|
525
|
+
} });
|
|
241
526
|
}
|
|
242
527
|
async notificationEndpointSnapshot() {
|
|
243
|
-
const
|
|
528
|
+
const resolution = require_dist.resolveNotificationTapAddress(this.storedFlags());
|
|
529
|
+
const port = (await this.resolveAdvertisedListenPort(null)).port;
|
|
244
530
|
return {
|
|
245
|
-
baseUrl:
|
|
246
|
-
resolved:
|
|
247
|
-
appLinkHub: await this.
|
|
531
|
+
baseUrl: resolution.flagged,
|
|
532
|
+
resolved: resolution.resolved,
|
|
533
|
+
appLinkHub: require_dist.pickAppLinkHub(await this.buildAdvertisedEndpoints(false, false, "https", port))
|
|
248
534
|
};
|
|
249
535
|
}
|
|
250
536
|
async snapshotViewerEndpoints() {
|
|
251
|
-
const
|
|
537
|
+
const resolution = require_dist.resolveAppConnections(await this.buildAdvertisedEndpoints(false, false, "http", (await this.resolveAdvertisedListenPort(null)).port), this.storedFlags());
|
|
252
538
|
return {
|
|
253
|
-
baseUrls:
|
|
254
|
-
resolved:
|
|
539
|
+
baseUrls: resolution.flagged,
|
|
540
|
+
resolved: resolution.resolved
|
|
255
541
|
};
|
|
256
542
|
}
|
|
257
543
|
/**
|
|
258
|
-
* Palette shown
|
|
259
|
-
*
|
|
260
|
-
*
|
|
544
|
+
* Palette shown to clients: LAN IPv4, stable LAN IPv6, public, mesh, and
|
|
545
|
+
* the custom addresses flagged `appConnections` (`kind: 'custom'`). The app
|
|
546
|
+
* HTTP/WS race is a subset (`selectAppConnectionEndpoints`) — IPv6 stays in this
|
|
547
|
+
* list because ICE/WebRTC gathers it regardless.
|
|
261
548
|
*/
|
|
262
549
|
async buildAdvertisedEndpoints(includeLoopback, ipv4Only, scheme, httpsPort) {
|
|
263
|
-
const allow = this.config.allowedAddresses;
|
|
264
|
-
const interfaces = applyAllowlist(this.enumerate(), allow);
|
|
265
550
|
await this.reconcileMeshEndpoint();
|
|
551
|
+
const detected = this.endpointsNow(includeLoopback, ipv4Only, scheme, httpsPort);
|
|
552
|
+
return withAppConnectionCustomEndpoints(detected, this.addressState(detected));
|
|
553
|
+
}
|
|
554
|
+
/** The DETECTED palette from the cached interface/tunnel/mesh state. */
|
|
555
|
+
endpointsNow(includeLoopback, ipv4Only, scheme, httpsPort) {
|
|
556
|
+
const interfaces = applyAllowlist(this.enumerate(), this.config.allowedAddresses);
|
|
266
557
|
const httpState = require_tls.readLanHttpState();
|
|
267
558
|
return buildEndpoints(interfaces, httpsPort, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint, {
|
|
268
559
|
enabled: this.config.httpEnabled !== false,
|
|
@@ -305,35 +596,50 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
305
596
|
},
|
|
306
597
|
getNotificationEndpoint: async () => this.notificationEndpointSnapshot(),
|
|
307
598
|
setNotificationEndpoint: async ({ baseUrl }) => {
|
|
308
|
-
await this.
|
|
309
|
-
this.ctx.logger.info("local-network: notification endpoint set", { meta: { baseUrl: baseUrl ?? "(auto)" } });
|
|
599
|
+
await this.setRole("notificationTaps", baseUrl, true, false);
|
|
310
600
|
return this.notificationEndpointSnapshot();
|
|
311
601
|
},
|
|
312
|
-
getHubAddresses: async () => this.snapshotHubAddresses(),
|
|
313
|
-
setHubAddress: async ({ audience, baseUrl }) => {
|
|
314
|
-
let stored = "";
|
|
315
|
-
if (baseUrl !== null && baseUrl.trim() !== "") {
|
|
316
|
-
const check = require_dist.validateHubAddressOverride(baseUrl);
|
|
317
|
-
if (!check.ok) throw new Error(check.reason);
|
|
318
|
-
stored = check.baseUrl;
|
|
319
|
-
}
|
|
320
|
-
await this.updateGlobalSettings(audience === "cluster" ? { agentBaseUrl: stored } : { signInBaseUrl: stored });
|
|
321
|
-
this.ctx.logger.info("local-network: hub address override set", { meta: {
|
|
322
|
-
audience,
|
|
323
|
-
baseUrl: stored === "" ? "(auto)" : stored
|
|
324
|
-
} });
|
|
325
|
-
return this.snapshotHubAddresses();
|
|
326
|
-
},
|
|
327
602
|
getViewerEndpoints: async () => this.snapshotViewerEndpoints(),
|
|
328
603
|
setViewerEndpoints: async ({ baseUrls }) => {
|
|
329
|
-
const
|
|
330
|
-
await this.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
604
|
+
const appConnections = [...new Set(baseUrls.filter((u) => u.length > 0))];
|
|
605
|
+
await this.changeAddressBook((state) => ({
|
|
606
|
+
...state,
|
|
607
|
+
flags: {
|
|
608
|
+
...state.flags,
|
|
609
|
+
appConnections
|
|
610
|
+
}
|
|
611
|
+
}));
|
|
612
|
+
this.ctx.logger.info("local-network: app connections set", { meta: {
|
|
613
|
+
count: appConnections.length,
|
|
614
|
+
auto: appConnections.length === 0
|
|
334
615
|
} });
|
|
335
616
|
return this.snapshotViewerEndpoints();
|
|
336
617
|
},
|
|
618
|
+
getAddresses: async (input) => this.addressBook(input?.scheme ?? "https"),
|
|
619
|
+
addCustomAddress: async ({ baseUrl, label }) => {
|
|
620
|
+
const check = require_dist.validateCustomAddress(baseUrl);
|
|
621
|
+
if (!check.ok) throw new Error(check.reason);
|
|
622
|
+
const entry = {
|
|
623
|
+
baseUrl: check.baseUrl,
|
|
624
|
+
label: label.trim()
|
|
625
|
+
};
|
|
626
|
+
await this.changeAddressBook((state) => withCustomAddress(state, entry));
|
|
627
|
+
this.ctx.logger.info("local-network: custom address added", { meta: {
|
|
628
|
+
baseUrl: entry.baseUrl,
|
|
629
|
+
label: entry.label
|
|
630
|
+
} });
|
|
631
|
+
return this.addressBook("https");
|
|
632
|
+
},
|
|
633
|
+
removeCustomAddress: async ({ baseUrl }) => {
|
|
634
|
+
await this.changeAddressBook((state) => withoutCustomAddress(state, baseUrl));
|
|
635
|
+
this.ctx.logger.info("local-network: custom address removed", { meta: { baseUrl } });
|
|
636
|
+
return this.addressBook("https");
|
|
637
|
+
},
|
|
638
|
+
setAddressRole: async ({ role, baseUrl, enabled, acceptInsecureHttp }) => {
|
|
639
|
+
await this.setRole(role, baseUrl, enabled, acceptInsecureHttp === true);
|
|
640
|
+
return this.addressBook("https");
|
|
641
|
+
},
|
|
642
|
+
getHubAddresses: async () => this.snapshotHubAddresses(),
|
|
337
643
|
getAllowedAddresses: async () => ({ addresses: this.config.allowedAddresses }),
|
|
338
644
|
resetAllowlistToBestMatch: async () => {
|
|
339
645
|
const seed = autoSeedAllowlist(this.enumerate());
|
|
@@ -875,25 +1181,6 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
875
1181
|
});
|
|
876
1182
|
return out.toSorted((a, b) => a.priority - b.priority);
|
|
877
1183
|
}
|
|
878
|
-
/** AUTO viewer race: LAN IPv4 + the public tunnel. Never IPv6, mesh, or loopback. */
|
|
879
|
-
function isDefaultViewerEndpoint(endpoint) {
|
|
880
|
-
if (endpoint.kind === "lan-ipv4") return true;
|
|
881
|
-
return endpoint.kind === "public" && endpoint.interfaceKind === "public";
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* The URLs the SDK should race for API / viewer access.
|
|
885
|
-
*
|
|
886
|
-
* Empty `viewerBaseUrls` = AUTO (IPv4 + public tunnel). A non-empty list is
|
|
887
|
-
* the operator's pin; if none of those URLs are in the current palette,
|
|
888
|
-
* fall back to AUTO rather than racing an empty set.
|
|
889
|
-
*/
|
|
890
|
-
function selectViewerEndpoints(endpoints, viewerBaseUrls) {
|
|
891
|
-
const auto = endpoints.filter(isDefaultViewerEndpoint);
|
|
892
|
-
if (viewerBaseUrls.length === 0) return auto;
|
|
893
|
-
const wanted = new Set(viewerBaseUrls);
|
|
894
|
-
const explicit = endpoints.filter((e) => wanted.has(e.baseUrl));
|
|
895
|
-
return explicit.length > 0 ? explicit : auto;
|
|
896
|
-
}
|
|
897
1184
|
/**
|
|
898
1185
|
* First-boot heuristic: which addresses should the allowlist start
|
|
899
1186
|
* with? Includes LAN + Wi-Fi IPv4 addresses + plausible IPv6:
|
|
@@ -1032,12 +1319,10 @@ exports.classifyKind = classifyKind;
|
|
|
1032
1319
|
exports.enumerateOsInterfaces = enumerateOsInterfaces;
|
|
1033
1320
|
exports.explainNonPlausible = explainNonPlausible;
|
|
1034
1321
|
exports.ipv6Prefix64Key = ipv6Prefix64Key;
|
|
1035
|
-
exports.isDefaultViewerEndpoint = isDefaultViewerEndpoint;
|
|
1036
1322
|
exports.isPlausibleAutoSeed = isPlausibleAutoSeed;
|
|
1037
1323
|
exports.parseProcIfInet6 = parseProcIfInet6;
|
|
1038
1324
|
exports.pickPreferred = pickPreferred;
|
|
1039
1325
|
exports.prefixLen = prefixLen;
|
|
1040
1326
|
exports.reconcileAllowlist = reconcileAllowlist;
|
|
1041
1327
|
exports.resolveAdvertisedPort = resolveAdvertisedPort;
|
|
1042
|
-
exports.selectViewerEndpoints = selectViewerEndpoints;
|
|
1043
1328
|
exports.shouldAdvertiseIpv6 = shouldAdvertiseIpv6;
|