dsh-mobile 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.en.md +4 -2
- package/README.md +6 -2
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +18 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +18 -0
- package/docs/MOBILE_PLUGIN_BASELINE.en.md +117 -0
- package/docs/MOBILE_PLUGIN_BASELINE.md +117 -0
- package/docs/README.md +1 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +166 -7
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +5 -0
- package/lib/index.mjs +178 -13
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +11 -2
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +3 -1
- package/packages/question-fixes/package.json +1 -1
package/lib/index.d.mts
CHANGED
|
@@ -542,6 +542,11 @@ export declare class MobileAccessGatewayController {
|
|
|
542
542
|
isRunning(): boolean;
|
|
543
543
|
/** Start or stop the runtime and persist only a successfully committed transition. */
|
|
544
544
|
setRunning(running: boolean): Promise<void>;
|
|
545
|
+
/** Change configuration with the runtime stopped, without changing its durable enabled preference. */
|
|
546
|
+
reconfigure<T>(change: () => Promise<T>, restore: () => Promise<void>): Promise<{
|
|
547
|
+
value: T;
|
|
548
|
+
running: boolean;
|
|
549
|
+
}>;
|
|
545
550
|
/** Stop the runtime after earlier transitions without changing the restart preference. */
|
|
546
551
|
close(): Promise<void>;
|
|
547
552
|
private enable;
|
package/lib/index.mjs
CHANGED
|
@@ -1547,6 +1547,15 @@ var ClientModulePreferenceStore = class {
|
|
|
1547
1547
|
};
|
|
1548
1548
|
//#endregion
|
|
1549
1549
|
//#region src/control.ts
|
|
1550
|
+
/** A configuration change failed; recovered distinguishes a restored runtime from an unknown configuration. */
|
|
1551
|
+
var MobileAccessReconfigurationError = class extends Error {
|
|
1552
|
+
recovered;
|
|
1553
|
+
constructor(recovered, cause) {
|
|
1554
|
+
super(recovered ? "mobile access configuration restored" : "mobile access configuration requires recovery", { cause });
|
|
1555
|
+
this.recovered = recovered;
|
|
1556
|
+
this.name = "MobileAccessReconfigurationError";
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1550
1559
|
/** Keeps one runtime aligned with a changing network selection. */
|
|
1551
1560
|
var FollowingMobileAccessRuntime = class {
|
|
1552
1561
|
select;
|
|
@@ -1737,6 +1746,36 @@ var MobileAccessGatewayController = class {
|
|
|
1737
1746
|
else await this.disable();
|
|
1738
1747
|
});
|
|
1739
1748
|
}
|
|
1749
|
+
/** Change configuration with the runtime stopped, without changing its durable enabled preference. */
|
|
1750
|
+
reconfigure(change, restore) {
|
|
1751
|
+
if (this.closing) return Promise.reject(/* @__PURE__ */ new Error("mobile-access control is closing"));
|
|
1752
|
+
return this.enqueue(async () => {
|
|
1753
|
+
if (!this.initialized) throw new Error("mobile-access control is not initialized");
|
|
1754
|
+
const previous = this.runtime;
|
|
1755
|
+
if (previous !== void 0) try {
|
|
1756
|
+
await previous.close();
|
|
1757
|
+
} catch (error) {
|
|
1758
|
+
throw new MobileAccessReconfigurationError(false, error);
|
|
1759
|
+
}
|
|
1760
|
+
this.runtime = void 0;
|
|
1761
|
+
try {
|
|
1762
|
+
const value = await change();
|
|
1763
|
+
if (previous !== void 0) this.runtime = await this.startRuntime();
|
|
1764
|
+
return {
|
|
1765
|
+
value,
|
|
1766
|
+
running: previous !== void 0
|
|
1767
|
+
};
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
try {
|
|
1770
|
+
await restore();
|
|
1771
|
+
if (previous !== void 0) this.runtime = await this.startRuntime();
|
|
1772
|
+
} catch (restoreError) {
|
|
1773
|
+
throw new MobileAccessReconfigurationError(false, new AggregateError([error, restoreError]));
|
|
1774
|
+
}
|
|
1775
|
+
throw new MobileAccessReconfigurationError(true, error);
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1740
1779
|
/** Stop the runtime after earlier transitions without changing the restart preference. */
|
|
1741
1780
|
close() {
|
|
1742
1781
|
if (this.closeTask !== void 0) return this.closeTask;
|
|
@@ -11829,6 +11868,11 @@ async function saveManagedTrustedNetworks(setupFile, values) {
|
|
|
11829
11868
|
await restrictPrivateFile(setupFile);
|
|
11830
11869
|
return next;
|
|
11831
11870
|
}
|
|
11871
|
+
/** Persist an existing managed setup without resetting its identity, paths or extra trusted networks. */
|
|
11872
|
+
async function saveManagedSetup(setupFile, setup) {
|
|
11873
|
+
const validated = parseManagedSetup(setup);
|
|
11874
|
+
await atomicWrite(setupFile, `${JSON.stringify(validated, null, 2)}\n`);
|
|
11875
|
+
}
|
|
11832
11876
|
function privateIpv4(value) {
|
|
11833
11877
|
const parts = value.split(".").map(Number);
|
|
11834
11878
|
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) && (parts[0] === 10 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168);
|
|
@@ -17647,6 +17691,44 @@ async function uninstallVps(serverAddress, uninstall, input, options = {}) {
|
|
|
17647
17691
|
});
|
|
17648
17692
|
}
|
|
17649
17693
|
//#endregion
|
|
17694
|
+
//#region src/lan-rebind.ts
|
|
17695
|
+
/** An unreadable setup is rejected before stopping the listener or replacing any file. */
|
|
17696
|
+
var ManagedLanConfigurationError = class extends Error {
|
|
17697
|
+
constructor(cause) {
|
|
17698
|
+
super("managed LAN configuration is unreadable", { cause });
|
|
17699
|
+
this.name = "ManagedLanConfigurationError";
|
|
17700
|
+
}
|
|
17701
|
+
};
|
|
17702
|
+
/** Rebind an existing listener; callers serialize this call with all other setup-file writes. */
|
|
17703
|
+
async function rebindManagedLan(options) {
|
|
17704
|
+
let previous;
|
|
17705
|
+
try {
|
|
17706
|
+
previous = parseManagedSetup(JSON.parse(await readFile(options.setupFile, "utf8")));
|
|
17707
|
+
await readManagedCa(previous.tls);
|
|
17708
|
+
} catch (error) {
|
|
17709
|
+
throw new ManagedLanConfigurationError(error);
|
|
17710
|
+
}
|
|
17711
|
+
const result = await options.controller.reconfigure(async () => {
|
|
17712
|
+
const next = Object.freeze({
|
|
17713
|
+
...previous,
|
|
17714
|
+
networkInterface: options.network.name
|
|
17715
|
+
});
|
|
17716
|
+
await refreshManagedServerCertificate(next, options.network.address);
|
|
17717
|
+
await saveManagedSetup(options.setupFile, next);
|
|
17718
|
+
options.publish(next);
|
|
17719
|
+
return next;
|
|
17720
|
+
}, async () => {
|
|
17721
|
+
await saveManagedSetup(options.setupFile, previous);
|
|
17722
|
+
options.publish(previous);
|
|
17723
|
+
const network = options.selectPrevious?.(previous) ?? selectLanNetwork(void 0, previous.networkInterface);
|
|
17724
|
+
await refreshManagedServerCertificate(previous, network.address);
|
|
17725
|
+
});
|
|
17726
|
+
return {
|
|
17727
|
+
setup: result.value,
|
|
17728
|
+
running: result.running
|
|
17729
|
+
};
|
|
17730
|
+
}
|
|
17731
|
+
//#endregion
|
|
17650
17732
|
//#region src/funnel.ts
|
|
17651
17733
|
const MAX_PROTOCOL_LINE_BYTES = 16384;
|
|
17652
17734
|
const FUNNEL_START_TIMEOUT_MS = 45e3;
|
|
@@ -23614,6 +23696,9 @@ function locateIrohCompanionRoots(importMetaUrl, environment = process.env) {
|
|
|
23614
23696
|
async function apply(ctx, config) {
|
|
23615
23697
|
const dshVersion = installedDshVersion();
|
|
23616
23698
|
const loaded = await loadSetup(config);
|
|
23699
|
+
/** Rebind overrides the managed setup at runtime; the file on disk is the source of truth on restart. */
|
|
23700
|
+
let managedSetupOverride;
|
|
23701
|
+
let lanRebindRecoveryRequired = false;
|
|
23617
23702
|
const mobileAccess = createMobileAccessService(ctx);
|
|
23618
23703
|
const baseTemplate = loopbackTemplate(loaded, ctx.webServer.port);
|
|
23619
23704
|
const upstreamLoginUrl = upstreamAuthenticatedUrl(ctx, baseTemplate.upstreamOrigin);
|
|
@@ -23910,8 +23995,17 @@ async function apply(ctx, config) {
|
|
|
23910
23995
|
let lanGateway;
|
|
23911
23996
|
let preparedLanSetup;
|
|
23912
23997
|
let trustedNetworksRestartRequired = false;
|
|
23913
|
-
let
|
|
23998
|
+
let lanConfigurationWork = Promise.resolve();
|
|
23914
23999
|
let adminClosing = false;
|
|
24000
|
+
const mutateLan = (operation) => {
|
|
24001
|
+
if (adminClosing) return Promise.reject(new HttpError(503, "unavailable"));
|
|
24002
|
+
const run = lanConfigurationWork.then(() => {
|
|
24003
|
+
if (adminClosing) throw new HttpError(503, "unavailable");
|
|
24004
|
+
return operation();
|
|
24005
|
+
});
|
|
24006
|
+
lanConfigurationWork = run.then(() => {}, () => {});
|
|
24007
|
+
return run;
|
|
24008
|
+
};
|
|
23915
24009
|
const startGateway = async (candidateConfig) => {
|
|
23916
24010
|
const resolved = withInstallationIdentity(parseGatewayConfig({
|
|
23917
24011
|
...candidateConfig,
|
|
@@ -23941,12 +24035,13 @@ async function apply(ctx, config) {
|
|
|
23941
24035
|
if (loaded.kind === "unconfigured") throw new Error(preparedLanSetup === void 0 ? "lan_setup_required" : "lan_setup_restart_required");
|
|
23942
24036
|
if (loaded.kind === "fixed") return startGateway(loaded.config);
|
|
23943
24037
|
const following = new FollowingMobileAccessRuntime(async () => {
|
|
23944
|
-
const
|
|
24038
|
+
const setup = managedSetupOverride ?? loaded.setup;
|
|
24039
|
+
const network = selectLanNetwork(void 0, setup.networkInterface);
|
|
23945
24040
|
return {
|
|
23946
24041
|
key: `${network.name}\0${network.address}\0${network.cidr}`,
|
|
23947
24042
|
start: async () => startGateway({
|
|
23948
24043
|
...loaded.config,
|
|
23949
|
-
...await materializeManagedSetup(
|
|
24044
|
+
...await materializeManagedSetup(setup)
|
|
23950
24045
|
})
|
|
23951
24046
|
};
|
|
23952
24047
|
}, (error) => {
|
|
@@ -24194,6 +24289,8 @@ async function apply(ctx, config) {
|
|
|
24194
24289
|
restartRequired: preparedLanSetup !== void 0
|
|
24195
24290
|
} : {},
|
|
24196
24291
|
running: lanController.isRunning(),
|
|
24292
|
+
rebindSupported: loaded.kind === "managed",
|
|
24293
|
+
...lanRebindRecoveryRequired ? { recoveryRequired: true } : {},
|
|
24197
24294
|
origin: lanGateway?.address().origin,
|
|
24198
24295
|
...preparedLanSetup === void 0 ? {} : { pendingOrigin: preparedLanSetup.origin },
|
|
24199
24296
|
...lanGateway === void 0 ? {} : { extensions: lanGateway.extensionStatus() }
|
|
@@ -24356,13 +24453,10 @@ async function apply(ctx, config) {
|
|
|
24356
24453
|
} catch {
|
|
24357
24454
|
throw new HttpError(400, "lan_trusted_networks_invalid");
|
|
24358
24455
|
}
|
|
24359
|
-
|
|
24360
|
-
if (adminClosing) throw new HttpError(503, "unavailable");
|
|
24456
|
+
await mutateLan(async () => {
|
|
24361
24457
|
await saveManagedTrustedNetworks(loaded.setupFile, values);
|
|
24362
24458
|
trustedNetworksRestartRequired = true;
|
|
24363
24459
|
});
|
|
24364
|
-
trustedNetworksWrite = write.catch(() => void 0);
|
|
24365
|
-
await write;
|
|
24366
24460
|
sendJson(response, 200, await trustedNetworksPayload(), false);
|
|
24367
24461
|
return;
|
|
24368
24462
|
}
|
|
@@ -24391,10 +24485,13 @@ async function apply(ctx, config) {
|
|
|
24391
24485
|
return;
|
|
24392
24486
|
}
|
|
24393
24487
|
if (request.method === "POST" && lanControl) {
|
|
24394
|
-
const
|
|
24395
|
-
if (typeof
|
|
24396
|
-
if (
|
|
24397
|
-
await
|
|
24488
|
+
const running = (await readJsonObject(request, 4096)).running;
|
|
24489
|
+
if (typeof running !== "boolean") throw new HttpError(400, "bad_request");
|
|
24490
|
+
if (running && loaded.kind === "unconfigured") throw new HttpError(409, preparedLanSetup === void 0 ? "lan_setup_required" : "lan_setup_restart_required");
|
|
24491
|
+
await mutateLan(async () => {
|
|
24492
|
+
if (running && lanRebindRecoveryRequired) throw new HttpError(409, "lan_rebind_recovery_required");
|
|
24493
|
+
await lanController.setRunning(running);
|
|
24494
|
+
});
|
|
24398
24495
|
sendJson(response, 200, lanPayload(), false);
|
|
24399
24496
|
return;
|
|
24400
24497
|
}
|
|
@@ -24424,6 +24521,74 @@ async function apply(ctx, config) {
|
|
|
24424
24521
|
sendJson(response, 200, await lanSetupPayload(), false);
|
|
24425
24522
|
return;
|
|
24426
24523
|
}
|
|
24524
|
+
if (request.method === "GET" && target.decodedPathname === `/api/mobile-access/lan/networks`) {
|
|
24525
|
+
await lanConfigurationWork;
|
|
24526
|
+
const networks = availableLanNetworks();
|
|
24527
|
+
const preferred = await preferredLanInterfaceNames();
|
|
24528
|
+
let recommendedAddress;
|
|
24529
|
+
try {
|
|
24530
|
+
recommendedAddress = selectLanNetwork(void 0, void 0, void 0, preferred).address;
|
|
24531
|
+
} catch {}
|
|
24532
|
+
const currentInterface = loaded.kind === "managed" ? (managedSetupOverride ?? loaded.setup).networkInterface : void 0;
|
|
24533
|
+
sendJson(response, 200, {
|
|
24534
|
+
supported: loaded.kind === "managed",
|
|
24535
|
+
currentInterface,
|
|
24536
|
+
listenPort: loaded.kind === "managed" ? loaded.setup.listenPort : 3443,
|
|
24537
|
+
platform: process.platform,
|
|
24538
|
+
networks: networks.map((network) => ({
|
|
24539
|
+
name: network.name,
|
|
24540
|
+
address: network.address,
|
|
24541
|
+
cidr: network.cidr,
|
|
24542
|
+
current: network.name === currentInterface,
|
|
24543
|
+
recommended: network.address === recommendedAddress
|
|
24544
|
+
}))
|
|
24545
|
+
}, false);
|
|
24546
|
+
return;
|
|
24547
|
+
}
|
|
24548
|
+
if (request.method === "POST" && target.decodedPathname === `/api/mobile-access/lan/rebind`) {
|
|
24549
|
+
if (loaded.kind !== "managed") throw new HttpError(409, "lan_rebind_unsupported");
|
|
24550
|
+
const body = await readJsonObject(request, 4096);
|
|
24551
|
+
const networkInterface = typeof body.networkInterface === "string" ? body.networkInterface : "";
|
|
24552
|
+
if (networkInterface === "" || Object.keys(body).some((key) => key !== "networkInterface")) throw new HttpError(400, "bad_request");
|
|
24553
|
+
sendJson(response, 200, await mutateLan(async () => {
|
|
24554
|
+
let network;
|
|
24555
|
+
try {
|
|
24556
|
+
network = selectLanNetwork(void 0, networkInterface);
|
|
24557
|
+
} catch {
|
|
24558
|
+
throw new HttpError(409, "lan_rebind_network_unavailable");
|
|
24559
|
+
}
|
|
24560
|
+
const current = managedSetupOverride ?? loaded.setup;
|
|
24561
|
+
if (!lanRebindRecoveryRequired && current.networkInterface === networkInterface) throw new HttpError(409, "lan_rebind_unchanged");
|
|
24562
|
+
const previousOrigin = lanGateway?.address().origin;
|
|
24563
|
+
try {
|
|
24564
|
+
const result = await rebindManagedLan({
|
|
24565
|
+
setupFile: loaded.setupFile,
|
|
24566
|
+
controller: lanController,
|
|
24567
|
+
network,
|
|
24568
|
+
publish: (setup) => {
|
|
24569
|
+
managedSetupOverride = setup;
|
|
24570
|
+
}
|
|
24571
|
+
});
|
|
24572
|
+
trustedNetworksRestartRequired = false;
|
|
24573
|
+
lanRebindRecoveryRequired = false;
|
|
24574
|
+
return {
|
|
24575
|
+
origin: lanGateway?.address().origin ?? `https://${network.address}:${String(result.setup.listenPort)}`,
|
|
24576
|
+
previousOrigin,
|
|
24577
|
+
restartRequired: false,
|
|
24578
|
+
disconnected: result.running,
|
|
24579
|
+
ready: lanGateway !== void 0
|
|
24580
|
+
};
|
|
24581
|
+
} catch (error) {
|
|
24582
|
+
if (error instanceof ManagedLanConfigurationError) throw new HttpError(409, "lan_rebind_configuration_invalid");
|
|
24583
|
+
if (!(error instanceof MobileAccessReconfigurationError)) throw new HttpError(503, "lan_rebind_unavailable");
|
|
24584
|
+
const recovered = error.recovered;
|
|
24585
|
+
if (!recovered) lanRebindRecoveryRequired = true;
|
|
24586
|
+
logger.error("managed LAN rebind failed; prior configuration restored: %s", String(recovered));
|
|
24587
|
+
throw new HttpError(500, recovered ? "lan_rebind_failed" : "lan_rebind_recovery_required");
|
|
24588
|
+
}
|
|
24589
|
+
}), false);
|
|
24590
|
+
return;
|
|
24591
|
+
}
|
|
24427
24592
|
if (request.method === "GET" && target.decodedPathname === `/api/mobile-access/remote/control`) {
|
|
24428
24593
|
sendJson(response, 200, remotePayload(), false);
|
|
24429
24594
|
return;
|
|
@@ -24942,7 +25107,7 @@ async function apply(ctx, config) {
|
|
|
24942
25107
|
stopAdmin,
|
|
24943
25108
|
drainRemoteWork,
|
|
24944
25109
|
() => releaseManager.dispose(),
|
|
24945
|
-
() =>
|
|
25110
|
+
() => lanConfigurationWork,
|
|
24946
25111
|
disposeMobileCommand,
|
|
24947
25112
|
disposeTaskEvents,
|
|
24948
25113
|
async () => {
|
|
@@ -24965,7 +25130,7 @@ async function apply(ctx, config) {
|
|
|
24965
25130
|
stopAdmin,
|
|
24966
25131
|
drainRemoteWork,
|
|
24967
25132
|
() => releaseManager.dispose(),
|
|
24968
|
-
() =>
|
|
25133
|
+
() => lanConfigurationWork,
|
|
24969
25134
|
disposeMobileCommand,
|
|
24970
25135
|
disposeTaskEvents,
|
|
24971
25136
|
async () => {
|