dsh-mobile 0.6.15 → 0.6.16
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 +16 -0
- package/CONTRIBUTORS.md +12 -2
- package/README.en.md +8 -6
- package/README.md +8 -6
- package/SECURITY.md +1 -0
- package/apps/mobile/README.md +29 -5
- package/apps/mobile/README.zh-CN.md +29 -5
- package/apps/mobile/release.json +3 -3
- package/docs/ATTACH_EXISTING_FRPS.en.md +8 -0
- package/docs/ATTACH_EXISTING_FRPS.md +8 -0
- package/lib/client.js +840 -59
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +4 -0
- package/lib/index.mjs +45 -10
- package/lib/index.mjs.map +1 -1
- package/package.json +7 -2
- package/packages/question-fixes/package.json +1 -1
package/lib/index.d.mts
CHANGED
|
@@ -394,6 +394,8 @@ interface ResolvedGatewayConfig {
|
|
|
394
394
|
readonly excludedClientModules: readonly string[];
|
|
395
395
|
readonly websocketCompression: ResolvedWebSocketCompressionConfig;
|
|
396
396
|
readonly instanceId: string;
|
|
397
|
+
/** Plugin-wide installation identity, disclosed only after native authentication. */
|
|
398
|
+
readonly installationId?: string;
|
|
397
399
|
/** Identity returned only for authenticated legacy native credentials without a stored binding. */
|
|
398
400
|
readonly legacyInstanceId?: string;
|
|
399
401
|
readonly pairingCaFile?: string;
|
|
@@ -1093,6 +1095,8 @@ export declare class MobileAccessGateway {
|
|
|
1093
1095
|
private handleRenew;
|
|
1094
1096
|
/** CA-backed entries retain their pin; public entries project the authenticated credential's identity. */
|
|
1095
1097
|
private nativeResponseInstanceId;
|
|
1098
|
+
/** Older Apps require exact wire keys; expose this hint only to explicit opt-in clients after authentication. */
|
|
1099
|
+
private nativeResponseInstallationIdentity;
|
|
1096
1100
|
private handleNativePair;
|
|
1097
1101
|
private handleNativeRenew;
|
|
1098
1102
|
private handleNativeProbe;
|
package/lib/index.mjs
CHANGED
|
@@ -4911,6 +4911,26 @@ function renderLoginScript(locale) {
|
|
|
4911
4911
|
`;
|
|
4912
4912
|
}
|
|
4913
4913
|
//#endregion
|
|
4914
|
+
//#region src/browser-ui-locale.ts
|
|
4915
|
+
const UI_LOCALE_COOKIE = "dsh_ma_ui_locale";
|
|
4916
|
+
/** Read presentation data only; malformed or ambiguous cookies keep browser negotiation. */
|
|
4917
|
+
function readBrowserUiLocale(cookieHeader) {
|
|
4918
|
+
if (cookieHeader === void 0 || cookieHeader.length > 8192) return void 0;
|
|
4919
|
+
let locale;
|
|
4920
|
+
for (const part of cookieHeader.split(";")) {
|
|
4921
|
+
const separator = part.indexOf("=");
|
|
4922
|
+
if ((separator === -1 ? part : part.slice(0, separator)).trim() !== UI_LOCALE_COOKIE) continue;
|
|
4923
|
+
const value = separator === -1 ? "" : part.slice(separator + 1).trim();
|
|
4924
|
+
if (locale !== void 0 || value !== "en" && value !== "zh" && value !== "it") return void 0;
|
|
4925
|
+
locale = value;
|
|
4926
|
+
}
|
|
4927
|
+
return locale;
|
|
4928
|
+
}
|
|
4929
|
+
/** Prefer the last DSH display language over the browser's default language. */
|
|
4930
|
+
function resolveBrowserUiLocale(cookieHeader, acceptLanguage) {
|
|
4931
|
+
return readBrowserUiLocale(cookieHeader) ?? resolveAuthPageLocale(acceptLanguage);
|
|
4932
|
+
}
|
|
4933
|
+
//#endregion
|
|
4914
4934
|
//#region src/gateway.ts
|
|
4915
4935
|
const MAX_CONTROL_BODY_BYTES = 16384;
|
|
4916
4936
|
const MAX_HEADER_BYTES = 16384;
|
|
@@ -6257,12 +6277,17 @@ var MobileAccessGateway = class {
|
|
|
6257
6277
|
nativeResponseInstanceId(result) {
|
|
6258
6278
|
return this.config.pairingCaFile === void 0 ? result.nativeInstanceId ?? this.config.instanceId : this.config.instanceId;
|
|
6259
6279
|
}
|
|
6280
|
+
/** Older Apps require exact wire keys; expose this hint only to explicit opt-in clients after authentication. */
|
|
6281
|
+
nativeResponseInstallationIdentity(request) {
|
|
6282
|
+
return this.config.installationId === void 0 || request.headers["x-dsh-mobile-capabilities"] !== "installation-id" ? {} : { installationId: this.config.installationId };
|
|
6283
|
+
}
|
|
6260
6284
|
async handleNativePair(request, response) {
|
|
6261
6285
|
const body = await readJsonObject(request, MAX_CONTROL_BODY_BYTES);
|
|
6262
6286
|
if (typeof body.token !== "string" || body.label !== void 0 && typeof body.label !== "string") throw new HttpError(400, "bad_request");
|
|
6263
6287
|
const result = await this.access.pair(request.socket.remoteAddress ?? "unknown", body.token, body.label);
|
|
6264
6288
|
sendJson(response, 201, {
|
|
6265
6289
|
instanceId: this.nativeResponseInstanceId(result),
|
|
6290
|
+
...this.nativeResponseInstallationIdentity(request),
|
|
6266
6291
|
deviceId: result.deviceId,
|
|
6267
6292
|
deviceToken: result.deviceToken,
|
|
6268
6293
|
deviceExpiresAt: result.deviceExpiresAt,
|
|
@@ -6283,6 +6308,7 @@ var MobileAccessGateway = class {
|
|
|
6283
6308
|
const result = await this.access.renew(selected.token);
|
|
6284
6309
|
sendJson(response, 200, {
|
|
6285
6310
|
instanceId: this.nativeResponseInstanceId(result),
|
|
6311
|
+
...this.nativeResponseInstallationIdentity(request),
|
|
6286
6312
|
deviceId: result.deviceId,
|
|
6287
6313
|
sessionToken: result.sessionToken,
|
|
6288
6314
|
csrfToken: result.csrfToken,
|
|
@@ -6302,6 +6328,7 @@ var MobileAccessGateway = class {
|
|
|
6302
6328
|
const result = await this.access.probe(selected.token);
|
|
6303
6329
|
sendJson(response, 200, {
|
|
6304
6330
|
instanceId: this.nativeResponseInstanceId(result),
|
|
6331
|
+
...this.nativeResponseInstallationIdentity(request),
|
|
6305
6332
|
deviceId: result.deviceId,
|
|
6306
6333
|
deviceExpiresAt: result.deviceExpiresAt
|
|
6307
6334
|
}, this.tlsEnabled);
|
|
@@ -6390,9 +6417,9 @@ var MobileAccessGateway = class {
|
|
|
6390
6417
|
if (target.search === "" && request.method === "GET" && (target.decodedPathname === `/mobile-access/pair` || target.decodedPathname === `/mobile-access/pair.js`)) {
|
|
6391
6418
|
if (!this.access.pairingStatus().open) throw new HttpError(404, "not_found");
|
|
6392
6419
|
const languageHeader = request.headers["accept-language"];
|
|
6393
|
-
const locale =
|
|
6420
|
+
const locale = resolveBrowserUiLocale(request.headers.cookie, typeof languageHeader === "string" ? languageHeader : void 0);
|
|
6394
6421
|
setSecurityHeaders(response, this.tlsEnabled);
|
|
6395
|
-
response.setHeader("Vary", "Accept-Language");
|
|
6422
|
+
response.setHeader("Vary", "Accept-Language, Cookie");
|
|
6396
6423
|
const body = target.decodedPathname.endsWith(".js") ? renderPairScript(locale) : renderPairPage(locale);
|
|
6397
6424
|
response.writeHead(200, {
|
|
6398
6425
|
"Content-Type": target.decodedPathname.endsWith(".js") ? "text/javascript; charset=utf-8" : "text/html; charset=utf-8",
|
|
@@ -6404,9 +6431,9 @@ var MobileAccessGateway = class {
|
|
|
6404
6431
|
if (request.method === "GET" && (target.decodedPathname === `/mobile-access/login` || target.decodedPathname === `/mobile-access/login.js`)) {
|
|
6405
6432
|
if (target.decodedPathname.endsWith(".js") && target.search !== "") throw new HttpError(400, "bad_request");
|
|
6406
6433
|
const languageHeader = request.headers["accept-language"];
|
|
6407
|
-
const locale =
|
|
6434
|
+
const locale = resolveBrowserUiLocale(request.headers.cookie, typeof languageHeader === "string" ? languageHeader : void 0);
|
|
6408
6435
|
setSecurityHeaders(response, this.tlsEnabled);
|
|
6409
|
-
response.setHeader("Vary", "Accept-Language");
|
|
6436
|
+
response.setHeader("Vary", "Accept-Language, Cookie");
|
|
6410
6437
|
const body = target.decodedPathname.endsWith(".js") ? renderLoginScript(locale) : renderLoginPage(locale);
|
|
6411
6438
|
response.writeHead(200, {
|
|
6412
6439
|
"Content-Type": target.decodedPathname.endsWith(".js") ? "text/javascript; charset=utf-8" : "text/html; charset=utf-8",
|
|
@@ -17670,16 +17697,20 @@ async function loadOrCreateInstallationId(file) {
|
|
|
17670
17697
|
//#endregion
|
|
17671
17698
|
//#region src/gateway-identity.ts
|
|
17672
17699
|
/**
|
|
17673
|
-
*
|
|
17700
|
+
* Attach the authenticated installation identity while retaining existing entry identity rules.
|
|
17674
17701
|
* @param config - Validated gateway configuration containing the legacy identity.
|
|
17675
17702
|
* @param installationId - Confirmed random identity read from this installation's state.
|
|
17676
17703
|
* @param explicitIdentity - Whether the operator supplied the gateway identity.
|
|
17677
17704
|
* @returns Configuration retaining certificate/operator identities or the new and legacy public identities.
|
|
17678
17705
|
*/
|
|
17679
17706
|
function withInstallationIdentity(config, installationId, explicitIdentity) {
|
|
17680
|
-
if (config.pairingCaFile !== void 0 || explicitIdentity) return
|
|
17707
|
+
if (config.pairingCaFile !== void 0 || explicitIdentity) return Object.freeze({
|
|
17708
|
+
...config,
|
|
17709
|
+
installationId
|
|
17710
|
+
});
|
|
17681
17711
|
return Object.freeze({
|
|
17682
17712
|
...config,
|
|
17713
|
+
installationId,
|
|
17683
17714
|
instanceId: installationId,
|
|
17684
17715
|
legacyInstanceId: config.instanceId
|
|
17685
17716
|
});
|
|
@@ -18065,10 +18096,10 @@ async function apply(ctx, config) {
|
|
|
18065
18096
|
const dshVersion = installedDshVersion();
|
|
18066
18097
|
const loaded = await loadSetup(config);
|
|
18067
18098
|
const mobileAccess = createMobileAccessService(ctx);
|
|
18068
|
-
const
|
|
18069
|
-
const upstreamLoginUrl = upstreamAuthenticatedUrl(ctx,
|
|
18070
|
-
const instanceId = await stableInstanceId(loaded,
|
|
18071
|
-
const stateDirectory = dirname(
|
|
18099
|
+
const baseTemplate = loopbackTemplate(loaded, ctx.webServer.port);
|
|
18100
|
+
const upstreamLoginUrl = upstreamAuthenticatedUrl(ctx, baseTemplate.upstreamOrigin);
|
|
18101
|
+
const instanceId = await stableInstanceId(loaded, baseTemplate);
|
|
18102
|
+
const stateDirectory = dirname(baseTemplate.stateFile);
|
|
18072
18103
|
const logFile = await installMobileFileLogger(ctx, stateDirectory);
|
|
18073
18104
|
const logger = ctx.logger("dsh-mobile");
|
|
18074
18105
|
logger.info("logging initialized file=%s", logFile);
|
|
@@ -18091,6 +18122,10 @@ async function apply(ctx, config) {
|
|
|
18091
18122
|
const frpComponent = new FrpComponentManager({ stateDirectory });
|
|
18092
18123
|
await frpComponent.initialize();
|
|
18093
18124
|
const frpIdentity = await loadOrCreateInstallationId(join(stateDirectory, "installation-id"));
|
|
18125
|
+
const template = Object.freeze({
|
|
18126
|
+
...baseTemplate,
|
|
18127
|
+
installationId: frpIdentity
|
|
18128
|
+
});
|
|
18094
18129
|
const remoteInstanceId = loaded.kind === "managed" ? frpIdentity : loaded.config.instanceId ?? frpIdentity;
|
|
18095
18130
|
const frpConfig = new FrpConfigStore(join(remoteDirectory, "frp", "config"), frpProxyName(frpIdentity), stateDirectory);
|
|
18096
18131
|
await frpConfig.initialize();
|