@camstack/system 1.2.357 → 1.2.359
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/auth/user-manager.d.ts +43 -2
- 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 +2 -2
- package/dist/builtins/autotrack/index.mjs +2 -2
- 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 +8 -8
- package/dist/builtins/composer/composer.addon.mjs +8 -8
- 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 +9 -9
- package/dist/builtins/device-manager/device-manager.addon.mjs +9 -9
- 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/admin-account-boot.d.ts +27 -0
- package/dist/builtins/local-auth/auth-schema.d.ts +3 -2
- package/dist/builtins/local-auth/local-auth.addon.js +281 -8
- package/dist/builtins/local-auth/local-auth.addon.mjs +291 -22
- package/dist/builtins/local-network/address-book.d.ts +69 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +44 -32
- package/dist/builtins/local-network/local-network.addon.js +368 -55
- package/dist/builtins/local-network/local-network.addon.mjs +369 -54
- 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-BcRHQBKW.mjs → child-cap-dispatch-Dm1TRDCr.mjs} +2 -2
- package/dist/{child-cap-dispatch-wbIQJctP.js → child-cap-dispatch-t61ohDpb.js} +2 -2
- package/dist/{composition-sources-CeO0K2cl.js → composition-sources-CCU_KizO.js} +1 -1
- package/dist/{composition-sources-BBfqepyT.mjs → composition-sources-CHlfWHl6.mjs} +1 -1
- package/dist/{dist-CFVOy8VT.js → dist-W_X44h-s.js} +669 -121
- package/dist/{dist-B7WRgEZY.mjs → dist-pTfD_M-U.mjs} +628 -122
- package/dist/index.js +6 -141
- package/dist/index.mjs +4 -139
- package/dist/kernel/config-manager.d.ts +1 -1
- package/dist/kernel/config-schema.d.ts +6 -1
- package/dist/{retired-settings-keys-DKRjfz0U.mjs → retired-settings-keys-CK_ifret.mjs} +1 -1
- package/dist/{retired-settings-keys-CRlTzxaS.js → retired-settings-keys-jglEzgWR.js} +1 -1
- package/package.json +3 -3
|
@@ -1,11 +1,235 @@
|
|
|
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-W_X44h-s.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
|
|
16
|
+
*
|
|
17
|
+
* The role flags keep the keys they always had, so a rolled-back hub reads
|
|
18
|
+
* them with the meaning it knew: `viewerBaseUrls` (viewer, many),
|
|
19
|
+
* `notificationBaseUrl` (notifications, D709), `agentBaseUrl` and
|
|
20
|
+
* `signInBaseUrl` (agents / sign-in, D729 amendment). A single-valued role is
|
|
21
|
+
* one URL — at most one address carries it, by construction. `''` / `[]` is
|
|
22
|
+
* automatic. The custom addresses are `customAddresses` (url + label).
|
|
23
|
+
*
|
|
24
|
+
* ## Migration in the read path
|
|
25
|
+
*
|
|
26
|
+
* Before this, a single-valued flag could name an address that was in no row:
|
|
27
|
+
* the notification links' typed custom address (D709) and the two hub-address
|
|
28
|
+
* overrides (D729 amendment) were free URLs. {@link normalizeAddressBook}
|
|
29
|
+
* turns every such flag into a custom address carrying that role, on every
|
|
30
|
+
* read; the next write persists the result. It is the same rule forever, not
|
|
31
|
+
* a one-off: a flagged detected address that disappears (a DHCP move) shows up
|
|
32
|
+
* as a custom row instead of vanishing while it still wins.
|
|
33
|
+
*/
|
|
34
|
+
var CustomAddressListSchema = zod.z.array(zod.z.object({
|
|
35
|
+
baseUrl: zod.z.string(),
|
|
36
|
+
label: zod.z.string()
|
|
37
|
+
}));
|
|
38
|
+
/** The label a flag promoted to a custom address is given. */
|
|
39
|
+
var PROMOTED_LABEL = {
|
|
40
|
+
notifications: "Notification links address",
|
|
41
|
+
agents: "Hub address for agents",
|
|
42
|
+
signIn: "Sign-in address"
|
|
43
|
+
};
|
|
44
|
+
var SINGLE_ROLES = [
|
|
45
|
+
"notifications",
|
|
46
|
+
"agents",
|
|
47
|
+
"signIn"
|
|
48
|
+
];
|
|
49
|
+
function flagOf(value) {
|
|
50
|
+
return value === "" ? null : value;
|
|
51
|
+
}
|
|
52
|
+
/** Parse the stored custom list; a malformed store reads as empty, never throws. */
|
|
53
|
+
function parseCustomAddresses(raw) {
|
|
54
|
+
const parsed = CustomAddressListSchema.safeParse(raw ?? []);
|
|
55
|
+
return parsed.success ? parsed.data : [];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The address book as it stands: stored custom addresses, plus every
|
|
59
|
+
* single-valued flag that names neither a detected address, a custom one nor
|
|
60
|
+
* the app link, promoted to a custom address (see the module doc).
|
|
61
|
+
*/
|
|
62
|
+
function normalizeAddressBook(stored, detectedUrls) {
|
|
63
|
+
const flags = {
|
|
64
|
+
viewer: stored.viewerBaseUrls,
|
|
65
|
+
notifications: flagOf(stored.notificationBaseUrl),
|
|
66
|
+
agents: flagOf(stored.agentBaseUrl),
|
|
67
|
+
signIn: flagOf(stored.signInBaseUrl)
|
|
68
|
+
};
|
|
69
|
+
const custom = [...parseCustomAddresses(stored.customAddresses)];
|
|
70
|
+
for (const role of SINGLE_ROLES) {
|
|
71
|
+
const url = flags[role];
|
|
72
|
+
if (url === null || require_dist.isAppLinkBase(url) || detectedUrls.has(url)) continue;
|
|
73
|
+
if (custom.some((c) => c.baseUrl === url)) continue;
|
|
74
|
+
custom.push({
|
|
75
|
+
baseUrl: url,
|
|
76
|
+
label: PROMOTED_LABEL[role]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
custom,
|
|
81
|
+
flags
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** The stored-settings patch that persists `state`. */
|
|
85
|
+
function addressBookPatch(state) {
|
|
86
|
+
return {
|
|
87
|
+
customAddresses: state.custom,
|
|
88
|
+
viewerBaseUrls: state.flags.viewer,
|
|
89
|
+
notificationBaseUrl: state.flags.notifications ?? "",
|
|
90
|
+
agentBaseUrl: state.flags.agents ?? "",
|
|
91
|
+
signInBaseUrl: state.flags.signIn ?? ""
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function withCustomAddress(state, entry) {
|
|
95
|
+
const custom = state.custom.some((c) => c.baseUrl === entry.baseUrl) ? state.custom.map((c) => c.baseUrl === entry.baseUrl ? entry : c) : [...state.custom, entry];
|
|
96
|
+
return {
|
|
97
|
+
...state,
|
|
98
|
+
custom
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Drop a custom address and every flag it carried (those roles turn automatic). */
|
|
102
|
+
function withoutCustomAddress(state, baseUrl) {
|
|
103
|
+
const clear = (flag) => flag === baseUrl ? null : flag;
|
|
104
|
+
return {
|
|
105
|
+
custom: state.custom.filter((c) => c.baseUrl !== baseUrl),
|
|
106
|
+
flags: {
|
|
107
|
+
viewer: state.flags.viewer.filter((u) => u !== baseUrl),
|
|
108
|
+
notifications: clear(state.flags.notifications),
|
|
109
|
+
agents: clear(state.flags.agents),
|
|
110
|
+
signIn: clear(state.flags.signIn)
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Every role back to automatic for `role`. */
|
|
115
|
+
function withRoleAutomatic(state, role) {
|
|
116
|
+
return role === "viewer" ? {
|
|
117
|
+
...state,
|
|
118
|
+
flags: {
|
|
119
|
+
...state.flags,
|
|
120
|
+
viewer: []
|
|
121
|
+
}
|
|
122
|
+
} : {
|
|
123
|
+
...state,
|
|
124
|
+
flags: {
|
|
125
|
+
...state.flags,
|
|
126
|
+
[role]: null
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Flag or unflag `baseUrl` for `role`. A single-valued role flagged here moves
|
|
132
|
+
* off every other address (it is one URL); unflagging the address that carries
|
|
133
|
+
* it returns the role to automatic. The app link flagged for notifications IS
|
|
134
|
+
* the automatic answer, stored as automatic — one representation. `viewer`
|
|
135
|
+
* toggles inside the set; toggling while automatic starts from the automatic
|
|
136
|
+
* set (`autoViewer`) so the other addresses keep racing.
|
|
137
|
+
*/
|
|
138
|
+
function withRoleFlag(state, role, baseUrl, enabled, autoViewer) {
|
|
139
|
+
if (role === "viewer") {
|
|
140
|
+
const without = (state.flags.viewer.length === 0 ? autoViewer : state.flags.viewer).filter((u) => u !== baseUrl);
|
|
141
|
+
const viewer = enabled ? [...without, baseUrl] : without;
|
|
142
|
+
return {
|
|
143
|
+
...state,
|
|
144
|
+
flags: {
|
|
145
|
+
...state.flags,
|
|
146
|
+
viewer
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (role === "notifications" && require_dist.isAppLinkBase(baseUrl)) return withRoleAutomatic(state, role);
|
|
151
|
+
if (enabled) return {
|
|
152
|
+
...state,
|
|
153
|
+
flags: {
|
|
154
|
+
...state.flags,
|
|
155
|
+
[role]: baseUrl
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
return state.flags[role] === baseUrl ? withRoleAutomatic(state, role) : state;
|
|
159
|
+
}
|
|
160
|
+
/** What a row may be flagged with. */
|
|
161
|
+
function eligibleRoles(endpoint) {
|
|
162
|
+
if (endpoint.kind === "loopback") return [];
|
|
163
|
+
if (endpoint.kind === "app-link") return ["notifications"];
|
|
164
|
+
return [
|
|
165
|
+
"viewer",
|
|
166
|
+
"notifications",
|
|
167
|
+
"agents",
|
|
168
|
+
"signIn"
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
/** A custom address as a palette entry (`kind: 'custom'`). */
|
|
172
|
+
function customEndpoint(entry) {
|
|
173
|
+
return {
|
|
174
|
+
label: entry.label === "" ? "Custom address" : entry.label,
|
|
175
|
+
baseUrl: entry.baseUrl,
|
|
176
|
+
kind: "custom",
|
|
177
|
+
interfaceKind: "other",
|
|
178
|
+
plausible: true,
|
|
179
|
+
plausibleReason: "",
|
|
180
|
+
priority: 200
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The palette a client races: the detected endpoints plus the custom
|
|
185
|
+
* addresses flagged `viewer` (a detected URL is never listed twice).
|
|
186
|
+
*/
|
|
187
|
+
function withViewerCustomEndpoints(detected, state) {
|
|
188
|
+
const known = new Set(detected.map((e) => e.baseUrl));
|
|
189
|
+
const wanted = new Set(state.flags.viewer);
|
|
190
|
+
const extra = state.custom.filter((c) => wanted.has(c.baseUrl) && !known.has(c.baseUrl)).map(customEndpoint);
|
|
191
|
+
return [...detected, ...extra];
|
|
192
|
+
}
|
|
193
|
+
/** Every role's answer — the resolvers from `@camstack/types`, nothing else. */
|
|
194
|
+
function resolveRoles(state, viewerPalette, httpsPalette) {
|
|
195
|
+
return require_dist.resolveHubAddressRoles(withViewerCustomEndpoints(viewerPalette, state), httpsPalette, state.flags);
|
|
196
|
+
}
|
|
197
|
+
function flagsOn(state, baseUrl) {
|
|
198
|
+
const out = [];
|
|
199
|
+
if (state.flags.viewer.includes(baseUrl)) out.push("viewer");
|
|
200
|
+
for (const role of SINGLE_ROLES) if (state.flags[role] === baseUrl) out.push(role);
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
function inUseOn(roles, baseUrl) {
|
|
204
|
+
const out = [];
|
|
205
|
+
if (roles.viewer.resolved.includes(baseUrl)) out.push("viewer");
|
|
206
|
+
for (const role of SINGLE_ROLES) if (roles[role].resolved === baseUrl) out.push(role);
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
/** The Addresses table: app link, detected rows, then custom-only rows. */
|
|
210
|
+
function buildAddressBook(state, inputs) {
|
|
211
|
+
const roles = resolveRoles(state, inputs.rowsPalette, inputs.httpsPalette);
|
|
212
|
+
const customUrls = new Set(state.custom.map((c) => c.baseUrl));
|
|
213
|
+
const detectedUrls = new Set(inputs.rowsPalette.map((e) => e.baseUrl));
|
|
214
|
+
const row = (endpoint, detected) => ({
|
|
215
|
+
...endpoint,
|
|
216
|
+
detected,
|
|
217
|
+
custom: customUrls.has(endpoint.baseUrl),
|
|
218
|
+
flags: endpoint.kind === "app-link" ? [] : flagsOn(state, endpoint.baseUrl),
|
|
219
|
+
inUse: endpoint.kind === "app-link" ? roles.notifications.resolved === "https://camstack.zentik.app" ? ["notifications"] : [] : inUseOn(roles, endpoint.baseUrl),
|
|
220
|
+
eligible: eligibleRoles(endpoint)
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
rows: [
|
|
224
|
+
row(inputs.appLink, false),
|
|
225
|
+
...inputs.rowsPalette.map((e) => row(e, true)),
|
|
226
|
+
...state.custom.filter((c) => !detectedUrls.has(c.baseUrl)).map((c) => row(customEndpoint(c), false))
|
|
227
|
+
],
|
|
228
|
+
roles,
|
|
229
|
+
appLinkHub: inputs.appLinkHub
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
9
233
|
//#region src/builtins/local-network/ipv6-lan.ts
|
|
10
234
|
/**
|
|
11
235
|
* IPv6 helpers for LAN advertisement: prefix matching (privacy addresses
|
|
@@ -199,55 +423,135 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
199
423
|
bootSeeded: false,
|
|
200
424
|
notificationBaseUrl: "",
|
|
201
425
|
viewerBaseUrls: [],
|
|
426
|
+
customAddresses: [],
|
|
202
427
|
httpEnabled: true,
|
|
203
428
|
httpPort: require_tls.DEFAULT_LAN_HTTP_PORT,
|
|
204
|
-
localHostname: ""
|
|
429
|
+
localHostname: "",
|
|
430
|
+
agentBaseUrl: "",
|
|
431
|
+
signInBaseUrl: ""
|
|
205
432
|
});
|
|
206
433
|
}
|
|
207
434
|
/**
|
|
208
|
-
* The
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* not a hub address: the NC's signed media and buttons keep their own hub
|
|
212
|
-
* ladder when it is flagged; only viewer taps take the app form.
|
|
435
|
+
* The detected palettes, with the mesh reconciled once: `https` (what agents
|
|
436
|
+
* and sign-in resolve over, and the app link's `hub=`) and `http` (what the
|
|
437
|
+
* viewer race resolves over, as before).
|
|
213
438
|
*/
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
439
|
+
async palettes() {
|
|
440
|
+
const port = (await this.resolveAdvertisedListenPort(null)).port;
|
|
441
|
+
await this.reconcileMeshEndpoint();
|
|
442
|
+
return {
|
|
443
|
+
http: this.endpointsNow(false, false, "http", port),
|
|
444
|
+
https: this.endpointsNow(false, false, "https", port)
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/** The stored address book, normalised against what is detected now. */
|
|
448
|
+
addressState(detected) {
|
|
449
|
+
return normalizeAddressBook(this.config, new Set(detected.map((e) => e.baseUrl)));
|
|
450
|
+
}
|
|
451
|
+
/** The role flags alone — they do not depend on what is detected. */
|
|
452
|
+
storedFlags() {
|
|
453
|
+
return normalizeAddressBook(this.config, /* @__PURE__ */ new Set()).flags;
|
|
454
|
+
}
|
|
455
|
+
/** `getAddresses`: the Addresses table and every role's answer. */
|
|
456
|
+
async addressBook(scheme) {
|
|
457
|
+
const { http, https } = await this.palettes();
|
|
458
|
+
return buildAddressBook(this.addressState([...http, ...https]), {
|
|
459
|
+
rowsPalette: scheme === "http" ? http : https,
|
|
460
|
+
httpsPalette: https,
|
|
461
|
+
appLink: appLinkEndpoint(),
|
|
462
|
+
appLinkHub: require_dist.pickAppLinkHub(https)
|
|
463
|
+
});
|
|
217
464
|
}
|
|
218
465
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* what the Network page lists. See `pickAppLinkHub`.
|
|
466
|
+
* Apply one change to the address book and persist it, normalised (a flag
|
|
467
|
+
* naming an address in no row becomes a custom address in the same write).
|
|
222
468
|
*/
|
|
223
|
-
async
|
|
224
|
-
const
|
|
225
|
-
|
|
469
|
+
async changeAddressBook(change) {
|
|
470
|
+
const { http, https } = await this.palettes();
|
|
471
|
+
const detected = [...http, ...https];
|
|
472
|
+
const detectedUrls = new Set(detected.map((e) => e.baseUrl));
|
|
473
|
+
const changed = change(this.addressState(detected), {
|
|
474
|
+
detectedUrls,
|
|
475
|
+
autoViewer: [...new Set(detected.filter(require_dist.isDefaultViewerEndpoint).map((e) => e.baseUrl))]
|
|
476
|
+
});
|
|
477
|
+
const after = normalizeAddressBook({
|
|
478
|
+
...addressBookPatch(changed),
|
|
479
|
+
customAddresses: changed.custom
|
|
480
|
+
}, detectedUrls);
|
|
481
|
+
await this.updateGlobalSettings(addressBookPatch(after));
|
|
482
|
+
}
|
|
483
|
+
/** The deprecated `getHubAddresses` shape, from the role resolvers. */
|
|
484
|
+
async snapshotHubAddresses() {
|
|
485
|
+
const { roles } = await this.addressBook("https");
|
|
486
|
+
const legacy = (r) => ({
|
|
487
|
+
override: r.flagged,
|
|
488
|
+
resolved: r.resolved,
|
|
489
|
+
source: r.source === "flagged" ? "override" : r.source
|
|
490
|
+
});
|
|
491
|
+
return {
|
|
492
|
+
cluster: legacy(roles.agents),
|
|
493
|
+
browser: legacy(roles.signIn)
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Flag / unflag `baseUrl` for `role`, or (`baseUrl: null`) return the role
|
|
498
|
+
* to automatic. Validated: the address must be a row (detected, custom, or
|
|
499
|
+
* the app link for notifications), never loopback, and `signIn` on plain
|
|
500
|
+
* http only when accepted. A refusal stores nothing.
|
|
501
|
+
*/
|
|
502
|
+
async setRole(role, baseUrl, enabled, acceptInsecureHttp) {
|
|
503
|
+
if (baseUrl === null || baseUrl.trim() === "" && role !== "viewer") {
|
|
504
|
+
await this.changeAddressBook((state) => withRoleAutomatic(state, role));
|
|
505
|
+
this.ctx.logger.info("local-network: address role set to automatic", { meta: { role } });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
let target = baseUrl.trim();
|
|
509
|
+
if (enabled) {
|
|
510
|
+
const check = require_dist.checkRoleAddress(role, target, { acceptInsecureHttp });
|
|
511
|
+
if (!check.ok) throw new Error(check.reason);
|
|
512
|
+
target = check.baseUrl;
|
|
513
|
+
}
|
|
514
|
+
await this.changeAddressBook((state, ctx) => {
|
|
515
|
+
const known = require_dist.isAppLinkBase(target) || ctx.detectedUrls.has(target) || state.custom.some((c) => c.baseUrl === target);
|
|
516
|
+
if (enabled && !known && role !== "notifications") throw new Error(`${target} is not in the address list — add it as a custom address first.`);
|
|
517
|
+
return withRoleFlag(state, role, target, enabled, ctx.autoViewer);
|
|
518
|
+
});
|
|
519
|
+
this.ctx.logger.info("local-network: address role set", { meta: {
|
|
520
|
+
role,
|
|
521
|
+
baseUrl: target,
|
|
522
|
+
enabled
|
|
523
|
+
} });
|
|
226
524
|
}
|
|
227
525
|
async notificationEndpointSnapshot() {
|
|
228
|
-
const
|
|
526
|
+
const resolution = require_dist.resolveNotificationsAddress(this.storedFlags());
|
|
527
|
+
const port = (await this.resolveAdvertisedListenPort(null)).port;
|
|
229
528
|
return {
|
|
230
|
-
baseUrl:
|
|
231
|
-
resolved:
|
|
232
|
-
appLinkHub: await this.
|
|
529
|
+
baseUrl: resolution.flagged,
|
|
530
|
+
resolved: resolution.resolved,
|
|
531
|
+
appLinkHub: require_dist.pickAppLinkHub(await this.buildAdvertisedEndpoints(false, false, "https", port))
|
|
233
532
|
};
|
|
234
533
|
}
|
|
235
534
|
async snapshotViewerEndpoints() {
|
|
236
|
-
const
|
|
535
|
+
const resolution = require_dist.resolveViewerAddresses(await this.buildAdvertisedEndpoints(false, false, "http", (await this.resolveAdvertisedListenPort(null)).port), this.storedFlags());
|
|
237
536
|
return {
|
|
238
|
-
baseUrls:
|
|
239
|
-
resolved:
|
|
537
|
+
baseUrls: resolution.flagged,
|
|
538
|
+
resolved: resolution.resolved
|
|
240
539
|
};
|
|
241
540
|
}
|
|
242
541
|
/**
|
|
243
|
-
* Palette shown
|
|
244
|
-
*
|
|
245
|
-
*
|
|
542
|
+
* Palette shown to clients: LAN IPv4, stable LAN IPv6, public, mesh, and
|
|
543
|
+
* the custom addresses flagged `viewer` (`kind: 'custom'`). The viewer
|
|
544
|
+
* HTTP/WS race is a subset (`selectViewerEndpoints`) — IPv6 stays in this
|
|
545
|
+
* list because ICE/WebRTC gathers it regardless.
|
|
246
546
|
*/
|
|
247
547
|
async buildAdvertisedEndpoints(includeLoopback, ipv4Only, scheme, httpsPort) {
|
|
248
|
-
const allow = this.config.allowedAddresses;
|
|
249
|
-
const interfaces = applyAllowlist(this.enumerate(), allow);
|
|
250
548
|
await this.reconcileMeshEndpoint();
|
|
549
|
+
const detected = this.endpointsNow(includeLoopback, ipv4Only, scheme, httpsPort);
|
|
550
|
+
return withViewerCustomEndpoints(detected, this.addressState(detected));
|
|
551
|
+
}
|
|
552
|
+
/** The DETECTED palette from the cached interface/tunnel/mesh state. */
|
|
553
|
+
endpointsNow(includeLoopback, ipv4Only, scheme, httpsPort) {
|
|
554
|
+
const interfaces = applyAllowlist(this.enumerate(), this.config.allowedAddresses);
|
|
251
555
|
const httpState = require_tls.readLanHttpState();
|
|
252
556
|
return buildEndpoints(interfaces, httpsPort, includeLoopback, ipv4Only, this.publicHostname, scheme, this.meshEndpoint, {
|
|
253
557
|
enabled: this.config.httpEnabled !== false,
|
|
@@ -290,20 +594,50 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
290
594
|
},
|
|
291
595
|
getNotificationEndpoint: async () => this.notificationEndpointSnapshot(),
|
|
292
596
|
setNotificationEndpoint: async ({ baseUrl }) => {
|
|
293
|
-
await this.
|
|
294
|
-
this.ctx.logger.info("local-network: notification endpoint set", { meta: { baseUrl: baseUrl ?? "(auto)" } });
|
|
597
|
+
await this.setRole("notifications", baseUrl, true, false);
|
|
295
598
|
return this.notificationEndpointSnapshot();
|
|
296
599
|
},
|
|
297
600
|
getViewerEndpoints: async () => this.snapshotViewerEndpoints(),
|
|
298
601
|
setViewerEndpoints: async ({ baseUrls }) => {
|
|
299
|
-
const
|
|
300
|
-
await this.
|
|
602
|
+
const viewer = [...new Set(baseUrls.filter((u) => u.length > 0))];
|
|
603
|
+
await this.changeAddressBook((state) => ({
|
|
604
|
+
...state,
|
|
605
|
+
flags: {
|
|
606
|
+
...state.flags,
|
|
607
|
+
viewer
|
|
608
|
+
}
|
|
609
|
+
}));
|
|
301
610
|
this.ctx.logger.info("local-network: viewer endpoints set", { meta: {
|
|
302
|
-
count:
|
|
303
|
-
auto:
|
|
611
|
+
count: viewer.length,
|
|
612
|
+
auto: viewer.length === 0
|
|
304
613
|
} });
|
|
305
614
|
return this.snapshotViewerEndpoints();
|
|
306
615
|
},
|
|
616
|
+
getAddresses: async (input) => this.addressBook(input?.scheme ?? "https"),
|
|
617
|
+
addCustomAddress: async ({ baseUrl, label }) => {
|
|
618
|
+
const check = require_dist.validateCustomAddress(baseUrl);
|
|
619
|
+
if (!check.ok) throw new Error(check.reason);
|
|
620
|
+
const entry = {
|
|
621
|
+
baseUrl: check.baseUrl,
|
|
622
|
+
label: label.trim()
|
|
623
|
+
};
|
|
624
|
+
await this.changeAddressBook((state) => withCustomAddress(state, entry));
|
|
625
|
+
this.ctx.logger.info("local-network: custom address added", { meta: {
|
|
626
|
+
baseUrl: entry.baseUrl,
|
|
627
|
+
label: entry.label
|
|
628
|
+
} });
|
|
629
|
+
return this.addressBook("https");
|
|
630
|
+
},
|
|
631
|
+
removeCustomAddress: async ({ baseUrl }) => {
|
|
632
|
+
await this.changeAddressBook((state) => withoutCustomAddress(state, baseUrl));
|
|
633
|
+
this.ctx.logger.info("local-network: custom address removed", { meta: { baseUrl } });
|
|
634
|
+
return this.addressBook("https");
|
|
635
|
+
},
|
|
636
|
+
setAddressRole: async ({ role, baseUrl, enabled, acceptInsecureHttp }) => {
|
|
637
|
+
await this.setRole(role, baseUrl, enabled, acceptInsecureHttp === true);
|
|
638
|
+
return this.addressBook("https");
|
|
639
|
+
},
|
|
640
|
+
getHubAddresses: async () => this.snapshotHubAddresses(),
|
|
307
641
|
getAllowedAddresses: async () => ({ addresses: this.config.allowedAddresses }),
|
|
308
642
|
resetAllowlistToBestMatch: async () => {
|
|
309
643
|
const seed = autoSeedAllowlist(this.enumerate());
|
|
@@ -845,25 +1179,6 @@ function buildEndpoints(interfaces, port, includeLoopback, ipv4Only, publicHostn
|
|
|
845
1179
|
});
|
|
846
1180
|
return out.toSorted((a, b) => a.priority - b.priority);
|
|
847
1181
|
}
|
|
848
|
-
/** AUTO viewer race: LAN IPv4 + the public tunnel. Never IPv6, mesh, or loopback. */
|
|
849
|
-
function isDefaultViewerEndpoint(endpoint) {
|
|
850
|
-
if (endpoint.kind === "lan-ipv4") return true;
|
|
851
|
-
return endpoint.kind === "public" && endpoint.interfaceKind === "public";
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* The URLs the SDK should race for API / viewer access.
|
|
855
|
-
*
|
|
856
|
-
* Empty `viewerBaseUrls` = AUTO (IPv4 + public tunnel). A non-empty list is
|
|
857
|
-
* the operator's pin; if none of those URLs are in the current palette,
|
|
858
|
-
* fall back to AUTO rather than racing an empty set.
|
|
859
|
-
*/
|
|
860
|
-
function selectViewerEndpoints(endpoints, viewerBaseUrls) {
|
|
861
|
-
const auto = endpoints.filter(isDefaultViewerEndpoint);
|
|
862
|
-
if (viewerBaseUrls.length === 0) return auto;
|
|
863
|
-
const wanted = new Set(viewerBaseUrls);
|
|
864
|
-
const explicit = endpoints.filter((e) => wanted.has(e.baseUrl));
|
|
865
|
-
return explicit.length > 0 ? explicit : auto;
|
|
866
|
-
}
|
|
867
1182
|
/**
|
|
868
1183
|
* First-boot heuristic: which addresses should the allowlist start
|
|
869
1184
|
* with? Includes LAN + Wi-Fi IPv4 addresses + plausible IPv6:
|
|
@@ -1002,12 +1317,10 @@ exports.classifyKind = classifyKind;
|
|
|
1002
1317
|
exports.enumerateOsInterfaces = enumerateOsInterfaces;
|
|
1003
1318
|
exports.explainNonPlausible = explainNonPlausible;
|
|
1004
1319
|
exports.ipv6Prefix64Key = ipv6Prefix64Key;
|
|
1005
|
-
exports.isDefaultViewerEndpoint = isDefaultViewerEndpoint;
|
|
1006
1320
|
exports.isPlausibleAutoSeed = isPlausibleAutoSeed;
|
|
1007
1321
|
exports.parseProcIfInet6 = parseProcIfInet6;
|
|
1008
1322
|
exports.pickPreferred = pickPreferred;
|
|
1009
1323
|
exports.prefixLen = prefixLen;
|
|
1010
1324
|
exports.reconcileAllowlist = reconcileAllowlist;
|
|
1011
1325
|
exports.resolveAdvertisedPort = resolveAdvertisedPort;
|
|
1012
|
-
exports.selectViewerEndpoints = selectViewerEndpoints;
|
|
1013
1326
|
exports.shouldAdvertiseIpv6 = shouldAdvertiseIpv6;
|