dsh-mobile 0.4.3 → 0.4.5
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 +15 -0
- package/CONTRIBUTORS.md +55 -0
- package/README.en.md +16 -9
- package/README.md +16 -10
- package/SECURITY.md +2 -2
- package/lib/client.js +104 -7
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +16 -1
- package/lib/index.mjs +127 -37
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-compat.js +1 -1
- package/lib/mobile-compat.js.map +1 -1
- package/package.json +22 -21
package/lib/index.d.mts
CHANGED
|
@@ -597,6 +597,7 @@ declare class MobileAccessGateway {
|
|
|
597
597
|
private readonly upstreamAuthenticatedUrl?;
|
|
598
598
|
private readonly extraWebSocketPaths?;
|
|
599
599
|
private readonly blockedUpgradeLog?;
|
|
600
|
+
private readonly onDiscoveryDegraded?;
|
|
600
601
|
readonly access: AccessController;
|
|
601
602
|
private readonly listenerTlsEnabled;
|
|
602
603
|
private readonly tlsEnabled;
|
|
@@ -611,6 +612,7 @@ declare class MobileAccessGateway {
|
|
|
611
612
|
* it, "the phone cannot find this computer" has no diagnosable cause on the host.
|
|
612
613
|
*/
|
|
613
614
|
private discoveryError;
|
|
615
|
+
private mdnsError;
|
|
614
616
|
/** Names of the bundled mobile assets that could not be read, if any. */
|
|
615
617
|
private mobileAssetError;
|
|
616
618
|
private bonjour;
|
|
@@ -644,10 +646,13 @@ declare class MobileAccessGateway {
|
|
|
644
646
|
private readonly probeLimiter;
|
|
645
647
|
constructor(config: ResolvedGatewayConfig, store: DeviceStore, extensions?: MobileAccessService | undefined, upstreamAuthenticatedUrl?: string | undefined, extraWebSocketPaths?: {
|
|
646
648
|
has(pathname: string): boolean;
|
|
647
|
-
} | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined);
|
|
649
|
+
} | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined);
|
|
648
650
|
/** Initialize durable state, validate TLS, and bind the externally reachable listener. */
|
|
649
651
|
start(): Promise<void>;
|
|
650
652
|
private startDiscovery;
|
|
653
|
+
private reportDiscoveryDegraded;
|
|
654
|
+
private recordBroadcastFailure;
|
|
655
|
+
private recordMdnsFailure;
|
|
651
656
|
private discoveryAnnouncement;
|
|
652
657
|
private closeFailedStart;
|
|
653
658
|
private closeBonjour;
|
|
@@ -668,6 +673,7 @@ declare class MobileAccessGateway {
|
|
|
668
673
|
readonly broadcast: boolean;
|
|
669
674
|
readonly mdns: boolean;
|
|
670
675
|
readonly errorCode?: string;
|
|
676
|
+
readonly mdnsErrorCode?: string;
|
|
671
677
|
readonly mobileAssetsErrorCode?: string;
|
|
672
678
|
};
|
|
673
679
|
private requirePolicy;
|
|
@@ -1416,6 +1422,15 @@ declare class TaskEventHub {
|
|
|
1416
1422
|
}
|
|
1417
1423
|
//#endregion
|
|
1418
1424
|
//#region src/plugin.d.ts
|
|
1425
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
1426
|
+
interface MessageSourceMap {
|
|
1427
|
+
'plugin:dsh-mobile': {
|
|
1428
|
+
kind: 'plugin:dsh-mobile';
|
|
1429
|
+
form: 'notice';
|
|
1430
|
+
summary: string;
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1419
1434
|
/** Stable Cordis plugin name. */
|
|
1420
1435
|
declare const name = "dsh-mobile";
|
|
1421
1436
|
/** The stock WebServer serves the control card; Connection authenticates the loopback DSH origin. */
|
package/lib/index.mjs
CHANGED
|
@@ -2451,6 +2451,16 @@ const MOBILE_BOOT_SIZE_PROBE_HEADER = "x-dsh-mobile-size-probe";
|
|
|
2451
2451
|
const MAX_MOBILE_BOOT_BATCHES = 8;
|
|
2452
2452
|
const MOBILE_BOOT_UPSTREAM_ATTEMPTS = 4;
|
|
2453
2453
|
const MOBILE_BOOT_RETRY_DELAY_MS = 150;
|
|
2454
|
+
function upstreamPluginBundleUrl(source, upstreamOrigin) {
|
|
2455
|
+
if (!source.startsWith("/plugins/") && !source.startsWith("plugins/") || source.includes("#")) return void 0;
|
|
2456
|
+
try {
|
|
2457
|
+
const target = new URL(source, new URL("/", upstreamOrigin));
|
|
2458
|
+
if (target.origin !== upstreamOrigin.origin || !target.pathname.startsWith("/plugins/")) return void 0;
|
|
2459
|
+
return target;
|
|
2460
|
+
} catch {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2454
2464
|
const TRANSIENT_UPSTREAM_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2455
2465
|
"EAI_AGAIN",
|
|
2456
2466
|
"ECONNABORTED",
|
|
@@ -2870,8 +2880,9 @@ function shouldCompressResponse(request, response) {
|
|
|
2870
2880
|
const pathname = request.url?.split("?", 1)[0] ?? "";
|
|
2871
2881
|
return (request.method === "GET" && (pathname.startsWith("/plugins/") || pathname.startsWith("/assets/")) || request.method === "POST" && pathname === SESSION_HISTORY_PATH) && response.statusCode === 200 && request.headers.range === void 0 && response.headers["content-range"] === void 0 && response.headers["content-encoding"] === void 0 && acceptsGzip(request.headers["accept-encoding"]) && isCompressibleContentType(response.headers["content-type"]);
|
|
2872
2882
|
}
|
|
2873
|
-
function revisionedStaticCacheControl(request) {
|
|
2883
|
+
function revisionedStaticCacheControl(request, statusCode) {
|
|
2874
2884
|
if (request.method !== "GET" && request.method !== "HEAD") return void 0;
|
|
2885
|
+
if (statusCode !== 200) return void 0;
|
|
2875
2886
|
let target;
|
|
2876
2887
|
try {
|
|
2877
2888
|
target = new URL(request.url ?? "/", "https://dsh-mobile.invalid");
|
|
@@ -2991,6 +3002,19 @@ function discoveryBroadcastTargets(cidrs) {
|
|
|
2991
3002
|
}
|
|
2992
3003
|
return [...targets];
|
|
2993
3004
|
}
|
|
3005
|
+
/** Keep Node's upgrade-owned socket safe after the HTTP parser removes its listener. */
|
|
3006
|
+
function guardUpgradeSocket(socket) {
|
|
3007
|
+
socket.on("error", () => {
|
|
3008
|
+
if (!socket.destroyed) socket.destroy();
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
function discoveryFailure(error) {
|
|
3012
|
+
const code = error instanceof Error && typeof error.code === "string" ? error.code : "unknown";
|
|
3013
|
+
return Object.freeze({
|
|
3014
|
+
code,
|
|
3015
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
2994
3018
|
function extensionTarget(pathname) {
|
|
2995
3019
|
const prefix = `${AUTH_PREFIX}/extensions`;
|
|
2996
3020
|
if (pathname === prefix || pathname === `${prefix}/` || pathname === `${prefix}/manifest`) return { kind: "manifest" };
|
|
@@ -3088,6 +3112,7 @@ var MobileAccessGateway = class {
|
|
|
3088
3112
|
upstreamAuthenticatedUrl;
|
|
3089
3113
|
extraWebSocketPaths;
|
|
3090
3114
|
blockedUpgradeLog;
|
|
3115
|
+
onDiscoveryDegraded;
|
|
3091
3116
|
access;
|
|
3092
3117
|
listenerTlsEnabled;
|
|
3093
3118
|
tlsEnabled;
|
|
@@ -3102,6 +3127,7 @@ var MobileAccessGateway = class {
|
|
|
3102
3127
|
* it, "the phone cannot find this computer" has no diagnosable cause on the host.
|
|
3103
3128
|
*/
|
|
3104
3129
|
discoveryError;
|
|
3130
|
+
mdnsError;
|
|
3105
3131
|
/** Names of the bundled mobile assets that could not be read, if any. */
|
|
3106
3132
|
mobileAssetError;
|
|
3107
3133
|
bonjour;
|
|
@@ -3133,12 +3159,13 @@ var MobileAccessGateway = class {
|
|
|
3133
3159
|
removeExtensionContentListener;
|
|
3134
3160
|
renewLimiter;
|
|
3135
3161
|
probeLimiter;
|
|
3136
|
-
constructor(config, store, extensions, upstreamAuthenticatedUrl, extraWebSocketPaths, blockedUpgradeLog) {
|
|
3162
|
+
constructor(config, store, extensions, upstreamAuthenticatedUrl, extraWebSocketPaths, blockedUpgradeLog, onDiscoveryDegraded) {
|
|
3137
3163
|
this.config = config;
|
|
3138
3164
|
this.extensions = extensions;
|
|
3139
3165
|
this.upstreamAuthenticatedUrl = upstreamAuthenticatedUrl;
|
|
3140
3166
|
this.extraWebSocketPaths = extraWebSocketPaths;
|
|
3141
3167
|
this.blockedUpgradeLog = blockedUpgradeLog;
|
|
3168
|
+
this.onDiscoveryDegraded = onDiscoveryDegraded;
|
|
3142
3169
|
this.listenerTlsEnabled = config.tls.mode === "provided";
|
|
3143
3170
|
this.tlsEnabled = config.publicTls;
|
|
3144
3171
|
this.access = new AccessController(store, {
|
|
@@ -3215,9 +3242,11 @@ var MobileAccessGateway = class {
|
|
|
3215
3242
|
socket.destroy();
|
|
3216
3243
|
});
|
|
3217
3244
|
server.on("upgrade", (request, socket, head) => {
|
|
3218
|
-
|
|
3245
|
+
const client = socket;
|
|
3246
|
+
guardUpgradeSocket(client);
|
|
3247
|
+
this.handleUpgrade(request, client, head).catch((error) => {
|
|
3219
3248
|
const mapped = mapError(error);
|
|
3220
|
-
rejectUpgrade(
|
|
3249
|
+
rejectUpgrade(client, mapped.status, mapped.code);
|
|
3221
3250
|
});
|
|
3222
3251
|
});
|
|
3223
3252
|
server.on("clientError", (_error, socket) => {
|
|
@@ -3252,33 +3281,56 @@ var MobileAccessGateway = class {
|
|
|
3252
3281
|
const socket = createSocket("udp4");
|
|
3253
3282
|
this.discoverySocket = socket;
|
|
3254
3283
|
const announcement = this.discoveryAnnouncement(port);
|
|
3284
|
+
let binding = true;
|
|
3285
|
+
socket.on("error", (error) => {
|
|
3286
|
+
if (!binding) this.recordBroadcastFailure(error, true);
|
|
3287
|
+
});
|
|
3288
|
+
const sendAnnouncement = (targetPort, address) => {
|
|
3289
|
+
if (this.closing || this.discoveryError !== void 0) return;
|
|
3290
|
+
try {
|
|
3291
|
+
socket.send(announcement, targetPort, address, (error) => {
|
|
3292
|
+
if (error !== null) this.recordBroadcastFailure(error, true);
|
|
3293
|
+
});
|
|
3294
|
+
} catch (error) {
|
|
3295
|
+
this.recordBroadcastFailure(error, true);
|
|
3296
|
+
}
|
|
3297
|
+
};
|
|
3255
3298
|
socket.on("message", (message, remote) => {
|
|
3256
|
-
if (this.closing || !message.equals(DISCOVERY_QUERY) || !addressAllowed(remote.address, this.config.allowedCidrs)) return;
|
|
3257
|
-
|
|
3299
|
+
if (this.closing || this.discoveryError !== void 0 || !message.equals(DISCOVERY_QUERY) || !addressAllowed(remote.address, this.config.allowedCidrs)) return;
|
|
3300
|
+
sendAnnouncement(remote.port, remote.address);
|
|
3258
3301
|
});
|
|
3259
3302
|
const bindHost = isIP(this.config.listenHost) === 4 && isLoopbackAddress(this.config.listenHost) ? this.config.listenHost : "0.0.0.0";
|
|
3260
3303
|
if (await new Promise((resolve) => {
|
|
3261
3304
|
const onBindError = (error) => {
|
|
3262
3305
|
socket.off("error", onBindError);
|
|
3263
|
-
this.
|
|
3264
|
-
code: typeof error.code === "string" ? error.code : "unknown",
|
|
3265
|
-
message: typeof error.message === "string" ? error.message : String(error)
|
|
3266
|
-
});
|
|
3306
|
+
this.recordBroadcastFailure(error, false);
|
|
3267
3307
|
resolve(false);
|
|
3268
3308
|
};
|
|
3269
3309
|
socket.once("error", onBindError);
|
|
3270
|
-
|
|
3271
|
-
socket.
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3310
|
+
try {
|
|
3311
|
+
socket.bind(port, bindHost, () => {
|
|
3312
|
+
socket.off("error", onBindError);
|
|
3313
|
+
binding = false;
|
|
3314
|
+
try {
|
|
3315
|
+
socket.setBroadcast(true);
|
|
3316
|
+
resolve(true);
|
|
3317
|
+
} catch (error) {
|
|
3318
|
+
this.recordBroadcastFailure(error, true);
|
|
3319
|
+
resolve(false);
|
|
3320
|
+
}
|
|
3321
|
+
});
|
|
3322
|
+
} catch (error) {
|
|
3323
|
+
onBindError(error);
|
|
3324
|
+
}
|
|
3325
|
+
}) && this.discoveryError === void 0) {
|
|
3276
3326
|
const announce = () => {
|
|
3277
|
-
for (const target of discoveryBroadcastTargets(this.config.allowedCidrs))
|
|
3327
|
+
for (const target of discoveryBroadcastTargets(this.config.allowedCidrs)) sendAnnouncement(port, target);
|
|
3278
3328
|
};
|
|
3279
3329
|
announce();
|
|
3280
|
-
this.
|
|
3281
|
-
|
|
3330
|
+
if (this.discoveryError === void 0) {
|
|
3331
|
+
this.discoveryTimer = setInterval(announce, DISCOVERY_INTERVAL_MS);
|
|
3332
|
+
this.discoveryTimer.unref();
|
|
3333
|
+
}
|
|
3282
3334
|
} else {
|
|
3283
3335
|
this.discoverySocket = void 0;
|
|
3284
3336
|
try {
|
|
@@ -3286,8 +3338,12 @@ var MobileAccessGateway = class {
|
|
|
3286
3338
|
} catch {}
|
|
3287
3339
|
}
|
|
3288
3340
|
const deviceName = discoveryDeviceName();
|
|
3289
|
-
const
|
|
3341
|
+
const onMdnsError = (error) => {
|
|
3342
|
+
this.recordMdnsFailure(error);
|
|
3343
|
+
};
|
|
3344
|
+
const bonjour = new Bonjour({ disableIPv6: true }, onMdnsError);
|
|
3290
3345
|
this.bonjour = bonjour;
|
|
3346
|
+
bonjour.server.mdns.on("error", onMdnsError);
|
|
3291
3347
|
bonjour.publish({
|
|
3292
3348
|
name: `${deviceName} (${this.config.instanceId.slice(0, 8)})`,
|
|
3293
3349
|
type: MDNS_SERVICE_TYPE,
|
|
@@ -3303,6 +3359,25 @@ var MobileAccessGateway = class {
|
|
|
3303
3359
|
}
|
|
3304
3360
|
});
|
|
3305
3361
|
}
|
|
3362
|
+
reportDiscoveryDegraded(source, code) {
|
|
3363
|
+
try {
|
|
3364
|
+
this.onDiscoveryDegraded?.(source, code);
|
|
3365
|
+
} catch (error) {
|
|
3366
|
+
process.emitWarning(`DSH Mobile could not log ${source} discovery error: ${String(error)}`, { code: "DSH_MOBILE_DISCOVERY_LOG_FAILED" });
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
recordBroadcastFailure(error, notify) {
|
|
3370
|
+
if (this.closing || this.discoveryError !== void 0) return;
|
|
3371
|
+
this.discoveryError = discoveryFailure(error);
|
|
3372
|
+
if (this.discoveryTimer !== void 0) clearInterval(this.discoveryTimer);
|
|
3373
|
+
this.discoveryTimer = void 0;
|
|
3374
|
+
if (notify) this.reportDiscoveryDegraded("broadcast", this.discoveryError.code);
|
|
3375
|
+
}
|
|
3376
|
+
recordMdnsFailure(error) {
|
|
3377
|
+
if (this.closing || this.mdnsError !== void 0) return;
|
|
3378
|
+
this.mdnsError = discoveryFailure(error);
|
|
3379
|
+
this.reportDiscoveryDegraded("mdns", this.mdnsError.code);
|
|
3380
|
+
}
|
|
3306
3381
|
discoveryAnnouncement(port) {
|
|
3307
3382
|
return Buffer.from(JSON.stringify({
|
|
3308
3383
|
deviceName: discoveryDeviceName(),
|
|
@@ -3360,8 +3435,9 @@ var MobileAccessGateway = class {
|
|
|
3360
3435
|
discoveryStatus() {
|
|
3361
3436
|
return Object.freeze({
|
|
3362
3437
|
broadcast: this.discoverySocket !== void 0 && this.discoveryTimer !== void 0,
|
|
3363
|
-
mdns: this.bonjour !== void 0,
|
|
3438
|
+
mdns: this.bonjour !== void 0 && this.mdnsError === void 0,
|
|
3364
3439
|
...this.discoveryError === void 0 ? {} : { errorCode: `discovery_broadcast_${this.discoveryError.code}` },
|
|
3440
|
+
...this.mdnsError === void 0 ? {} : { mdnsErrorCode: `discovery_mdns_${this.mdnsError.code}` },
|
|
3365
3441
|
...this.mobileAssetError === void 0 ? {} : { mobileAssetsErrorCode: this.mobileAssetError }
|
|
3366
3442
|
});
|
|
3367
3443
|
}
|
|
@@ -4061,7 +4137,7 @@ var MobileAccessGateway = class {
|
|
|
4061
4137
|
async resolveMobileBootSizes(planEntries) {
|
|
4062
4138
|
const sizes = /* @__PURE__ */ new Map();
|
|
4063
4139
|
const passThrough = /* @__PURE__ */ new Set();
|
|
4064
|
-
const candidates = planEntries.filter((entry) => entry.id !== MOBILE_LAYOUT_MODULE && entry.url.
|
|
4140
|
+
const candidates = planEntries.filter((entry) => entry.id !== MOBILE_LAYOUT_MODULE && upstreamPluginBundleUrl(entry.url, this.config.upstreamOrigin) !== void 0);
|
|
4065
4141
|
let cursor = 0;
|
|
4066
4142
|
const worker = async () => {
|
|
4067
4143
|
while (cursor < candidates.length) {
|
|
@@ -4085,11 +4161,10 @@ var MobileAccessGateway = class {
|
|
|
4085
4161
|
* cached for a short window.
|
|
4086
4162
|
*/
|
|
4087
4163
|
async upstreamBundleSize(source) {
|
|
4088
|
-
|
|
4164
|
+
const target = upstreamPluginBundleUrl(source, this.config.upstreamOrigin);
|
|
4165
|
+
if (target === void 0) return void 0;
|
|
4089
4166
|
const cached = this.bootEntrySizeCache.get(source);
|
|
4090
4167
|
if (cached !== void 0 && Date.now() - cached.at < MOBILE_BOOT_SIZE_CACHE_TTL_MS) return cached.size;
|
|
4091
|
-
const target = new URL(source, this.config.upstreamOrigin);
|
|
4092
|
-
if (target.origin !== this.config.upstreamOrigin.origin) return void 0;
|
|
4093
4168
|
const upstreamCookie = await this.upstreamCookieHeader();
|
|
4094
4169
|
let upstreamRequest;
|
|
4095
4170
|
try {
|
|
@@ -4255,9 +4330,8 @@ var MobileAccessGateway = class {
|
|
|
4255
4330
|
}
|
|
4256
4331
|
async readUpstreamClientBundle(source, signal) {
|
|
4257
4332
|
signal.throwIfAborted();
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
if (target.origin !== this.config.upstreamOrigin.origin) throw new HttpError(502, "upstream_unavailable");
|
|
4333
|
+
const target = upstreamPluginBundleUrl(source, this.config.upstreamOrigin);
|
|
4334
|
+
if (target === void 0) throw new HttpError(502, "upstream_unavailable");
|
|
4261
4335
|
let upstreamRequest;
|
|
4262
4336
|
const aborted = () => {
|
|
4263
4337
|
upstreamRequest?.destroy(/* @__PURE__ */ new Error("request aborted"));
|
|
@@ -4403,7 +4477,8 @@ var MobileAccessGateway = class {
|
|
|
4403
4477
|
});
|
|
4404
4478
|
setSecurityHeaders(response, this.tlsEnabled, "proxied");
|
|
4405
4479
|
const headers = sanitizeResponseHeaders(proxied.headers, this.config.upstreamOrigin);
|
|
4406
|
-
const
|
|
4480
|
+
const statusCode = proxied.statusCode ?? 502;
|
|
4481
|
+
const cacheControl = revisionedStaticCacheControl(request$2, statusCode);
|
|
4407
4482
|
if (cacheControl !== void 0) headers["cache-control"] = cacheControl;
|
|
4408
4483
|
const compressed = shouldCompressResponse(request$2, proxied);
|
|
4409
4484
|
if (compressed) {
|
|
@@ -4413,7 +4488,7 @@ var MobileAccessGateway = class {
|
|
|
4413
4488
|
headers["content-encoding"] = "gzip";
|
|
4414
4489
|
addVaryAcceptEncoding(headers);
|
|
4415
4490
|
}
|
|
4416
|
-
response.writeHead(
|
|
4491
|
+
response.writeHead(statusCode, headers);
|
|
4417
4492
|
await Promise.all([bodyDone, compressed ? pipeline(proxied, createGzip(), response) : pipeline(proxied, response)]);
|
|
4418
4493
|
} catch (error) {
|
|
4419
4494
|
holder.request?.destroy();
|
|
@@ -4623,6 +4698,7 @@ var MobileAccessGateway = class {
|
|
|
4623
4698
|
const authorization = this.authorize(request);
|
|
4624
4699
|
if (this.activeWebSockets.size >= this.config.maxWebSockets) throw new HttpError(429, "busy");
|
|
4625
4700
|
const upstreamCookie = await this.upstreamCookieHeader();
|
|
4701
|
+
if (client.destroyed) return;
|
|
4626
4702
|
const upstream = connect({
|
|
4627
4703
|
host: stripIpv6Brackets(this.config.upstreamOrigin.hostname),
|
|
4628
4704
|
port: Number(this.config.upstreamOrigin.port)
|
|
@@ -6721,10 +6797,23 @@ async function attemptDownload(request) {
|
|
|
6721
6797
|
});
|
|
6722
6798
|
const response = first.status >= 300 && first.status < 400 ? await followValidatedRedirect(first, request) : first;
|
|
6723
6799
|
if (!response.ok) throw new Error(`${request.errorPrefix}_download_http_${String(response.status)}`);
|
|
6724
|
-
const
|
|
6725
|
-
if (
|
|
6726
|
-
|
|
6727
|
-
|
|
6800
|
+
const contentLength = response.headers.get("content-length");
|
|
6801
|
+
if (contentLength !== null && (!/^\d+$/u.test(contentLength) || Number(contentLength) !== request.expectedBytes)) throw new Error(`${request.errorPrefix}_download_size_mismatch`);
|
|
6802
|
+
if (response.body === null) throw new Error(`${request.errorPrefix}_download_size_mismatch`);
|
|
6803
|
+
const bytes = new Uint8Array(request.expectedBytes);
|
|
6804
|
+
const reader = response.body.getReader();
|
|
6805
|
+
let received = 0;
|
|
6806
|
+
while (true) {
|
|
6807
|
+
const chunk = await reader.read();
|
|
6808
|
+
if (chunk.done) break;
|
|
6809
|
+
if (chunk.value.byteLength > bytes.byteLength - received) {
|
|
6810
|
+
await reader.cancel();
|
|
6811
|
+
throw new Error(`${request.errorPrefix}_download_size_mismatch`);
|
|
6812
|
+
}
|
|
6813
|
+
bytes.set(chunk.value, received);
|
|
6814
|
+
received += chunk.value.byteLength;
|
|
6815
|
+
}
|
|
6816
|
+
if (received !== request.expectedBytes) throw new Error(`${request.errorPrefix}_download_size_mismatch`);
|
|
6728
6817
|
return bytes;
|
|
6729
6818
|
}
|
|
6730
6819
|
/**
|
|
@@ -11312,7 +11401,9 @@ async function apply(ctx, config) {
|
|
|
11312
11401
|
...candidateConfig,
|
|
11313
11402
|
upstreamOrigin: template.upstreamOrigin.origin
|
|
11314
11403
|
});
|
|
11315
|
-
const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths)
|
|
11404
|
+
const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, (source, code) => {
|
|
11405
|
+
logger.warn("%s discovery degraded while DSH remains available: %s", source, code);
|
|
11406
|
+
});
|
|
11316
11407
|
await candidate.start();
|
|
11317
11408
|
lanGateway = candidate;
|
|
11318
11409
|
const removeTaskSink = taskEventHub.add(candidate);
|
|
@@ -11854,8 +11945,7 @@ async function apply(ctx, config) {
|
|
|
11854
11945
|
text: `${guide}\n\n用户需求:${task}`
|
|
11855
11946
|
}],
|
|
11856
11947
|
source: {
|
|
11857
|
-
kind: "plugin",
|
|
11858
|
-
plugin: "dsh-mobile",
|
|
11948
|
+
kind: "plugin:dsh-mobile",
|
|
11859
11949
|
form: "notice",
|
|
11860
11950
|
summary: boundContextSummary(`/mobile ${task}`)
|
|
11861
11951
|
}
|