dsh-mobile 0.4.6 → 0.4.7
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 +12 -0
- package/CONTRIBUTORS.md +7 -1
- package/README.en.md +23 -8
- package/README.md +23 -8
- package/lib/client.js +63 -1
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +6 -2
- package/lib/index.mjs +156 -36
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js.map +1 -1
- package/lib/mobile-layout.js +75 -20
- package/lib/mobile-layout.js.map +1 -1
- package/package.json +13 -13
package/lib/index.d.mts
CHANGED
|
@@ -262,6 +262,8 @@ interface PluginConfig {
|
|
|
262
262
|
mobileLayoutFile?: string;
|
|
263
263
|
/** Standalone browser compatibility bundle used before DSH boot. */
|
|
264
264
|
mobileCompatibilityFile?: string;
|
|
265
|
+
/** Optional application-phase client package ids omitted only from the mobile boot graph. */
|
|
266
|
+
excludedClientModules?: string[];
|
|
265
267
|
/** Stable public discovery identifier; it is not an authentication secret. */
|
|
266
268
|
instanceId?: string;
|
|
267
269
|
/** Managed CA certificate offered to the Android installer after fingerprint binding. */
|
|
@@ -302,6 +304,7 @@ interface ResolvedGatewayConfig {
|
|
|
302
304
|
readonly customScriptFile: string;
|
|
303
305
|
readonly mobileLayoutFile: string;
|
|
304
306
|
readonly mobileCompatibilityFile: string;
|
|
307
|
+
readonly excludedClientModules: readonly string[];
|
|
305
308
|
readonly instanceId: string;
|
|
306
309
|
readonly pairingCaFile?: string;
|
|
307
310
|
readonly tls: TlsConfig;
|
|
@@ -589,8 +592,8 @@ declare class MobileAccessService extends Service {
|
|
|
589
592
|
declare function createMobileAccessService(ctx: Context): MobileAccessService;
|
|
590
593
|
//#endregion
|
|
591
594
|
//#region src/gateway.d.ts
|
|
592
|
-
/**
|
|
593
|
-
declare function rewriteMobileIndex(html: string): string;
|
|
595
|
+
/** Rewrite the dedicated mobile graph; exclusions are opt-in and leave stock DSH unchanged. */
|
|
596
|
+
declare function rewriteMobileIndex(html: string, excludedClientModules?: readonly string[]): string;
|
|
594
597
|
/** Authenticated TLS edge in front of the ordinary loopback-only DSH Web server. */
|
|
595
598
|
declare class MobileAccessGateway {
|
|
596
599
|
readonly config: ResolvedGatewayConfig;
|
|
@@ -616,6 +619,7 @@ declare class MobileAccessGateway {
|
|
|
616
619
|
private mdnsError;
|
|
617
620
|
/** Names of the bundled mobile assets that could not be read, if any. */
|
|
618
621
|
private mobileAssetError;
|
|
622
|
+
private lastModuleExclusionError;
|
|
619
623
|
private bonjour;
|
|
620
624
|
private pairingCaCertificate;
|
|
621
625
|
private listenerPort;
|
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
|
})(),
|
|
@@ -2614,6 +2623,17 @@ const SIDEBAR_MODULE = "@deepseek-ai/dsh-client-ui-sidebar";
|
|
|
2614
2623
|
const SETTINGS_MODULE = "@deepseek-ai/dsh-client-ui-settings";
|
|
2615
2624
|
const API_GATEWAY_MODULE = "@deepseek-ai/dsh-api-gateway";
|
|
2616
2625
|
const API_REMOTES_MODULE = "@deepseek-ai/dsh-api-remotes";
|
|
2626
|
+
const MOBILE_BOOT_CORE_MODULES = /* @__PURE__ */ new Set([
|
|
2627
|
+
MOBILE_CLIENT_MODULE,
|
|
2628
|
+
MOBILE_LAYOUT_MODULE,
|
|
2629
|
+
CONNECTION_MODULE,
|
|
2630
|
+
RUNTIME_MODULE,
|
|
2631
|
+
RENDERER_MODULE,
|
|
2632
|
+
SIDEBAR_MODULE,
|
|
2633
|
+
SETTINGS_MODULE,
|
|
2634
|
+
API_GATEWAY_MODULE,
|
|
2635
|
+
API_REMOTES_MODULE
|
|
2636
|
+
]);
|
|
2617
2637
|
const MOBILE_LAYOUT_DEPENDENCY_PROFILES = Object.freeze([Object.freeze({
|
|
2618
2638
|
slots: RUNTIME_MODULE,
|
|
2619
2639
|
dependencies: Object.freeze([RUNTIME_MODULE, "@deepseek-ai/dsh-client-ui-theme"])
|
|
@@ -2719,13 +2739,49 @@ function revisionedMobileBatchPath(entries) {
|
|
|
2719
2739
|
path: `${MOBILE_BOOT_BATCH_PREFIX}${key}.js`
|
|
2720
2740
|
};
|
|
2721
2741
|
}
|
|
2742
|
+
var ExcludedClientModulesError = class extends Error {
|
|
2743
|
+
constructor(message) {
|
|
2744
|
+
super(`excludedClientModules: ${message}`);
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
/** Validate graph edges before removing application rows from both manifest views. */
|
|
2748
|
+
function excludeMobileBootEntries(entries, batches, excludedIds) {
|
|
2749
|
+
if (excludedIds.length === 0) return;
|
|
2750
|
+
const excluded = new Set(excludedIds);
|
|
2751
|
+
if (excluded.size !== excludedIds.length) throw new ExcludedClientModulesError("duplicate module ids");
|
|
2752
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
2753
|
+
const phases = /* @__PURE__ */ new Map();
|
|
2754
|
+
for (const batch of batches) for (const id of batch.entries) {
|
|
2755
|
+
if (phases.has(id)) throw new ExcludedClientModulesError(`upstream module ${id} belongs to multiple batches`);
|
|
2756
|
+
phases.set(id, batch.phase);
|
|
2757
|
+
}
|
|
2758
|
+
for (const entry of entries) if (!phases.has(entry.id)) throw new ExcludedClientModulesError(`upstream module ${entry.id} belongs to no batch`);
|
|
2759
|
+
for (const id of excluded) {
|
|
2760
|
+
const entry = byId.get(id);
|
|
2761
|
+
if (entry === void 0) throw new ExcludedClientModulesError(`module ${id} is not installed in this DSH client graph`);
|
|
2762
|
+
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`);
|
|
2763
|
+
}
|
|
2764
|
+
for (const entry of entries) {
|
|
2765
|
+
if (excluded.has(entry.id)) continue;
|
|
2766
|
+
for (const [field, edges] of [["inject", entry.inject], ["external", entry.external]]) {
|
|
2767
|
+
if (edges !== void 0 && (!Array.isArray(edges) || edges.some((id) => typeof id !== "string"))) throw new ExcludedClientModulesError(`upstream module ${entry.id} has malformed ${field} dependencies`);
|
|
2768
|
+
for (const dependency of edges ?? []) {
|
|
2769
|
+
const packageId = dependency.endsWith("/client") ? dependency.slice(0, -7) : dependency;
|
|
2770
|
+
if (excluded.has(packageId)) throw new ExcludedClientModulesError(`module ${entry.id} still depends on excluded module ${packageId}; exclude the dependent too or keep ${packageId}`);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
entries.splice(0, entries.length, ...entries.filter((entry) => !excluded.has(entry.id)));
|
|
2775
|
+
for (const batch of batches) batch.entries = batch.entries.filter((id) => !excluded.has(id));
|
|
2776
|
+
batches.splice(0, batches.length, ...batches.filter((batch) => batch.entries.length > 0));
|
|
2777
|
+
}
|
|
2722
2778
|
/**
|
|
2723
2779
|
* Parse one upstream boot manifest and rewire the layout entry onto the
|
|
2724
|
-
* gateway-owned layout module.
|
|
2725
|
-
*
|
|
2726
|
-
* gateway, so the
|
|
2780
|
+
* gateway-owned layout module. Optional exclusions remove application entries
|
|
2781
|
+
* and identify every affected batch for reassembly. The dedicated frontend is
|
|
2782
|
+
* authenticated in the gateway, so the layout needs no upstream address.
|
|
2727
2783
|
*/
|
|
2728
|
-
function parseMobileBootPlan(html) {
|
|
2784
|
+
function parseMobileBootPlan(html, excludedClientModules = []) {
|
|
2729
2785
|
const assignment = /(?:window\.__DSH_BOOT__|globalThis\["__DSH_BOOT__"\])\s*=\s*/u.exec(html);
|
|
2730
2786
|
if (assignment?.index === void 0) throw new Error("upstream DSH index has no boot manifest");
|
|
2731
2787
|
const replaceStart = assignment.index;
|
|
@@ -2744,14 +2800,17 @@ function parseMobileBootPlan(html) {
|
|
|
2744
2800
|
layout[0].url = MOBILE_LAYOUT_PATH;
|
|
2745
2801
|
layout[0].rev = `dsh-mobile-layout-${DSH_MOBILE_VERSION}`;
|
|
2746
2802
|
const parsedBatches = parsed.batches;
|
|
2747
|
-
if (parsedBatches === void 0)
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2803
|
+
if (parsedBatches === void 0) {
|
|
2804
|
+
if (excludedClientModules.length > 0) throw new ExcludedClientModulesError("this DSH version has no batched boot manifest; disable exclusions or update DSH");
|
|
2805
|
+
return Object.freeze({
|
|
2806
|
+
parsed,
|
|
2807
|
+
entries,
|
|
2808
|
+
slotProvider: dependencyProfile.slots,
|
|
2809
|
+
assignment: assignment[0],
|
|
2810
|
+
replaceStart,
|
|
2811
|
+
replaceEnd: scriptEnd
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2755
2814
|
if (!Array.isArray(parsedBatches)) throw new Error("upstream DSH boot manifest batches are malformed");
|
|
2756
2815
|
const batches = parsedBatches;
|
|
2757
2816
|
const entryById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
@@ -2763,15 +2822,26 @@ function parseMobileBootPlan(html) {
|
|
|
2763
2822
|
}
|
|
2764
2823
|
if (layoutBatches.length !== 1 || layoutBatches[0]?.phase !== "application") throw new Error("upstream DSH boot manifest has no unique application layout batch");
|
|
2765
2824
|
const layoutBatch = layoutBatches[0];
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2825
|
+
const originalApplicationBatches = batches.filter((batch) => batch.phase === "application").map((batch) => ({
|
|
2826
|
+
...batch,
|
|
2827
|
+
entries: [...batch.entries]
|
|
2828
|
+
}));
|
|
2829
|
+
excludeMobileBootEntries(entries, batches, excludedClientModules);
|
|
2830
|
+
const affectedBatchUrls = new Set(originalApplicationBatches.filter((batch) => batch.entries.some((id) => excludedClientModules.includes(id))).map((batch) => batch.url));
|
|
2831
|
+
const rewrittenBatchEntries = batches.filter((batch) => batch === layoutBatch || batch.phase === "application" && affectedBatchUrls.has(batch.url)).map((batch) => ({
|
|
2832
|
+
batch,
|
|
2833
|
+
entries: [...batch.entries].sort((left, right) => left < right ? -1 : left > right ? 1 : 0).map((id) => {
|
|
2834
|
+
const entry = entryById.get(id);
|
|
2835
|
+
if (entry === void 0 || typeof entry.url !== "string" || typeof entry.rev !== "string") throw new Error("upstream DSH boot manifest batches are malformed");
|
|
2836
|
+
return Object.freeze({
|
|
2837
|
+
id,
|
|
2838
|
+
url: entry.url,
|
|
2839
|
+
rev: entry.rev
|
|
2840
|
+
});
|
|
2841
|
+
})
|
|
2842
|
+
}));
|
|
2843
|
+
const planEntries = rewrittenBatchEntries.find((candidate) => candidate.batch === layoutBatch)?.entries;
|
|
2844
|
+
if (planEntries === void 0) throw new Error("upstream DSH boot manifest lost the application layout batch");
|
|
2775
2845
|
return Object.freeze({
|
|
2776
2846
|
parsed,
|
|
2777
2847
|
entries,
|
|
@@ -2781,7 +2851,9 @@ function parseMobileBootPlan(html) {
|
|
|
2781
2851
|
replaceStart,
|
|
2782
2852
|
replaceEnd: scriptEnd,
|
|
2783
2853
|
layoutBatch,
|
|
2784
|
-
planEntries
|
|
2854
|
+
planEntries,
|
|
2855
|
+
originalApplicationBatches,
|
|
2856
|
+
rewrittenBatchEntries
|
|
2785
2857
|
});
|
|
2786
2858
|
}
|
|
2787
2859
|
/**
|
|
@@ -2840,30 +2912,62 @@ function splitMobileBootBatch(entries, sizes, passThrough) {
|
|
|
2840
2912
|
rows
|
|
2841
2913
|
};
|
|
2842
2914
|
}
|
|
2915
|
+
function escapeMobilePreloadAttribute(value) {
|
|
2916
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
2917
|
+
}
|
|
2918
|
+
function mobileApplicationPreload(url) {
|
|
2919
|
+
return `<link rel="preload" as="script" href="${escapeMobilePreloadAttribute(url)}">`;
|
|
2920
|
+
}
|
|
2921
|
+
function preloadAttribute(tag, name) {
|
|
2922
|
+
const match = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "iu").exec(tag);
|
|
2923
|
+
return match?.[1] ?? match?.[2];
|
|
2924
|
+
}
|
|
2925
|
+
/** Replace only DSH's application-script preloads; bootstrap and unrelated links are untouched. */
|
|
2926
|
+
function rewriteMobileApplicationPreloads(html, replacements) {
|
|
2927
|
+
if (replacements.size === 0) return html;
|
|
2928
|
+
const escaped = new Map([...replacements].map(([url, markup]) => [escapeMobilePreloadAttribute(url), markup]));
|
|
2929
|
+
return html.replace(/<link\b[^>]*>/giu, (tag) => {
|
|
2930
|
+
if (!preloadAttribute(tag, "rel")?.split(/\s+/u).includes("preload") || preloadAttribute(tag, "as") !== "script") return tag;
|
|
2931
|
+
return escaped.get(preloadAttribute(tag, "href") ?? "") ?? tag;
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2843
2934
|
function rewriteMobileIndexWithBatch(html, options = {}) {
|
|
2844
|
-
const plan = parseMobileBootPlan(html);
|
|
2935
|
+
const plan = parseMobileBootPlan(html, options.excludedClientModules);
|
|
2845
2936
|
const remoteSettings = orderAuthenticatedSettings(plan.entries, plan.slotProvider);
|
|
2846
2937
|
let batches = [];
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
const
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2938
|
+
let preloadReplacements = /* @__PURE__ */ new Map();
|
|
2939
|
+
if (plan.batches !== void 0 && plan.rewrittenBatchEntries !== void 0) {
|
|
2940
|
+
const rewrites = plan.rewrittenBatchEntries.map(({ batch, entries }) => ({
|
|
2941
|
+
batch,
|
|
2942
|
+
split: splitMobileBootBatch(entries, options.sizes ?? /* @__PURE__ */ new Map(), options.passThrough ?? /* @__PURE__ */ new Set())
|
|
2943
|
+
}));
|
|
2944
|
+
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`);
|
|
2945
|
+
for (const { batch, split } of rewrites) {
|
|
2946
|
+
const at = plan.batches.indexOf(batch);
|
|
2947
|
+
if (at < 0) throw new Error("upstream DSH boot manifest lost an application batch");
|
|
2948
|
+
plan.batches.splice(at, 1, ...split.rows);
|
|
2949
|
+
batches = [...batches, ...split.plans];
|
|
2950
|
+
}
|
|
2951
|
+
for (const original of plan.originalApplicationBatches ?? []) {
|
|
2952
|
+
const replacement = rewrites.find((item) => item.batch.url === original.url);
|
|
2953
|
+
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(""));
|
|
2954
|
+
}
|
|
2955
|
+
batches = Object.freeze(batches);
|
|
2853
2956
|
plan.parsed.rev = createHash("sha256").update(JSON.stringify({
|
|
2854
2957
|
entries: plan.entries,
|
|
2855
2958
|
batches: plan.batches
|
|
2856
2959
|
})).digest("hex").slice(0, 16);
|
|
2857
2960
|
}
|
|
2858
2961
|
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)};`;
|
|
2962
|
+
const rewritten = `${html.slice(0, plan.replaceStart)}${replacement}${html.slice(plan.replaceEnd)}`;
|
|
2859
2963
|
return Object.freeze({
|
|
2860
|
-
html: ensureMobileViewport(ensureMobileCompatibility(
|
|
2964
|
+
html: ensureMobileViewport(ensureMobileCompatibility(rewriteMobileApplicationPreloads(rewritten, preloadReplacements))),
|
|
2861
2965
|
batches
|
|
2862
2966
|
});
|
|
2863
2967
|
}
|
|
2864
|
-
/**
|
|
2865
|
-
function rewriteMobileIndex(html) {
|
|
2866
|
-
return rewriteMobileIndexWithBatch(html).html;
|
|
2968
|
+
/** Rewrite the dedicated mobile graph; exclusions are opt-in and leave stock DSH unchanged. */
|
|
2969
|
+
function rewriteMobileIndex(html, excludedClientModules = []) {
|
|
2970
|
+
return rewriteMobileIndexWithBatch(html, { excludedClientModules }).html;
|
|
2867
2971
|
}
|
|
2868
2972
|
var ByteLimitTransform = class extends Transform {
|
|
2869
2973
|
maximum;
|
|
@@ -3307,6 +3411,7 @@ var MobileAccessGateway = class {
|
|
|
3307
3411
|
mdnsError;
|
|
3308
3412
|
/** Names of the bundled mobile assets that could not be read, if any. */
|
|
3309
3413
|
mobileAssetError;
|
|
3414
|
+
lastModuleExclusionError;
|
|
3310
3415
|
bonjour;
|
|
3311
3416
|
pairingCaCertificate;
|
|
3312
3417
|
listenerPort;
|
|
@@ -4276,11 +4381,26 @@ var MobileAccessGateway = class {
|
|
|
4276
4381
|
let body;
|
|
4277
4382
|
try {
|
|
4278
4383
|
const html = Buffer.concat(chunks).toString("utf8");
|
|
4279
|
-
const plan = parseMobileBootPlan(html);
|
|
4280
|
-
const rewritten = rewriteMobileIndexWithBatch(html, plan.
|
|
4384
|
+
const plan = parseMobileBootPlan(html, this.config.excludedClientModules);
|
|
4385
|
+
const rewritten = rewriteMobileIndexWithBatch(html, plan.rewrittenBatchEntries === void 0 ? { excludedClientModules: this.config.excludedClientModules } : {
|
|
4386
|
+
...await this.resolveMobileBootSizes(plan.rewrittenBatchEntries.flatMap((batch) => batch.entries)),
|
|
4387
|
+
excludedClientModules: this.config.excludedClientModules
|
|
4388
|
+
});
|
|
4281
4389
|
for (const batch of rewritten.batches) this.rememberMobileBootBatch(batch);
|
|
4282
4390
|
body = Buffer.from(rewritten.html);
|
|
4283
|
-
} catch {
|
|
4391
|
+
} catch (error) {
|
|
4392
|
+
if (error instanceof ExcludedClientModulesError) {
|
|
4393
|
+
const detail = error.message.replace(/[\r\n]/gu, " ").slice(0, 512);
|
|
4394
|
+
if (detail !== this.lastModuleExclusionError) {
|
|
4395
|
+
this.lastModuleExclusionError = detail;
|
|
4396
|
+
process.emitWarning(detail, { code: "DSH_MOBILE_MODULE_EXCLUSION_INVALID" });
|
|
4397
|
+
}
|
|
4398
|
+
sendJson(response, 409, {
|
|
4399
|
+
error: "excluded_client_modules_invalid",
|
|
4400
|
+
detail
|
|
4401
|
+
}, this.tlsEnabled);
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4284
4404
|
throw new HttpError(502, "upstream_unavailable");
|
|
4285
4405
|
}
|
|
4286
4406
|
const headers = sanitizeResponseHeaders(proxied.headers, this.config.upstreamOrigin);
|