dsh-mobile 0.4.6 → 0.5.0
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 +22 -0
- package/CONTRIBUTORS.md +10 -1
- package/README.en.md +25 -8
- package/README.md +25 -8
- package/SECURITY.md +1 -1
- package/lib/client.js +326 -13
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +8 -3
- package/lib/index.mjs +293 -75
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js.map +1 -1
- package/lib/mobile-layout.js +123 -21
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +13 -13
package/lib/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { X509Certificate } from "node:crypto";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
4
|
+
import { IncomingMessage } from "node:http";
|
|
4
5
|
import { Readable } from "node:stream";
|
|
5
6
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
6
7
|
import { WebRoute } from "@deepseek-ai/dsh-host-webserver";
|
|
@@ -262,6 +263,8 @@ interface PluginConfig {
|
|
|
262
263
|
mobileLayoutFile?: string;
|
|
263
264
|
/** Standalone browser compatibility bundle used before DSH boot. */
|
|
264
265
|
mobileCompatibilityFile?: string;
|
|
266
|
+
/** Optional application-phase client package ids omitted only from the mobile boot graph. */
|
|
267
|
+
excludedClientModules?: string[];
|
|
265
268
|
/** Stable public discovery identifier; it is not an authentication secret. */
|
|
266
269
|
instanceId?: string;
|
|
267
270
|
/** Managed CA certificate offered to the Android installer after fingerprint binding. */
|
|
@@ -302,6 +305,7 @@ interface ResolvedGatewayConfig {
|
|
|
302
305
|
readonly customScriptFile: string;
|
|
303
306
|
readonly mobileLayoutFile: string;
|
|
304
307
|
readonly mobileCompatibilityFile: string;
|
|
308
|
+
readonly excludedClientModules: readonly string[];
|
|
305
309
|
readonly instanceId: string;
|
|
306
310
|
readonly pairingCaFile?: string;
|
|
307
311
|
readonly tls: TlsConfig;
|
|
@@ -589,8 +593,8 @@ declare class MobileAccessService extends Service {
|
|
|
589
593
|
declare function createMobileAccessService(ctx: Context): MobileAccessService;
|
|
590
594
|
//#endregion
|
|
591
595
|
//#region src/gateway.d.ts
|
|
592
|
-
/**
|
|
593
|
-
declare function rewriteMobileIndex(html: string): string;
|
|
596
|
+
/** Rewrite the dedicated mobile graph; exclusions are opt-in and leave stock DSH unchanged. */
|
|
597
|
+
declare function rewriteMobileIndex(html: string, excludedClientModules?: readonly string[]): string;
|
|
594
598
|
/** Authenticated TLS edge in front of the ordinary loopback-only DSH Web server. */
|
|
595
599
|
declare class MobileAccessGateway {
|
|
596
600
|
readonly config: ResolvedGatewayConfig;
|
|
@@ -616,6 +620,7 @@ declare class MobileAccessGateway {
|
|
|
616
620
|
private mdnsError;
|
|
617
621
|
/** Names of the bundled mobile assets that could not be read, if any. */
|
|
618
622
|
private mobileAssetError;
|
|
623
|
+
private lastModuleExclusionError;
|
|
619
624
|
private bonjour;
|
|
620
625
|
private pairingCaCertificate;
|
|
621
626
|
private listenerPort;
|
|
@@ -752,7 +757,7 @@ declare class MobileAccessGateway {
|
|
|
752
757
|
blockedUpgradePathReport(): BlockedUpgradePathEntry[];
|
|
753
758
|
private handleUpgrade;
|
|
754
759
|
/** Loopback-only DSH WebServer route for opening pairing and managing devices. */
|
|
755
|
-
localAdminRoute(prefix?: string): WebRoute;
|
|
760
|
+
localAdminRoute(prefix?: string, authenticateDesktop?: (request: IncomingMessage) => boolean): WebRoute;
|
|
756
761
|
/** Close listeners and abort all accepted work before resolving teardown. */
|
|
757
762
|
close(): Promise<void>;
|
|
758
763
|
private performClose;
|
package/lib/index.mjs
CHANGED
|
@@ -761,6 +761,7 @@ const Config = z.object({
|
|
|
761
761
|
customScriptFile: z.string().hidden(),
|
|
762
762
|
mobileLayoutFile: z.string().hidden(),
|
|
763
763
|
mobileCompatibilityFile: z.string().hidden(),
|
|
764
|
+
excludedClientModules: z.array(String).default([]),
|
|
764
765
|
instanceId: z.string().hidden(),
|
|
765
766
|
pairingCaFile: z.string().hidden(),
|
|
766
767
|
initiallyEnabled: z.boolean().hidden().required(),
|
|
@@ -797,6 +798,13 @@ function absoluteFile(value, name) {
|
|
|
797
798
|
if (typeof value !== "string" || value.length === 0 || !isAbsolute(value)) throw new Error(`${name} must be an absolute file path`);
|
|
798
799
|
return resolve(value);
|
|
799
800
|
}
|
|
801
|
+
function excludedClientModules(value) {
|
|
802
|
+
if (value === void 0) return Object.freeze([]);
|
|
803
|
+
if (!Array.isArray(value) || value.some((id) => typeof id !== "string" || id.length === 0 || id.trim() !== id)) throw new Error("excludedClientModules must be an array of exact, non-empty client module ids");
|
|
804
|
+
const ids = value;
|
|
805
|
+
if (new Set(ids).size !== ids.length) throw new Error("excludedClientModules must not contain duplicate ids");
|
|
806
|
+
return Object.freeze([...ids]);
|
|
807
|
+
}
|
|
800
808
|
/** Resolve the hidden runtime-control file independently from gateway configuration. */
|
|
801
809
|
function parseControlFile(value) {
|
|
802
810
|
return absoluteFile(value, "controlFile");
|
|
@@ -887,6 +895,7 @@ function parseGatewayConfig(raw) {
|
|
|
887
895
|
customScriptFile: value.customScriptFile === void 0 ? join(dirname(absoluteFile(value.stateFile, "stateFile")), "mobile.js") : absoluteFile(value.customScriptFile, "customScriptFile"),
|
|
888
896
|
mobileLayoutFile: value.mobileLayoutFile === void 0 ? fileURLToPath(new URL("./mobile-layout.js", import.meta.url)) : absoluteFile(value.mobileLayoutFile, "mobileLayoutFile"),
|
|
889
897
|
mobileCompatibilityFile: value.mobileCompatibilityFile === void 0 ? fileURLToPath(new URL("./mobile-compat.js", import.meta.url)) : absoluteFile(value.mobileCompatibilityFile, "mobileCompatibilityFile"),
|
|
898
|
+
excludedClientModules: excludedClientModules(value.excludedClientModules),
|
|
890
899
|
instanceId: value.instanceId === void 0 ? createHash("sha256").update(absoluteFile(value.stateFile, "stateFile")).digest("hex") : /^[a-f\d]{64}$/u.test(value.instanceId) ? value.instanceId : (() => {
|
|
891
900
|
throw new Error("instanceId must be a lowercase SHA-256 value");
|
|
892
901
|
})(),
|
|
@@ -1213,9 +1222,6 @@ var MobileAccessGatewayController = class {
|
|
|
1213
1222
|
return run;
|
|
1214
1223
|
}
|
|
1215
1224
|
};
|
|
1216
|
-
//#endregion
|
|
1217
|
-
//#region src/local-admin-host.ts
|
|
1218
|
-
/** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
|
|
1219
1225
|
function unwrapBrackets(hostname) {
|
|
1220
1226
|
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
1221
1227
|
}
|
|
@@ -1330,7 +1336,8 @@ function setSecurityHeaders(response, tls, framing = "gateway") {
|
|
|
1330
1336
|
"worker-src 'self' blob:",
|
|
1331
1337
|
...proxied ? [PROXIED_FRAME_SRC] : []
|
|
1332
1338
|
].join("; "));
|
|
1333
|
-
|
|
1339
|
+
const microphone = proxied ? "microphone=(self)" : "microphone=()";
|
|
1340
|
+
response.setHeader("Permissions-Policy", `camera=(), ${microphone}, geolocation=(), payment=(), usb=()`);
|
|
1334
1341
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
1335
1342
|
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
1336
1343
|
response.setHeader("X-Frame-Options", proxied ? "SAMEORIGIN" : "DENY");
|
|
@@ -1427,7 +1434,7 @@ function localAuthority(header) {
|
|
|
1427
1434
|
}
|
|
1428
1435
|
}
|
|
1429
1436
|
/** Protect the inner management route from non-loopback and DNS-rebinding callers. */
|
|
1430
|
-
function assertLocalAdminTrust(request, requireBrowserOrigin) {
|
|
1437
|
+
function assertLocalAdminTrust(request, requireBrowserOrigin, authenticateDesktop = () => false) {
|
|
1431
1438
|
if (request.socket.remoteAddress === void 0 || !isLoopbackAddress(request.socket.remoteAddress)) throw new HttpError(403, "forbidden");
|
|
1432
1439
|
const host = localAuthority(request.headers.host);
|
|
1433
1440
|
if (host === void 0 || !isLocalAdminHostname(host.hostname)) throw new HttpError(403, "forbidden");
|
|
@@ -1441,7 +1448,8 @@ function assertLocalAdminTrust(request, requireBrowserOrigin) {
|
|
|
1441
1448
|
if (error instanceof HttpError) throw error;
|
|
1442
1449
|
throw new HttpError(403, "forbidden");
|
|
1443
1450
|
}
|
|
1444
|
-
|
|
1451
|
+
const desktopForward = site === void 0 && request.headers["x-dsh-mobile-desktop-admin"] === "dsh-app://app";
|
|
1452
|
+
if (requireBrowserOrigin && origin === void 0 && !(desktopForward && authenticateDesktop())) throw new HttpError(403, "forbidden");
|
|
1445
1453
|
if (requireBrowserOrigin && site !== void 0 && site !== "same-origin") throw new HttpError(403, "forbidden");
|
|
1446
1454
|
}
|
|
1447
1455
|
//#endregion
|
|
@@ -2614,6 +2622,17 @@ const SIDEBAR_MODULE = "@deepseek-ai/dsh-client-ui-sidebar";
|
|
|
2614
2622
|
const SETTINGS_MODULE = "@deepseek-ai/dsh-client-ui-settings";
|
|
2615
2623
|
const API_GATEWAY_MODULE = "@deepseek-ai/dsh-api-gateway";
|
|
2616
2624
|
const API_REMOTES_MODULE = "@deepseek-ai/dsh-api-remotes";
|
|
2625
|
+
const MOBILE_BOOT_CORE_MODULES = /* @__PURE__ */ new Set([
|
|
2626
|
+
MOBILE_CLIENT_MODULE,
|
|
2627
|
+
MOBILE_LAYOUT_MODULE,
|
|
2628
|
+
CONNECTION_MODULE,
|
|
2629
|
+
RUNTIME_MODULE,
|
|
2630
|
+
RENDERER_MODULE,
|
|
2631
|
+
SIDEBAR_MODULE,
|
|
2632
|
+
SETTINGS_MODULE,
|
|
2633
|
+
API_GATEWAY_MODULE,
|
|
2634
|
+
API_REMOTES_MODULE
|
|
2635
|
+
]);
|
|
2617
2636
|
const MOBILE_LAYOUT_DEPENDENCY_PROFILES = Object.freeze([Object.freeze({
|
|
2618
2637
|
slots: RUNTIME_MODULE,
|
|
2619
2638
|
dependencies: Object.freeze([RUNTIME_MODULE, "@deepseek-ai/dsh-client-ui-theme"])
|
|
@@ -2627,7 +2646,7 @@ const MOBILE_LAYOUT_DEPENDENCY_PROFILES = Object.freeze([Object.freeze({
|
|
|
2627
2646
|
])
|
|
2628
2647
|
})]);
|
|
2629
2648
|
const MOBILE_CSRF_FETCH_BOOTSTRAP = `(()=>{const nativeFetch=window.fetch.bind(window);window.fetch=(input,init)=>{const source=input instanceof Request?input:undefined;const method=String(init?.method??source?.method??'GET').toUpperCase();if(method==='GET'||method==='HEAD')return nativeFetch(input,init);const raw=typeof input==='string'?input:input instanceof URL?input.href:source?.url;if(raw===undefined||new URL(raw,location.href).origin!==location.origin)return nativeFetch(input,init);const headers=new Headers(init?.headers??source?.headers);if(!headers.has(${JSON.stringify(CSRF_HEADER)})){const prefix=${JSON.stringify(`${CSRF_COOKIE}=`)};const token=document.cookie.split(';').map(value=>value.trim()).find(value=>value.startsWith(prefix))?.slice(prefix.length);if(token!==undefined)headers.set(${JSON.stringify(CSRF_HEADER)},token)}return nativeFetch(input,{...init,headers})};})();`;
|
|
2630
|
-
const MOBILE_AUTHENTICATED_TRANSPORT_BOOTSTRAP = `(()=>{if(window.__DSH_TRANSPORT__!==undefined)throw new Error('DSH Mobile cannot replace an existing transport override');window.__DSH_TRANSPORT__={fetch:(input,init)=>window.fetch(input,init),ownsHost:true}})();`;
|
|
2649
|
+
const MOBILE_AUTHENTICATED_TRANSPORT_BOOTSTRAP = `(()=>{if(window.__DSH_TRANSPORT__!==undefined)throw new Error('DSH Mobile cannot replace an existing transport override');window.__DSH_TRANSPORT__={fetch:(input,init)=>window.fetch(input,init),ownsHost:true},window.__DSH_FILE_UPLOAD__={fetch:(input,init)=>window.fetch(input,init)}})();`;
|
|
2631
2650
|
/** Use this page's gateway health, rather than OS internet validation, for DSH reconnects. */
|
|
2632
2651
|
const MOBILE_GATEWAY_REACHABILITY_BOOTSTRAP = `(()=>{
|
|
2633
2652
|
const view=globalThis;
|
|
@@ -2719,13 +2738,49 @@ function revisionedMobileBatchPath(entries) {
|
|
|
2719
2738
|
path: `${MOBILE_BOOT_BATCH_PREFIX}${key}.js`
|
|
2720
2739
|
};
|
|
2721
2740
|
}
|
|
2741
|
+
var ExcludedClientModulesError = class extends Error {
|
|
2742
|
+
constructor(message) {
|
|
2743
|
+
super(`excludedClientModules: ${message}`);
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
/** Validate graph edges before removing application rows from both manifest views. */
|
|
2747
|
+
function excludeMobileBootEntries(entries, batches, excludedIds) {
|
|
2748
|
+
if (excludedIds.length === 0) return;
|
|
2749
|
+
const excluded = new Set(excludedIds);
|
|
2750
|
+
if (excluded.size !== excludedIds.length) throw new ExcludedClientModulesError("duplicate module ids");
|
|
2751
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
2752
|
+
const phases = /* @__PURE__ */ new Map();
|
|
2753
|
+
for (const batch of batches) for (const id of batch.entries) {
|
|
2754
|
+
if (phases.has(id)) throw new ExcludedClientModulesError(`upstream module ${id} belongs to multiple batches`);
|
|
2755
|
+
phases.set(id, batch.phase);
|
|
2756
|
+
}
|
|
2757
|
+
for (const entry of entries) if (!phases.has(entry.id)) throw new ExcludedClientModulesError(`upstream module ${entry.id} belongs to no batch`);
|
|
2758
|
+
for (const id of excluded) {
|
|
2759
|
+
const entry = byId.get(id);
|
|
2760
|
+
if (entry === void 0) throw new ExcludedClientModulesError(`module ${id} is not installed in this DSH client graph`);
|
|
2761
|
+
if (MOBILE_BOOT_CORE_MODULES.has(id) || entry.immediately === true || phases.get(id) !== "application") throw new ExcludedClientModulesError(`module ${id} is required for mobile boot or belongs to a bootstrap batch`);
|
|
2762
|
+
}
|
|
2763
|
+
for (const entry of entries) {
|
|
2764
|
+
if (excluded.has(entry.id)) continue;
|
|
2765
|
+
for (const [field, edges] of [["inject", entry.inject], ["external", entry.external]]) {
|
|
2766
|
+
if (edges !== void 0 && (!Array.isArray(edges) || edges.some((id) => typeof id !== "string"))) throw new ExcludedClientModulesError(`upstream module ${entry.id} has malformed ${field} dependencies`);
|
|
2767
|
+
for (const dependency of edges ?? []) {
|
|
2768
|
+
const packageId = dependency.endsWith("/client") ? dependency.slice(0, -7) : dependency;
|
|
2769
|
+
if (excluded.has(packageId)) throw new ExcludedClientModulesError(`module ${entry.id} still depends on excluded module ${packageId}; exclude the dependent too or keep ${packageId}`);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
entries.splice(0, entries.length, ...entries.filter((entry) => !excluded.has(entry.id)));
|
|
2774
|
+
for (const batch of batches) batch.entries = batch.entries.filter((id) => !excluded.has(id));
|
|
2775
|
+
batches.splice(0, batches.length, ...batches.filter((batch) => batch.entries.length > 0));
|
|
2776
|
+
}
|
|
2722
2777
|
/**
|
|
2723
2778
|
* Parse one upstream boot manifest and rewire the layout entry onto the
|
|
2724
|
-
* gateway-owned layout module.
|
|
2725
|
-
*
|
|
2726
|
-
* gateway, so the
|
|
2779
|
+
* gateway-owned layout module. Optional exclusions remove application entries
|
|
2780
|
+
* and identify every affected batch for reassembly. The dedicated frontend is
|
|
2781
|
+
* authenticated in the gateway, so the layout needs no upstream address.
|
|
2727
2782
|
*/
|
|
2728
|
-
function parseMobileBootPlan(html) {
|
|
2783
|
+
function parseMobileBootPlan(html, excludedClientModules = []) {
|
|
2729
2784
|
const assignment = /(?:window\.__DSH_BOOT__|globalThis\["__DSH_BOOT__"\])\s*=\s*/u.exec(html);
|
|
2730
2785
|
if (assignment?.index === void 0) throw new Error("upstream DSH index has no boot manifest");
|
|
2731
2786
|
const replaceStart = assignment.index;
|
|
@@ -2744,14 +2799,17 @@ function parseMobileBootPlan(html) {
|
|
|
2744
2799
|
layout[0].url = MOBILE_LAYOUT_PATH;
|
|
2745
2800
|
layout[0].rev = `dsh-mobile-layout-${DSH_MOBILE_VERSION}`;
|
|
2746
2801
|
const parsedBatches = parsed.batches;
|
|
2747
|
-
if (parsedBatches === void 0)
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2802
|
+
if (parsedBatches === void 0) {
|
|
2803
|
+
if (excludedClientModules.length > 0) throw new ExcludedClientModulesError("this DSH version has no batched boot manifest; disable exclusions or update DSH");
|
|
2804
|
+
return Object.freeze({
|
|
2805
|
+
parsed,
|
|
2806
|
+
entries,
|
|
2807
|
+
slotProvider: dependencyProfile.slots,
|
|
2808
|
+
assignment: assignment[0],
|
|
2809
|
+
replaceStart,
|
|
2810
|
+
replaceEnd: scriptEnd
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2755
2813
|
if (!Array.isArray(parsedBatches)) throw new Error("upstream DSH boot manifest batches are malformed");
|
|
2756
2814
|
const batches = parsedBatches;
|
|
2757
2815
|
const entryById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
@@ -2763,15 +2821,26 @@ function parseMobileBootPlan(html) {
|
|
|
2763
2821
|
}
|
|
2764
2822
|
if (layoutBatches.length !== 1 || layoutBatches[0]?.phase !== "application") throw new Error("upstream DSH boot manifest has no unique application layout batch");
|
|
2765
2823
|
const layoutBatch = layoutBatches[0];
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2824
|
+
const originalApplicationBatches = batches.filter((batch) => batch.phase === "application").map((batch) => ({
|
|
2825
|
+
...batch,
|
|
2826
|
+
entries: [...batch.entries]
|
|
2827
|
+
}));
|
|
2828
|
+
excludeMobileBootEntries(entries, batches, excludedClientModules);
|
|
2829
|
+
const affectedBatchUrls = new Set(originalApplicationBatches.filter((batch) => batch.entries.some((id) => excludedClientModules.includes(id))).map((batch) => batch.url));
|
|
2830
|
+
const rewrittenBatchEntries = batches.filter((batch) => batch === layoutBatch || batch.phase === "application" && affectedBatchUrls.has(batch.url)).map((batch) => ({
|
|
2831
|
+
batch,
|
|
2832
|
+
entries: [...batch.entries].sort((left, right) => left < right ? -1 : left > right ? 1 : 0).map((id) => {
|
|
2833
|
+
const entry = entryById.get(id);
|
|
2834
|
+
if (entry === void 0 || typeof entry.url !== "string" || typeof entry.rev !== "string") throw new Error("upstream DSH boot manifest batches are malformed");
|
|
2835
|
+
return Object.freeze({
|
|
2836
|
+
id,
|
|
2837
|
+
url: entry.url,
|
|
2838
|
+
rev: entry.rev
|
|
2839
|
+
});
|
|
2840
|
+
})
|
|
2841
|
+
}));
|
|
2842
|
+
const planEntries = rewrittenBatchEntries.find((candidate) => candidate.batch === layoutBatch)?.entries;
|
|
2843
|
+
if (planEntries === void 0) throw new Error("upstream DSH boot manifest lost the application layout batch");
|
|
2775
2844
|
return Object.freeze({
|
|
2776
2845
|
parsed,
|
|
2777
2846
|
entries,
|
|
@@ -2781,7 +2850,9 @@ function parseMobileBootPlan(html) {
|
|
|
2781
2850
|
replaceStart,
|
|
2782
2851
|
replaceEnd: scriptEnd,
|
|
2783
2852
|
layoutBatch,
|
|
2784
|
-
planEntries
|
|
2853
|
+
planEntries,
|
|
2854
|
+
originalApplicationBatches,
|
|
2855
|
+
rewrittenBatchEntries
|
|
2785
2856
|
});
|
|
2786
2857
|
}
|
|
2787
2858
|
/**
|
|
@@ -2840,30 +2911,62 @@ function splitMobileBootBatch(entries, sizes, passThrough) {
|
|
|
2840
2911
|
rows
|
|
2841
2912
|
};
|
|
2842
2913
|
}
|
|
2914
|
+
function escapeMobilePreloadAttribute(value) {
|
|
2915
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
2916
|
+
}
|
|
2917
|
+
function mobileApplicationPreload(url) {
|
|
2918
|
+
return `<link rel="preload" as="script" href="${escapeMobilePreloadAttribute(url)}">`;
|
|
2919
|
+
}
|
|
2920
|
+
function preloadAttribute(tag, name) {
|
|
2921
|
+
const match = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "iu").exec(tag);
|
|
2922
|
+
return match?.[1] ?? match?.[2];
|
|
2923
|
+
}
|
|
2924
|
+
/** Replace only DSH's application-script preloads; bootstrap and unrelated links are untouched. */
|
|
2925
|
+
function rewriteMobileApplicationPreloads(html, replacements) {
|
|
2926
|
+
if (replacements.size === 0) return html;
|
|
2927
|
+
const escaped = new Map([...replacements].map(([url, markup]) => [escapeMobilePreloadAttribute(url), markup]));
|
|
2928
|
+
return html.replace(/<link\b[^>]*>/giu, (tag) => {
|
|
2929
|
+
if (!preloadAttribute(tag, "rel")?.split(/\s+/u).includes("preload") || preloadAttribute(tag, "as") !== "script") return tag;
|
|
2930
|
+
return escaped.get(preloadAttribute(tag, "href") ?? "") ?? tag;
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2843
2933
|
function rewriteMobileIndexWithBatch(html, options = {}) {
|
|
2844
|
-
const plan = parseMobileBootPlan(html);
|
|
2934
|
+
const plan = parseMobileBootPlan(html, options.excludedClientModules);
|
|
2845
2935
|
const remoteSettings = orderAuthenticatedSettings(plan.entries, plan.slotProvider);
|
|
2846
2936
|
let batches = [];
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
const
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2937
|
+
let preloadReplacements = /* @__PURE__ */ new Map();
|
|
2938
|
+
if (plan.batches !== void 0 && plan.rewrittenBatchEntries !== void 0) {
|
|
2939
|
+
const rewrites = plan.rewrittenBatchEntries.map(({ batch, entries }) => ({
|
|
2940
|
+
batch,
|
|
2941
|
+
split: splitMobileBootBatch(entries, options.sizes ?? /* @__PURE__ */ new Map(), options.passThrough ?? /* @__PURE__ */ new Set())
|
|
2942
|
+
}));
|
|
2943
|
+
if (rewrites.reduce((count, item) => count + item.split.plans.length, 0) > MAX_MOBILE_BOOT_BATCHES) throw new Error(`upstream DSH mobile boot requires more than ${String(MAX_MOBILE_BOOT_BATCHES)} merged batches`);
|
|
2944
|
+
for (const { batch, split } of rewrites) {
|
|
2945
|
+
const at = plan.batches.indexOf(batch);
|
|
2946
|
+
if (at < 0) throw new Error("upstream DSH boot manifest lost an application batch");
|
|
2947
|
+
plan.batches.splice(at, 1, ...split.rows);
|
|
2948
|
+
batches = [...batches, ...split.plans];
|
|
2949
|
+
}
|
|
2950
|
+
for (const original of plan.originalApplicationBatches ?? []) {
|
|
2951
|
+
const replacement = rewrites.find((item) => item.batch.url === original.url);
|
|
2952
|
+
preloadReplacements.set(original.url, replacement === void 0 ? plan.batches.some((batch) => batch.url === original.url) ? mobileApplicationPreload(original.url) : "" : replacement.split.rows.map((row) => mobileApplicationPreload(row.url)).join(""));
|
|
2953
|
+
}
|
|
2954
|
+
batches = Object.freeze(batches);
|
|
2853
2955
|
plan.parsed.rev = createHash("sha256").update(JSON.stringify({
|
|
2854
2956
|
entries: plan.entries,
|
|
2855
2957
|
batches: plan.batches
|
|
2856
2958
|
})).digest("hex").slice(0, 16);
|
|
2857
2959
|
}
|
|
2858
2960
|
const replacement = `${MOBILE_GATEWAY_REACHABILITY_BOOTSTRAP}${remoteSettings ? MOBILE_AUTHENTICATED_TRANSPORT_BOOTSTRAP : ""}${MOBILE_CSRF_FETCH_BOOTSTRAP}window.__DSH_MOBILE_FRONTEND__="dedicated";${plan.assignment}${JSON.stringify(plan.parsed)};`;
|
|
2961
|
+
const rewritten = `${html.slice(0, plan.replaceStart)}${replacement}${html.slice(plan.replaceEnd)}`;
|
|
2859
2962
|
return Object.freeze({
|
|
2860
|
-
html: ensureMobileViewport(ensureMobileCompatibility(
|
|
2963
|
+
html: ensureMobileViewport(ensureMobileCompatibility(rewriteMobileApplicationPreloads(rewritten, preloadReplacements))),
|
|
2861
2964
|
batches
|
|
2862
2965
|
});
|
|
2863
2966
|
}
|
|
2864
|
-
/**
|
|
2865
|
-
function rewriteMobileIndex(html) {
|
|
2866
|
-
return rewriteMobileIndexWithBatch(html).html;
|
|
2967
|
+
/** Rewrite the dedicated mobile graph; exclusions are opt-in and leave stock DSH unchanged. */
|
|
2968
|
+
function rewriteMobileIndex(html, excludedClientModules = []) {
|
|
2969
|
+
return rewriteMobileIndexWithBatch(html, { excludedClientModules }).html;
|
|
2867
2970
|
}
|
|
2868
2971
|
var ByteLimitTransform = class extends Transform {
|
|
2869
2972
|
maximum;
|
|
@@ -3307,6 +3410,7 @@ var MobileAccessGateway = class {
|
|
|
3307
3410
|
mdnsError;
|
|
3308
3411
|
/** Names of the bundled mobile assets that could not be read, if any. */
|
|
3309
3412
|
mobileAssetError;
|
|
3413
|
+
lastModuleExclusionError;
|
|
3310
3414
|
bonjour;
|
|
3311
3415
|
pairingCaCertificate;
|
|
3312
3416
|
listenerPort;
|
|
@@ -4276,11 +4380,26 @@ var MobileAccessGateway = class {
|
|
|
4276
4380
|
let body;
|
|
4277
4381
|
try {
|
|
4278
4382
|
const html = Buffer.concat(chunks).toString("utf8");
|
|
4279
|
-
const plan = parseMobileBootPlan(html);
|
|
4280
|
-
const rewritten = rewriteMobileIndexWithBatch(html, plan.
|
|
4383
|
+
const plan = parseMobileBootPlan(html, this.config.excludedClientModules);
|
|
4384
|
+
const rewritten = rewriteMobileIndexWithBatch(html, plan.rewrittenBatchEntries === void 0 ? { excludedClientModules: this.config.excludedClientModules } : {
|
|
4385
|
+
...await this.resolveMobileBootSizes(plan.rewrittenBatchEntries.flatMap((batch) => batch.entries)),
|
|
4386
|
+
excludedClientModules: this.config.excludedClientModules
|
|
4387
|
+
});
|
|
4281
4388
|
for (const batch of rewritten.batches) this.rememberMobileBootBatch(batch);
|
|
4282
4389
|
body = Buffer.from(rewritten.html);
|
|
4283
|
-
} catch {
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
if (error instanceof ExcludedClientModulesError) {
|
|
4392
|
+
const detail = error.message.replace(/[\r\n]/gu, " ").slice(0, 512);
|
|
4393
|
+
if (detail !== this.lastModuleExclusionError) {
|
|
4394
|
+
this.lastModuleExclusionError = detail;
|
|
4395
|
+
process.emitWarning(detail, { code: "DSH_MOBILE_MODULE_EXCLUSION_INVALID" });
|
|
4396
|
+
}
|
|
4397
|
+
sendJson(response, 409, {
|
|
4398
|
+
error: "excluded_client_modules_invalid",
|
|
4399
|
+
detail
|
|
4400
|
+
}, this.tlsEnabled);
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4284
4403
|
throw new HttpError(502, "upstream_unavailable");
|
|
4285
4404
|
}
|
|
4286
4405
|
const headers = sanitizeResponseHeaders(proxied.headers, this.config.upstreamOrigin);
|
|
@@ -4965,14 +5084,14 @@ var MobileAccessGateway = class {
|
|
|
4965
5084
|
}
|
|
4966
5085
|
}
|
|
4967
5086
|
/** Loopback-only DSH WebServer route for opening pairing and managing devices. */
|
|
4968
|
-
localAdminRoute(prefix = LOCAL_ADMIN_PREFIX) {
|
|
5087
|
+
localAdminRoute(prefix = LOCAL_ADMIN_PREFIX, authenticateDesktop) {
|
|
4969
5088
|
return {
|
|
4970
5089
|
kind: "prefix",
|
|
4971
5090
|
path: prefix,
|
|
4972
5091
|
handler: async (request, response) => {
|
|
4973
5092
|
try {
|
|
4974
5093
|
const target = parseRequestTarget(request.url);
|
|
4975
|
-
assertLocalAdminTrust(request, request.method === "POST");
|
|
5094
|
+
assertLocalAdminTrust(request, request.method === "POST", () => authenticateDesktop?.(request) === true);
|
|
4976
5095
|
if (target.search !== "") throw new HttpError(400, "bad_request");
|
|
4977
5096
|
if (request.method === "GET" && target.decodedPathname === `${prefix}/status`) {
|
|
4978
5097
|
sendJson(response, 200, {
|
|
@@ -9389,6 +9508,10 @@ var TaskEventHub = class {
|
|
|
9389
9508
|
};
|
|
9390
9509
|
//#endregion
|
|
9391
9510
|
//#region src/diagnostics.ts
|
|
9511
|
+
/** Recognize the active remote-web-ui boot hook without evaluating or modifying it. */
|
|
9512
|
+
function hasCompetingRemoteChannelBoot(rows) {
|
|
9513
|
+
return rows.some((row) => row.kind === "script" && row.text?.includes("__DSH_REMOTE_CHANNEL_BOOT__") === true);
|
|
9514
|
+
}
|
|
9392
9515
|
const REMOTE_ERROR_GUIDANCE = Object.freeze({
|
|
9393
9516
|
component_missing: "重新安装完整插件包。",
|
|
9394
9517
|
funnel_permission_required: "继续完成 Tailscale Funnel 授权。",
|
|
@@ -9783,6 +9906,7 @@ async function collectConnectionDiagnostics(snapshot, probes = {}) {
|
|
|
9783
9906
|
const remoteProbe = snapshot.remote.provider !== "origin" && snapshot.remote.running && snapshot.remote.state === "ready" && snapshot.remote.origin !== void 0 ? (probes.remote ?? defaultRemoteProbe)(snapshot.remote.origin) : Promise.resolve({ state: "not-applicable" });
|
|
9784
9907
|
const [firewall, remoteObservation] = await Promise.all([(probes.firewall ?? defaultFirewallProbe())(snapshot.lan.port), remoteProbe]);
|
|
9785
9908
|
checks.push(check("versions", "ok", "versions-current", "版本兼容", `插件 ${DSH_MOBILE_VERSION},DSH ${snapshot.dshVersion},Android App 最低 ${MINIMUM_ANDROID_APP_VERSION}。`));
|
|
9909
|
+
if (snapshot.competingRemoteChannelBoot === true) checks.push(check("remote-plugin-conflict", "warning", "competing-remote-channel", "第三方远程插件", "检测到另一套远程请求通道脚本;手机页面可能进入该插件自己的配对流程。", "在电脑端关闭 dsh-remote-web-ui 的远程访问后刷新,再使用 DSH Mobile 当前二维码配对;不要混用两种配对码。"));
|
|
9786
9910
|
if (snapshot.lan.configured === false) checks.push(check("network", "error", "lan-setup-required", "局域网配置", "尚未完成局域网初始化。", "返回局域网页选择网卡并完成配置。"));
|
|
9787
9911
|
else if (snapshot.lan.networkError !== void 0) checks.push(check("network", "error", "network-unavailable", "局域网网卡", "已保存的网卡当前不可用。", "重新运行 dsh-mobile setup。"));
|
|
9788
9912
|
else if (snapshot.lan.configuredInterface !== void 0) {
|
|
@@ -11731,6 +11855,87 @@ async function regularFile(file, expectedBytes) {
|
|
|
11731
11855
|
throw error;
|
|
11732
11856
|
}
|
|
11733
11857
|
}
|
|
11858
|
+
async function installStep(code, operation) {
|
|
11859
|
+
try {
|
|
11860
|
+
return await operation();
|
|
11861
|
+
} catch (error) {
|
|
11862
|
+
if (error instanceof Error && error.message.startsWith("cpolar_")) throw error;
|
|
11863
|
+
throw new Error(code, { cause: error });
|
|
11864
|
+
}
|
|
11865
|
+
}
|
|
11866
|
+
async function pathExists(path) {
|
|
11867
|
+
try {
|
|
11868
|
+
await lstat(path);
|
|
11869
|
+
return true;
|
|
11870
|
+
} catch (error) {
|
|
11871
|
+
if (error.code === "ENOENT") return false;
|
|
11872
|
+
throw error;
|
|
11873
|
+
}
|
|
11874
|
+
}
|
|
11875
|
+
/** Replace only this version's verified binary, retaining the previous directory until promotion succeeds. */
|
|
11876
|
+
async function publishVerifiedCpolarExecutable(extracted, componentRoot, componentStorage, executableName, operations = {}) {
|
|
11877
|
+
if (dirname(componentStorage) !== componentRoot || !inside(componentRoot, componentStorage) || basename(executableName) !== executableName || executableName === "." || executableName === "..") throw new Error("cpolar component storage escaped its private root");
|
|
11878
|
+
const move = operations.move ?? rename;
|
|
11879
|
+
const remove = operations.remove ?? ((path) => rm(path, {
|
|
11880
|
+
recursive: true,
|
|
11881
|
+
force: true
|
|
11882
|
+
}));
|
|
11883
|
+
const candidate = join(componentRoot, `.install-${randomBytes(12).toString("hex")}`);
|
|
11884
|
+
const backup = join(componentRoot, `.previous-${randomBytes(12).toString("hex")}`);
|
|
11885
|
+
let backupContainsPrevious = false;
|
|
11886
|
+
let replacementInstalled = false;
|
|
11887
|
+
try {
|
|
11888
|
+
await mkdir(candidate, {
|
|
11889
|
+
recursive: true,
|
|
11890
|
+
mode: 448
|
|
11891
|
+
});
|
|
11892
|
+
await copyFile(extracted, join(candidate, executableName));
|
|
11893
|
+
await chmod(join(candidate, executableName), 448);
|
|
11894
|
+
if (await pathExists(componentStorage)) {
|
|
11895
|
+
await move(componentStorage, backup);
|
|
11896
|
+
backupContainsPrevious = true;
|
|
11897
|
+
}
|
|
11898
|
+
try {
|
|
11899
|
+
await move(candidate, componentStorage);
|
|
11900
|
+
replacementInstalled = true;
|
|
11901
|
+
} catch (error) {
|
|
11902
|
+
if (backupContainsPrevious) try {
|
|
11903
|
+
await move(backup, componentStorage);
|
|
11904
|
+
backupContainsPrevious = false;
|
|
11905
|
+
} catch (rollbackError) {
|
|
11906
|
+
process.emitWarning(`cpolar previous component remains at ${backup}; automatic rollback failed`, { code: "DSH_MOBILE_CPOLAR_ROLLBACK_FAILED" });
|
|
11907
|
+
throw new Error("cpolar_storage_failed", { cause: new AggregateError([error, rollbackError]) });
|
|
11908
|
+
}
|
|
11909
|
+
throw error;
|
|
11910
|
+
}
|
|
11911
|
+
} finally {
|
|
11912
|
+
try {
|
|
11913
|
+
await remove(candidate);
|
|
11914
|
+
} catch {
|
|
11915
|
+
process.emitWarning(`cpolar temporary component remains at ${candidate}; cleanup failed`, { code: "DSH_MOBILE_CPOLAR_CLEANUP_FAILED" });
|
|
11916
|
+
}
|
|
11917
|
+
if (replacementInstalled && backupContainsPrevious) try {
|
|
11918
|
+
await remove(backup);
|
|
11919
|
+
} catch {
|
|
11920
|
+
process.emitWarning(`cpolar previous component remains at ${backup}; cleanup failed`, { code: "DSH_MOBILE_CPOLAR_CLEANUP_FAILED" });
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
}
|
|
11924
|
+
/** Remove only this install's staging directory without replacing a preceding failure. */
|
|
11925
|
+
async function cleanupCpolarInstallStaging(staging, installFailed, remove = (path) => rm(path, {
|
|
11926
|
+
recursive: true,
|
|
11927
|
+
force: true
|
|
11928
|
+
})) {
|
|
11929
|
+
try {
|
|
11930
|
+
await remove(staging);
|
|
11931
|
+
} catch (error) {
|
|
11932
|
+
if (installFailed) {
|
|
11933
|
+
process.emitWarning(`cpolar staging directory remains at ${staging}; cleanup failed`, { code: "DSH_MOBILE_CPOLAR_CLEANUP_FAILED" });
|
|
11934
|
+
return;
|
|
11935
|
+
}
|
|
11936
|
+
throw new Error("cpolar_storage_failed", { cause: error });
|
|
11937
|
+
}
|
|
11938
|
+
}
|
|
11734
11939
|
async function run(file, args) {
|
|
11735
11940
|
await new Promise((resolveRun, reject) => {
|
|
11736
11941
|
execFile(file, [...args], {
|
|
@@ -11885,11 +12090,14 @@ var CpolarComponentManager = class {
|
|
|
11885
12090
|
return this.enqueue(async () => {
|
|
11886
12091
|
const release = this.release;
|
|
11887
12092
|
if (release === void 0) throw new Error("cpolar_component_unsupported");
|
|
11888
|
-
await
|
|
11889
|
-
|
|
11890
|
-
|
|
12093
|
+
const staging = await installStep("cpolar_storage_failed", async () => {
|
|
12094
|
+
await mkdir(this.stagingRoot, {
|
|
12095
|
+
recursive: true,
|
|
12096
|
+
mode: 448
|
|
12097
|
+
});
|
|
12098
|
+
return mkdtemp(join(this.stagingRoot, "install-"));
|
|
11891
12099
|
});
|
|
11892
|
-
|
|
12100
|
+
let installFailed = false;
|
|
11893
12101
|
try {
|
|
11894
12102
|
const controller = new AbortController();
|
|
11895
12103
|
const timeout = setTimeout(() => {
|
|
@@ -11898,38 +12106,35 @@ var CpolarComponentManager = class {
|
|
|
11898
12106
|
timeout.unref();
|
|
11899
12107
|
let bytes;
|
|
11900
12108
|
try {
|
|
11901
|
-
bytes = await this.fetchArtifact(release.downloadUrl, controller.signal);
|
|
12109
|
+
bytes = await installStep("cpolar_download_failed", () => this.fetchArtifact(release.downloadUrl, controller.signal));
|
|
12110
|
+
} catch (error) {
|
|
12111
|
+
if (controller.signal.aborted) throw new Error("cpolar_download_timeout", { cause: error });
|
|
12112
|
+
throw error;
|
|
11902
12113
|
} finally {
|
|
11903
12114
|
clearTimeout(timeout);
|
|
11904
12115
|
}
|
|
11905
12116
|
if (createHash("sha256").update(bytes).digest("hex") !== release.downloadSha256) throw new Error("cpolar_download_hash_mismatch");
|
|
11906
12117
|
const archive = join(staging, release.archiveKind === "tar.gz" ? "cpolar.tar.gz" : "cpolar.zip");
|
|
11907
|
-
await writeFile(archive, bytes, {
|
|
12118
|
+
await installStep("cpolar_storage_failed", () => writeFile(archive, bytes, {
|
|
11908
12119
|
flag: "wx",
|
|
11909
12120
|
mode: 384
|
|
11910
|
-
});
|
|
11911
|
-
await this.extractArtifact(archive, staging);
|
|
12121
|
+
}));
|
|
12122
|
+
await installStep("cpolar_extract_failed", () => this.extractArtifact(archive, staging));
|
|
11912
12123
|
const extracted = join(staging, release.executableName);
|
|
11913
|
-
if (!await regularFile(extracted, release.executableBytes)
|
|
11914
|
-
|
|
11915
|
-
await mkdir(candidate, {
|
|
11916
|
-
recursive: true,
|
|
11917
|
-
mode: 448
|
|
11918
|
-
});
|
|
11919
|
-
await copyFile(extracted, join(candidate, release.executableName));
|
|
11920
|
-
await chmod(join(candidate, release.executableName), 448);
|
|
11921
|
-
await rm(this.componentStorage, {
|
|
11922
|
-
recursive: true,
|
|
11923
|
-
force: true
|
|
11924
|
-
});
|
|
11925
|
-
await rename(candidate, this.componentStorage);
|
|
12124
|
+
if (!await installStep("cpolar_extract_failed", async () => await regularFile(extracted, release.executableBytes) && await sha256(extracted) === release.executableSha256)) throw new Error("cpolar_executable_hash_mismatch");
|
|
12125
|
+
await installStep("cpolar_storage_failed", () => publishVerifiedCpolarExecutable(extracted, this.componentRoot, this.componentStorage, release.executableName));
|
|
11926
12126
|
this.installed = true;
|
|
11927
12127
|
this.errorCode = void 0;
|
|
12128
|
+
} catch (error) {
|
|
12129
|
+
installFailed = true;
|
|
12130
|
+
try {
|
|
12131
|
+
this.installed = await regularFile(this.executable, release.executableBytes) && await sha256(this.executable) === release.executableSha256;
|
|
12132
|
+
} catch (_inspectionError) {
|
|
12133
|
+
this.installed = false;
|
|
12134
|
+
}
|
|
12135
|
+
throw error;
|
|
11928
12136
|
} finally {
|
|
11929
|
-
await
|
|
11930
|
-
recursive: true,
|
|
11931
|
-
force: true
|
|
11932
|
-
});
|
|
12137
|
+
await cleanupCpolarInstallStaging(staging, installFailed);
|
|
11933
12138
|
}
|
|
11934
12139
|
});
|
|
11935
12140
|
}
|
|
@@ -13103,6 +13308,7 @@ async function apply(ctx, config) {
|
|
|
13103
13308
|
} : void 0;
|
|
13104
13309
|
return collectConnectionDiagnostics({
|
|
13105
13310
|
dshVersion,
|
|
13311
|
+
competingRemoteChannelBoot: hasCompetingRemoteChannelBoot(ctx.webServer.collectIndexInjections?.() ?? []),
|
|
13106
13312
|
lan: {
|
|
13107
13313
|
configured: loaded.kind !== "unconfigured" || preparedLanSetup !== void 0,
|
|
13108
13314
|
running: lanController.isRunning(),
|
|
@@ -13126,13 +13332,17 @@ async function apply(ctx, config) {
|
|
|
13126
13332
|
}
|
|
13127
13333
|
}, pinnedFrpProbe === void 0 ? {} : { remote: pinnedFrpProbe });
|
|
13128
13334
|
};
|
|
13335
|
+
const authenticateDesktopAdmin = (request) => {
|
|
13336
|
+
const connection = ctx.connection;
|
|
13337
|
+
return typeof connection?.requestRejection === "function" && connection.requestRejection(request) === void 0;
|
|
13338
|
+
};
|
|
13129
13339
|
const adminRoute = {
|
|
13130
13340
|
kind: "prefix",
|
|
13131
13341
|
path: LOCAL_ADMIN_PREFIX,
|
|
13132
13342
|
handler: async (request, response) => {
|
|
13133
13343
|
try {
|
|
13134
13344
|
const target = parseRequestTarget(request.url);
|
|
13135
|
-
assertLocalAdminTrust(request, request.method === "POST");
|
|
13345
|
+
assertLocalAdminTrust(request, request.method === "POST", () => authenticateDesktopAdmin(request));
|
|
13136
13346
|
if (target.search !== "") throw new HttpError(400, "bad_request");
|
|
13137
13347
|
const lanControl = target.decodedPathname === `/api/mobile-access/control` || target.decodedPathname === `/api/mobile-access/lan/control`;
|
|
13138
13348
|
if (request.method === "GET" && lanControl) {
|
|
@@ -13221,7 +13431,15 @@ async function apply(ctx, config) {
|
|
|
13221
13431
|
}
|
|
13222
13432
|
if (request.method === "POST" && target.decodedPathname === `/api/mobile-access/remote/cpolar/component/install`) {
|
|
13223
13433
|
if ((await readJsonObject(request, 4096)).confirm !== true) throw new HttpError(400, "bad_request");
|
|
13224
|
-
|
|
13434
|
+
logger.info("cpolar component install started");
|
|
13435
|
+
try {
|
|
13436
|
+
await remoteProviders.mutate(async () => cpolarComponent.install());
|
|
13437
|
+
logger.info("cpolar component install completed");
|
|
13438
|
+
} catch (error) {
|
|
13439
|
+
const cause = error instanceof Error && error.cause instanceof Error ? error.cause : void 0;
|
|
13440
|
+
logger.error("cpolar component install failed: %s%s", error instanceof Error ? error.stack ?? error.message : String(error), cause === void 0 ? "" : `; cause: ${cause.message}`);
|
|
13441
|
+
throw error;
|
|
13442
|
+
}
|
|
13225
13443
|
sendJson(response, 200, remotePayload(), false);
|
|
13226
13444
|
return;
|
|
13227
13445
|
}
|
|
@@ -13464,18 +13682,18 @@ async function apply(ctx, config) {
|
|
|
13464
13682
|
if (target.decodedPathname.startsWith(`/api/mobile-access/remote/`)) {
|
|
13465
13683
|
const active = remoteController().gateway();
|
|
13466
13684
|
if (active === void 0) throw new HttpError(409, "gateway_stopped");
|
|
13467
|
-
await active.localAdminRoute(`${LOCAL_ADMIN_PREFIX}/remote
|
|
13685
|
+
await active.localAdminRoute(`${LOCAL_ADMIN_PREFIX}/remote`, authenticateDesktopAdmin).handler(request, response);
|
|
13468
13686
|
return;
|
|
13469
13687
|
}
|
|
13470
13688
|
if (target.decodedPathname.startsWith(`/api/mobile-access/lan/`)) {
|
|
13471
13689
|
const active = lanGateway;
|
|
13472
13690
|
if (active === void 0) throw new HttpError(409, "gateway_stopped");
|
|
13473
|
-
await active.localAdminRoute(`${LOCAL_ADMIN_PREFIX}/lan
|
|
13691
|
+
await active.localAdminRoute(`${LOCAL_ADMIN_PREFIX}/lan`, authenticateDesktopAdmin).handler(request, response);
|
|
13474
13692
|
return;
|
|
13475
13693
|
}
|
|
13476
13694
|
const active = lanGateway;
|
|
13477
13695
|
if (active === void 0) throw new HttpError(409, "gateway_stopped");
|
|
13478
|
-
await active.localAdminRoute().handler(request, response);
|
|
13696
|
+
await active.localAdminRoute(LOCAL_ADMIN_PREFIX, authenticateDesktopAdmin).handler(request, response);
|
|
13479
13697
|
} catch (error) {
|
|
13480
13698
|
const mapped = mapAdminError(error);
|
|
13481
13699
|
if (response.headersSent) response.destroy();
|