dsh-mobile 0.6.12 → 0.6.14

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/lib/index.d.mts CHANGED
@@ -540,6 +540,33 @@ interface AuthFailureEntry {
540
540
  readonly suppressed?: number;
541
541
  }
542
542
  //#endregion
543
+ //#region src/http-failure-log.d.ts
544
+ /** Fixed parser details safe to record without Node's raw packet, message or request headers. */
545
+ type HttpParserCode = 'ERR_HTTP_REQUEST_TIMEOUT' | 'HPE_HEADER_OVERFLOW' | 'HPE_INVALID_CHUNK_SIZE' | 'HPE_INVALID_CONTENT_LENGTH' | 'HPE_INVALID_EOF_STATE' | 'HPE_INVALID_HEADER_TOKEN' | 'HPE_INVALID_TRANSFER_ENCODING' | 'HPE_UNEXPECTED_CONTENT_LENGTH' | 'other';
546
+ /** A rejected inbound HTTP message's public response and bounded diagnostic category. */
547
+ interface HttpParserFailure {
548
+ readonly status: 400 | 408 | 431;
549
+ readonly code: 'bad_request' | 'request_timeout' | 'headers_too_large';
550
+ readonly parserCode: HttpParserCode;
551
+ }
552
+ /** One sampled rejection. Arbitrary paths, query strings, body data and credentials are excluded. */
553
+ type HttpFailureEntry = ({
554
+ readonly kind: 'parser';
555
+ readonly status: HttpParserFailure['status'];
556
+ readonly code: HttpParserCode;
557
+ } | {
558
+ readonly kind: 'target';
559
+ readonly status: 400;
560
+ readonly code: 'bad_request';
561
+ } | {
562
+ readonly kind: 'upstream';
563
+ readonly status: number;
564
+ readonly method: 'POST';
565
+ readonly path: '/api/session/prompt';
566
+ }) & {
567
+ readonly suppressed?: number;
568
+ };
569
+ //#endregion
543
570
  //#region src/http-security.d.ts
544
571
  export declare const DEVICE_COOKIE = "dsh_ma_device";
545
572
  export declare const SESSION_COOKIE = "dsh_ma_session";
@@ -1002,9 +1029,10 @@ export declare class MobileAccessGateway {
1002
1029
  private readonly clientModuleTasks;
1003
1030
  private readonly clientModuleRevocations;
1004
1031
  private readonly authFailures;
1032
+ private readonly httpFailures;
1005
1033
  constructor(config: ResolvedGatewayConfig, store: DeviceStore, extensions?: MobileAccessService | undefined, upstreamAuthenticatedUrl?: string | undefined, extraWebSocketPaths?: {
1006
1034
  has(pathname: string): boolean;
1007
- } | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined, clientModulePreferences?: ClientModulePreferenceStore, authFailureLog?: (entry: AuthFailureEntry) => void);
1035
+ } | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined, clientModulePreferences?: ClientModulePreferenceStore, authFailureLog?: (entry: AuthFailureEntry) => void, httpFailureLog?: (entry: HttpFailureEntry) => void);
1008
1036
  /** Initialize durable state, validate TLS, and bind the externally reachable listener. */
1009
1037
  start(): Promise<void>;
1010
1038
  /** Install a newly signed provided leaf for future TLS handshakes without dropping sessions. */
@@ -1921,7 +1949,7 @@ interface OriginConfigurationStatus {
1921
1949
  }
1922
1950
  /** Require a public HTTPS origin; custom external ports are supported. */
1923
1951
  export declare function validateOriginPublicOrigin(value: unknown): string;
1924
- /** Bind only one explicit loopback or RFC1918 IPv4 interface, never all interfaces. */
1952
+ /** Bind one explicit loopback, RFC1918 IPv4 or IPv6 ULA interface; zone IDs are unsupported. */
1925
1953
  export declare function validateOriginListenHost(value: unknown): string;
1926
1954
  export declare function validateOriginListenPort(value: unknown): number;
1927
1955
  /** Authorize direct proxy socket peers, not untrusted forwarded client headers. */
package/lib/index.mjs CHANGED
@@ -9,7 +9,7 @@ import { performance as performance$1 } from "node:perf_hooks";
9
9
  import { execFile, spawn } from "node:child_process";
10
10
  import { createSocket } from "node:dgram";
11
11
  import { homedir, hostname, networkInterfaces, tmpdir } from "node:os";
12
- import { createServer as createServer$1, request } from "node:http";
12
+ import { STATUS_CODES, createServer as createServer$1, request } from "node:http";
13
13
  import { createServer as createServer$2, request as request$1 } from "node:https";
14
14
  import { Readable, Transform, finished } from "node:stream";
15
15
  import { finished as finished$1, pipeline } from "node:stream/promises";
@@ -1968,7 +1968,7 @@ function authenticatedCookie(values, authenticate, accepts = () => true) {
1968
1968
  }
1969
1969
  //#endregion
1970
1970
  //#region src/auth-failure-log.ts
1971
- const LOG_INTERVAL_MS = 3e4;
1971
+ const LOG_INTERVAL_MS$1 = 3e4;
1972
1972
  const MAX_LOGGED_ORIGIN_LENGTH = 512;
1973
1973
  function requestPathCategory(path) {
1974
1974
  switch (path) {
@@ -2065,7 +2065,7 @@ var AuthFailureRecorder = class {
2065
2065
  return;
2066
2066
  }
2067
2067
  const suppressed = window.suppressed;
2068
- window.nextLogAt = now + LOG_INTERVAL_MS;
2068
+ window.nextLogAt = now + LOG_INTERVAL_MS$1;
2069
2069
  window.suppressed = 0;
2070
2070
  try {
2071
2071
  this.observe({
@@ -2082,6 +2082,129 @@ var AuthFailureRecorder = class {
2082
2082
  }
2083
2083
  };
2084
2084
  //#endregion
2085
+ //#region src/http-failure-log.ts
2086
+ function safeParserCode(code) {
2087
+ switch (code) {
2088
+ case "ERR_HTTP_REQUEST_TIMEOUT":
2089
+ case "HPE_HEADER_OVERFLOW":
2090
+ case "HPE_INVALID_CHUNK_SIZE":
2091
+ case "HPE_INVALID_CONTENT_LENGTH":
2092
+ case "HPE_INVALID_EOF_STATE":
2093
+ case "HPE_INVALID_HEADER_TOKEN":
2094
+ case "HPE_INVALID_TRANSFER_ENCODING":
2095
+ case "HPE_UNEXPECTED_CONTENT_LENGTH": return code;
2096
+ default: return "other";
2097
+ }
2098
+ }
2099
+ /**
2100
+ * Classify Node's HTTP rejection without reflecting an unrecognized error code.
2101
+ * @param nodeCode - The parser error's machine code, not its message or raw packet.
2102
+ * @returns The fixed public HTTP status/category and allowlisted diagnostic code.
2103
+ */
2104
+ function classifyHttpParserFailure(nodeCode) {
2105
+ const parserCode = safeParserCode(nodeCode);
2106
+ if (parserCode === "ERR_HTTP_REQUEST_TIMEOUT") return {
2107
+ status: 408,
2108
+ code: "request_timeout",
2109
+ parserCode
2110
+ };
2111
+ if (parserCode === "HPE_HEADER_OVERFLOW") return {
2112
+ status: 431,
2113
+ code: "headers_too_large",
2114
+ parserCode
2115
+ };
2116
+ return {
2117
+ status: 400,
2118
+ code: "bad_request",
2119
+ parserCode
2120
+ };
2121
+ }
2122
+ const LOG_INTERVAL_MS = 3e4;
2123
+ /**
2124
+ * Per-Gateway diagnostics with three fixed sampling slots and no timers or request-selected keys.
2125
+ * Each kind records once immediately, then at most once per 30 seconds with suppressed counts.
2126
+ * A sink failure cannot change a response, retry a write or expose the diagnostic sink's error.
2127
+ */
2128
+ var HttpFailureRecorder = class {
2129
+ observe;
2130
+ now;
2131
+ reportObserverFailure;
2132
+ windows = {
2133
+ parser: {
2134
+ nextLogAt: 0,
2135
+ suppressed: 0
2136
+ },
2137
+ target: {
2138
+ nextLogAt: 0,
2139
+ suppressed: 0
2140
+ },
2141
+ upstream: {
2142
+ nextLogAt: 0,
2143
+ suppressed: 0
2144
+ }
2145
+ };
2146
+ observerFailureReported = false;
2147
+ constructor(observe, now = () => performance.now(), reportObserverFailure = () => {
2148
+ process.emitWarning("Mobile access HTTP diagnostics could not be recorded", { code: "DSH_MOBILE_HTTP_LOG_FAILED" });
2149
+ }) {
2150
+ this.observe = observe;
2151
+ this.now = now;
2152
+ this.reportObserverFailure = reportObserverFailure;
2153
+ }
2154
+ /** Classify an inbound parser rejection and sample only its fixed status and safe code. */
2155
+ recordParser(nodeCode) {
2156
+ const failure = classifyHttpParserFailure(nodeCode);
2157
+ this.record({
2158
+ kind: "parser",
2159
+ status: failure.status,
2160
+ code: failure.parserCode
2161
+ });
2162
+ return failure;
2163
+ }
2164
+ /** Sample an invalid request target without retaining its raw or decoded URL. */
2165
+ recordTarget() {
2166
+ this.record({
2167
+ kind: "target",
2168
+ status: 400,
2169
+ code: "bad_request"
2170
+ });
2171
+ }
2172
+ /** Sample only authenticated prompt rejections; the caller must authorize before forwarding. */
2173
+ recordUpstream(method, pathname, status) {
2174
+ if (method !== "POST" || pathname !== "/api/session/prompt" || !Number.isInteger(status) || status < 400 || status > 599) return;
2175
+ this.record({
2176
+ kind: "upstream",
2177
+ status,
2178
+ method: "POST",
2179
+ path: "/api/session/prompt"
2180
+ });
2181
+ }
2182
+ record(entry) {
2183
+ if (this.observe === void 0) return;
2184
+ const now = this.now();
2185
+ const window = this.windows[entry.kind];
2186
+ if (now < window.nextLogAt) {
2187
+ window.suppressed = Math.min(Number.MAX_SAFE_INTEGER, window.suppressed + 1);
2188
+ return;
2189
+ }
2190
+ const suppressed = window.suppressed;
2191
+ window.nextLogAt = now + LOG_INTERVAL_MS;
2192
+ window.suppressed = 0;
2193
+ try {
2194
+ this.observe({
2195
+ ...entry,
2196
+ ...suppressed === 0 ? {} : { suppressed }
2197
+ });
2198
+ } catch (observerError) {
2199
+ if (this.observerFailureReported) return;
2200
+ this.observerFailureReported = true;
2201
+ try {
2202
+ this.reportObserverFailure();
2203
+ } catch (reporterError) {}
2204
+ }
2205
+ }
2206
+ };
2207
+ //#endregion
2085
2208
  //#region src/client-module-recovery.ts
2086
2209
  const CLIENT_MODULE_RECOVERY_COPY = {
2087
2210
  zh: {
@@ -5052,7 +5175,7 @@ function rejectUpgrade(socket, status, code) {
5052
5175
  if (socket.destroyed) return;
5053
5176
  const body = `${JSON.stringify({ error: code })}\n`;
5054
5177
  socket.end([
5055
- `HTTP/1.1 ${String(status)} ${status === 401 ? "Unauthorized" : status === 403 ? "Forbidden" : "Bad Request"}`,
5178
+ `HTTP/1.1 ${String(status)} ${STATUS_CODES[status] ?? "Bad Request"}`,
5056
5179
  "Connection: close",
5057
5180
  "Cache-Control: no-store",
5058
5181
  "Content-Type: application/json; charset=utf-8",
@@ -5423,7 +5546,8 @@ var MobileAccessGateway = class {
5423
5546
  clientModuleTasks = /* @__PURE__ */ new Set();
5424
5547
  clientModuleRevocations = /* @__PURE__ */ new Set();
5425
5548
  authFailures;
5426
- constructor(config, store, extensions, upstreamAuthenticatedUrl, extraWebSocketPaths, blockedUpgradeLog, onDiscoveryDegraded, clientModulePreferences, authFailureLog) {
5549
+ httpFailures;
5550
+ constructor(config, store, extensions, upstreamAuthenticatedUrl, extraWebSocketPaths, blockedUpgradeLog, onDiscoveryDegraded, clientModulePreferences, authFailureLog, httpFailureLog) {
5427
5551
  this.config = config;
5428
5552
  this.extensions = extensions;
5429
5553
  this.upstreamAuthenticatedUrl = upstreamAuthenticatedUrl;
@@ -5431,6 +5555,7 @@ var MobileAccessGateway = class {
5431
5555
  this.blockedUpgradeLog = blockedUpgradeLog;
5432
5556
  this.onDiscoveryDegraded = onDiscoveryDegraded;
5433
5557
  this.authFailures = new AuthFailureRecorder(authFailureLog);
5558
+ this.httpFailures = new HttpFailureRecorder(httpFailureLog);
5434
5559
  this.clientModulePreferences = clientModulePreferences ?? new ClientModulePreferenceStore(join(dirname(config.stateFile), `${basename(config.stateFile)}.client-modules.json`), config.excludedClientModules);
5435
5560
  this.listenerTlsEnabled = config.tls.mode === "provided";
5436
5561
  this.tlsEnabled = config.publicTls;
@@ -5527,8 +5652,10 @@ var MobileAccessGateway = class {
5527
5652
  rejectUpgrade(client, mapped.status, mapped.code);
5528
5653
  });
5529
5654
  });
5530
- server.on("clientError", (_error, socket) => {
5531
- rejectUpgrade(socket, 400, "bad_request");
5655
+ server.on("clientError", (error, socket) => {
5656
+ const nodeCode = "code" in error && typeof error.code === "string" ? error.code : void 0;
5657
+ const failure = this.httpFailures.recordParser(nodeCode);
5658
+ rejectUpgrade(socket, failure.status, failure.code);
5532
5659
  });
5533
5660
  await new Promise((resolve, reject) => {
5534
5661
  const failed = (error) => {
@@ -5932,7 +6059,13 @@ var MobileAccessGateway = class {
5932
6059
  sendJson(response, 200, { loggedOut: true }, this.tlsEnabled);
5933
6060
  }
5934
6061
  async handleExternalRequest(request, response) {
5935
- const target = parseRequestTarget(request.url);
6062
+ let target;
6063
+ try {
6064
+ target = parseRequestTarget(request.url);
6065
+ } catch (error) {
6066
+ if (error instanceof HttpError && error.status === 400) this.httpFailures.recordTarget();
6067
+ throw error;
6068
+ }
5936
6069
  const policy = this.requirePolicy();
5937
6070
  const isMutation = request.method !== "GET" && request.method !== "HEAD";
5938
6071
  assertExternalTrust(request, policy, isMutation);
@@ -7093,6 +7226,7 @@ var MobileAccessGateway = class {
7093
7226
  setSecurityHeaders(response, this.tlsEnabled, "proxied");
7094
7227
  const headers = sanitizeResponseHeaders(proxied.headers, this.config.upstreamOrigin);
7095
7228
  const statusCode = proxied.statusCode ?? 502;
7229
+ this.httpFailures.recordUpstream(request$2.method, request$2.url?.split("?", 1)[0] ?? "", statusCode);
7096
7230
  const cacheControl = revisionedStaticCacheControl(request$2, statusCode);
7097
7231
  if (cacheControl !== void 0) headers["cache-control"] = cacheControl;
7098
7232
  const compressible = canCompressResponse(request$2, proxied);
@@ -11620,12 +11754,16 @@ const MIN_ORIGIN_LISTEN_PORT = 1024;
11620
11754
  const RESERVED_LAN_GATEWAY_PORT$1 = 3443;
11621
11755
  /** DSH's own WebServer port; the plugin cannot function while it is taken. */
11622
11756
  const RESERVED_DSH_WEB_PORT = 3080;
11623
- const PRIVATE_NETWORKS = [
11624
- "10.0.0.0/8",
11625
- "172.16.0.0/12",
11626
- "192.168.0.0/16"
11627
- ].map(parseCidr);
11628
- const TRUSTED_NETWORKS = [...PRIVATE_NETWORKS, parseCidr("127.0.0.0/8")];
11757
+ const TRUSTED_NETWORKS = [
11758
+ ...[
11759
+ "10.0.0.0/8",
11760
+ "172.16.0.0/12",
11761
+ "192.168.0.0/16",
11762
+ "fc00::/7"
11763
+ ].map(parseCidr),
11764
+ parseCidr("127.0.0.0/8"),
11765
+ parseCidr("::1/128")
11766
+ ];
11629
11767
  /** Require a public HTTPS origin; custom external ports are supported. */
11630
11768
  function validateOriginPublicOrigin(value) {
11631
11769
  if (typeof value !== "string" || value.length > 512 || /[\s\u0000-\u001f\u007f\\@?#]/u.test(value)) throw new Error("origin_public_origin_invalid");
@@ -11647,10 +11785,15 @@ function validateOriginPublicOrigin(value) {
11647
11785
  if (url.protocol !== "https:" || url.pathname !== "/" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || url.port !== "" && Number(url.port) < 1 || (isIP(host) === 4 ? !isGloballyRoutableIpv4(host) : isIP(host) !== 0 || !validHostname)) throw new Error("origin_public_origin_invalid");
11648
11786
  return url.origin;
11649
11787
  }
11650
- /** Bind only one explicit loopback or RFC1918 IPv4 interface, never all interfaces. */
11788
+ /** Bind one explicit loopback, RFC1918 IPv4 or IPv6 ULA interface; zone IDs are unsupported. */
11651
11789
  function validateOriginListenHost(value) {
11652
- if (typeof value !== "string" || isIP(value) !== 4 || !isLoopbackAddress(value) && !addressAllowed(value, PRIVATE_NETWORKS)) throw new Error("origin_listen_host_invalid");
11653
- return value;
11790
+ const addressFamily = isIP(value);
11791
+ if (typeof value !== "string" || value.includes("%") || addressFamily === 0 || !addressAllowed(value, TRUSTED_NETWORKS.filter((range) => range.bits === (addressFamily === 6 ? 128 : 32)))) throw new Error("origin_listen_host_invalid");
11792
+ return addressFamily === 6 ? new URL("http://[" + value + "]").hostname.slice(1, -1) : value;
11793
+ }
11794
+ /** Format a validated backend listener, including the brackets required by IPv6 URLs. */
11795
+ function formatOriginBackendOrigin(listenHost, listenPort) {
11796
+ return "http://" + (isIP(listenHost) === 6 ? "[" + listenHost + "]" : listenHost) + ":" + String(listenPort);
11654
11797
  }
11655
11798
  function validateOriginListenPort(value) {
11656
11799
  if (!Number.isSafeInteger(value) || Number(value) < MIN_ORIGIN_LISTEN_PORT || Number(value) > 65535) throw new Error("origin_listen_port_invalid");
@@ -11659,14 +11802,15 @@ function validateOriginListenPort(value) {
11659
11802
  }
11660
11803
  /** Authorize direct proxy socket peers, not untrusted forwarded client headers. */
11661
11804
  function validateOriginAllowedCidrs(value, listenHost) {
11662
- const input = value === void 0 && isLoopbackAddress(listenHost) ? ["127.0.0.0/8"] : value;
11805
+ const addressFamily = isIP(listenHost);
11806
+ const input = value === void 0 && isLoopbackAddress(listenHost) ? [addressFamily === 6 ? "::1/128" : "127.0.0.0/8"] : value;
11663
11807
  if (!Array.isArray(input) || input.length === 0 || input.length > 16) throw new Error("origin_allowed_cidrs_invalid");
11664
11808
  const cidrs = [];
11665
11809
  for (const entry of input) {
11666
- if (typeof entry !== "string" || entry.length > 64 || isIP(entry.split("/")[0] ?? "") !== 4) throw new Error("origin_allowed_cidrs_invalid");
11810
+ if (typeof entry !== "string" || entry.length > 64 || entry.includes("%") || isIP(entry.split("/")[0] ?? "") !== addressFamily) throw new Error("origin_allowed_cidrs_invalid");
11667
11811
  try {
11668
11812
  const cidr = parseCidr(entry);
11669
- if (cidr.bits !== 32 || !TRUSTED_NETWORKS.some((range) => cidr.prefix >= range.prefix && addressAllowed(entry.split("/")[0], [range]))) throw new Error("untrusted CIDR");
11813
+ if (cidr.bits !== (addressFamily === 6 ? 128 : 32) || !TRUSTED_NETWORKS.some((range) => cidr.bits === range.bits && cidr.prefix >= range.prefix && addressAllowed(entry.split("/")[0], [range]))) throw new Error("untrusted CIDR");
11670
11814
  cidrs.push(cidr.source);
11671
11815
  } catch {
11672
11816
  throw new Error("origin_allowed_cidrs_invalid");
@@ -11744,7 +11888,7 @@ var OriginConfigStore = class {
11744
11888
  listenHost: settings.listenHost,
11745
11889
  listenPort: settings.listenPort,
11746
11890
  allowedCidrs: settings.allowedCidrs,
11747
- backendOrigin: "http://" + settings.listenHost + ":" + String(settings.listenPort)
11891
+ backendOrigin: formatOriginBackendOrigin(settings.listenHost, settings.listenPort)
11748
11892
  },
11749
11893
  storagePath: this.stateRoot,
11750
11894
  ...this.errorCode === void 0 ? {} : { errorCode: this.errorCode }
@@ -11904,7 +12048,7 @@ var OriginController = class {
11904
12048
  enabled: true,
11905
12049
  state: "ready",
11906
12050
  origin: settings.publicOrigin,
11907
- backendOrigin: "http://" + settings.listenHost + ":" + String(gateway.address().port)
12051
+ backendOrigin: formatOriginBackendOrigin(settings.listenHost, gateway.address().port)
11908
12052
  });
11909
12053
  }
11910
12054
  async stop() {
@@ -17601,6 +17745,8 @@ async function apply(ctx, config) {
17601
17745
  logger.warn("%s discovery degraded while DSH remains available: %s", source, code);
17602
17746
  }, clientModulePreferences, (entry) => {
17603
17747
  logger.warn("auth failure %s", JSON.stringify(entry));
17748
+ }, (entry) => {
17749
+ logger.warn("http failure %s", JSON.stringify(entry));
17604
17750
  });
17605
17751
  await candidate.start();
17606
17752
  lanGateway = candidate;
@@ -17659,6 +17805,8 @@ async function apply(ctx, config) {
17659
17805
  const resolved = remoteGatewayConfig(template, publicOrigin, remoteDeviceFile, remoteInstanceId, listenPort, instanceId);
17660
17806
  const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences, (entry) => {
17661
17807
  logger.warn("auth failure %s", JSON.stringify(entry));
17808
+ }, (entry) => {
17809
+ logger.warn("http failure %s", JSON.stringify(entry));
17662
17810
  });
17663
17811
  await candidate.start();
17664
17812
  return candidate;
@@ -17675,7 +17823,11 @@ async function apply(ctx, config) {
17675
17823
  if (!isFrpSelfSignedIngress(settings)) return createRemoteGateway(publicOrigin);
17676
17824
  const ingress = await ensureFrpIngressCertificate(settings, remoteDeviceFile, Date.now(), void 0, stateDirectory);
17677
17825
  const resolved = frpIngressGatewayConfig(template, settings, remoteDeviceFile, ingress);
17678
- const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences);
17826
+ const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences, (entry) => {
17827
+ logger.warn("auth failure %s", JSON.stringify(entry));
17828
+ }, (entry) => {
17829
+ logger.warn("http failure %s", JSON.stringify(entry));
17830
+ });
17679
17831
  try {
17680
17832
  await candidate.start();
17681
17833
  } catch (error) {
@@ -17686,7 +17838,11 @@ async function apply(ctx, config) {
17686
17838
  };
17687
17839
  const createOriginGateway = async (settings) => {
17688
17840
  const resolved = originGatewayConfig(template, settings, remoteDeviceFile, remoteInstanceId, settings.listenPort, instanceId);
17689
- const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences);
17841
+ const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences, (entry) => {
17842
+ logger.warn("auth failure %s", JSON.stringify(entry));
17843
+ }, (entry) => {
17844
+ logger.warn("http failure %s", JSON.stringify(entry));
17845
+ });
17690
17846
  try {
17691
17847
  await candidate.start();
17692
17848
  } catch (error) {