@dsh-remote/relay 3.1.7 → 3.1.9

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/README.md CHANGED
@@ -18,19 +18,21 @@ curl -s http://127.0.0.1:8787/healthz
18
18
 
19
19
  ### 常用配置
20
20
 
21
- | 变量 | 必填 | 默认 | 说明 |
22
- | ----------------------------- | ---- | ----------- | ------------------------------------------------------------------------------------------------ |
23
- | `DRC_HOST_TOKEN` | ✅ | — | 主机出站认证凭据。`openssl rand -hex 32` 生成。**与主机插件那份必须逐字一致** |
24
- | `DRC_PORT` | | `8787` | 监听端口。`0` = 让系统分配(测试与容器用) |
25
- | `DRC_BIND` | | `127.0.0.1` | 绑定地址。默认只绑回环:TLS 由反代终止。**只有自己就是边缘(容器直接发布端口)时才设 `0.0.0.0`** |
26
- | `DRC_PUBLIC_URL` | | 空 | `/api/info` 返回的对外地址。公网填 `wss://你的域名` |
27
- | `DRC_LOG_LEVEL` | | `info` | `debug` / `info` / `warn` / `error` / `silent`。**区分大小写** |
28
- | `DRC_PAIR_TTL_MS` | | `120000` | 配对码的**服务端权威**寿命(毫秒) |
29
- | `DRC_STATE_FILE` | | 空(关闭) | 会话表落盘路径。默认关闭 = 纯内存;配上后启动先读它、会话表有增删时写回 |
30
- | `DRC_STATE_SAVE_MS` | | `60000` | 周期补写状态文件的间隔(只为刷新 `lastActivityAt`) |
31
- | `DRC_MAX_BUFFERED_BYTES` | | `1048576` | 慢消费者阈值:发送缓冲区**连续**超限达**本角色窗口**才 1008 断开 |
32
- | `DRC_SLOW_CONSUMER_HOST_MS` | | `10000` | **主机**侧慢消费者窗口(毫秒)。主机卡住就是所有人卡住,所以窗口短 |
33
- | `DRC_SLOW_CONSUMER_CLIENT_MS` | | `45000` | **客户端**侧慢消费者窗口(毫秒)。**必须大于手机自己的重连周期**(≈42.5 秒) |
21
+ | 变量 | 必填 | 默认 | 说明 |
22
+ | ----------------------------- | ---- | ----------- | -------------------------------------------------------------------------------------------------------- |
23
+ | `DRC_HOST_TOKEN` | ✅ | — | 主机出站认证凭据。`openssl rand -hex 32` 生成。**与主机插件那份必须逐字一致** |
24
+ | `DRC_PORT` | | `8787` | 监听端口。`0` = 让系统分配(测试与容器用) |
25
+ | `DRC_BIND` | | `127.0.0.1` | 绑定地址。默认只绑回环:TLS 由反代终止。**只有自己就是边缘(容器直接发布端口)时才设 `0.0.0.0`** |
26
+ | `DRC_PUBLIC_URL` | | 空 | `/api/info` 返回的对外地址。公网填 `wss://你的域名` |
27
+ | `DRC_LOG_LEVEL` | | `info` | `debug` / `info` / `warn` / `error` / `silent`。**区分大小写** |
28
+ | `DRC_PAIR_TTL_MS` | | `120000` | 配对码的**服务端权威**寿命(毫秒) |
29
+ | `DRC_MP_APPID` | | — | 小程序 appid。与 `DRC_MP_SECRET` 同时配置才启用 `GET /code`(生成小程序码);不配则插件回退旧 `dshr:` 码 |
30
+ | `DRC_MP_SECRET` | | — | 小程序 secret。**只放在中继**,永不进插件包;换了 secret 记得同步这里 |
31
+ | `DRC_STATE_FILE` | | 空(关闭) | 会话表落盘路径。默认关闭 = 纯内存;配上后启动先读它、会话表有增删时写回 |
32
+ | `DRC_STATE_SAVE_MS` | | `60000` | 周期补写状态文件的间隔(只为刷新 `lastActivityAt`) |
33
+ | `DRC_MAX_BUFFERED_BYTES` | | `1048576` | 慢消费者阈值:发送缓冲区**连续**超限达**本角色窗口**才 1008 断开 |
34
+ | `DRC_SLOW_CONSUMER_HOST_MS` | | `10000` | **主机**侧慢消费者窗口(毫秒)。主机卡住就是所有人卡住,所以窗口短 |
35
+ | `DRC_SLOW_CONSUMER_CLIENT_MS` | | `45000` | **客户端**侧慢消费者窗口(毫秒)。**必须大于手机自己的重连周期**(≈42.5 秒) |
34
36
 
35
37
  ### 端点
36
38
 
@@ -23443,7 +23443,7 @@ function date4(params) {
23443
23443
  return _coercedDate(ZodDate, params);
23444
23444
  }
23445
23445
 
23446
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/limits.js
23446
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/limits.js
23447
23447
  var MAX_RELAY_MESSAGE_BYTES = 1024 * 1024;
23448
23448
  var MAX_CIPHERTEXT_BYTES = MAX_RELAY_MESSAGE_BYTES - 8 * 1024;
23449
23449
  var MAX_IMAGE_ATTACHMENTS = 4;
@@ -23452,7 +23452,7 @@ var MAX_ATTACHMENT_BYTES = 512 * 1024;
23452
23452
  var MAX_ATTACHMENT_TOTAL_BYTES = 512 * 1024;
23453
23453
  var IDEMPOTENCY_WINDOW_MS = 5 * 60 * 1e3;
23454
23454
 
23455
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/frames.js
23455
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/frames.js
23456
23456
  var PROTOCOL_VERSION = 1;
23457
23457
  var PAIRING_TOKEN_RE = /^\d{6}$/;
23458
23458
  var nonEmpty = external_exports.string().min(1);
@@ -23481,13 +23481,40 @@ var helloFrame = external_exports.object({
23481
23481
  clientId: external_exports.string().min(1).max(128).optional(),
23482
23482
  clientMeta: clientMeta.optional()
23483
23483
  });
23484
+ var publicKeyB64 = external_exports.string().regex(/^[A-Za-z0-9+/]{43}=$/, "\u5FC5\u987B\u662F 32 \u5B57\u8282 base64 \u516C\u94A5");
23484
23485
  var pairBeginFrame = external_exports.object({
23485
23486
  t: external_exports.literal("pair-begin"),
23486
23487
  pairingToken
23487
23488
  });
23488
23489
  var pairClaimFrame = external_exports.object({
23489
23490
  t: external_exports.literal("pair-begin-client"),
23490
- pairingToken
23491
+ pairingToken,
23492
+ clientPub: publicKeyB64.optional()
23493
+ });
23494
+ var pairRequestFrame = external_exports.object({
23495
+ t: external_exports.literal("pair-request"),
23496
+ pairingToken,
23497
+ clientPub: publicKeyB64,
23498
+ /**
23499
+ * 认领这条码时**新开的会话号**。中继在 claim 的同一时刻就知道它,一并带来:
23500
+ * 主机回 `pair-grant` 必须带会话号(见 pairGrantFrame),否则中继无处可送。
23501
+ */
23502
+ sessionId: nonEmpty,
23503
+ hostId: nonEmpty.optional()
23504
+ });
23505
+ var pairGrantFrame = external_exports.object({
23506
+ t: external_exports.literal("pair-grant"),
23507
+ /**
23508
+ * **带 sessionId 而不是 pairingToken**(2026-10-21 拍板)。
23509
+ *
23510
+ * 中继在 `claim()` 之后就没有 token→会话 的映射了(pendingPairs 当场消费),
23511
+ * 若按 token 转发就得再养一张 60s 的短命表。而主机在 `peer-joined` 里**已经拿到
23512
+ * sessionId**,让它把会话号原样带回来,中继按会话成员转发即可,零新状态。
23513
+ */
23514
+ sessionId: nonEmpty,
23515
+ hostPub: publicKeyB64,
23516
+ nonce: nonEmpty,
23517
+ wrappedPsk: nonEmpty
23491
23518
  });
23492
23519
  var encFrame = external_exports.object({
23493
23520
  t: external_exports.literal("enc"),
@@ -23525,6 +23552,8 @@ var endpointFrame = external_exports.discriminatedUnion("t", [
23525
23552
  helloFrame,
23526
23553
  pairBeginFrame,
23527
23554
  pairClaimFrame,
23555
+ // 主机发出去的:psk 密文(中继原样转交给手机)
23556
+ pairGrantFrame,
23528
23557
  resyncFrame,
23529
23558
  encFrame,
23530
23559
  encBatchFrame,
@@ -23678,6 +23707,9 @@ var pongFrame = external_exports.object({ t: external_exports.literal("pong"), t
23678
23707
  var relayFrame = external_exports.discriminatedUnion("t", [
23679
23708
  helloOkFrame,
23680
23709
  pairReadyFrame,
23710
+ // 中继发出的两条:认领请求带给主机、psk 密文带给手机
23711
+ pairRequestFrame,
23712
+ pairGrantFrame,
23681
23713
  pairedFrame,
23682
23714
  pairFailFrame,
23683
23715
  peerJoinedFrame,
@@ -23844,6 +23876,8 @@ function loadConfig(env, version2) {
23844
23876
  }
23845
23877
  const config2 = {
23846
23878
  hostToken,
23879
+ mpAppId: (env.DRC_MP_APPID ?? "").trim(),
23880
+ mpSecret: (env.DRC_MP_SECRET ?? "").trim(),
23847
23881
  port: portNumber(env.DRC_PORT, 8787, "DRC_PORT", problems),
23848
23882
  // 默认只绑回环:TLS 由反代终止,中继没理由出现在局域网里。
23849
23883
  // 只有自己就是边缘(容器直接发布端口)时才设 DRC_BIND=0.0.0.0。
@@ -23930,7 +23964,7 @@ var import_subprotocol = __toESM(require_subprotocol(), 1);
23930
23964
  var import_websocket = __toESM(require_websocket(), 1);
23931
23965
  var import_websocket_server = __toESM(require_websocket_server(), 1);
23932
23966
 
23933
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/identity/ids.js
23967
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/identity/ids.js
23934
23968
  import { randomBytes, randomUUID } from "node:crypto";
23935
23969
  var CONVERSATION_ID_PREFIX = "c_";
23936
23970
  function newConversationId() {
@@ -23940,7 +23974,7 @@ function newHostId() {
23940
23974
  return randomBytes(4).toString("hex");
23941
23975
  }
23942
23976
 
23943
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/negotiate.js
23977
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/negotiate.js
23944
23978
  function negotiateProtocol(peerProtocol, own2 = PROTOCOL_VERSION) {
23945
23979
  if (peerProtocol === void 0 || peerProtocol === null) {
23946
23980
  return {
@@ -23976,16 +24010,84 @@ function negotiateProtocol(peerProtocol, own2 = PROTOCOL_VERSION) {
23976
24010
  return { ok: true, version: peerProtocol };
23977
24011
  }
23978
24012
 
23979
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/errors.js
24013
+ // src/mpcode.ts
24014
+ var TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
24015
+ var CODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit";
24016
+ var LANDING_PAGE = "pages/sessions/sessions";
24017
+ var TOKEN_SKEW_MS = 6e4;
24018
+ var DEFAULT_TOKEN_TTL_MS = 7e6;
24019
+ function createMpCodeIssuer(options) {
24020
+ const appId = options.appId.trim();
24021
+ const secret = options.secret.trim();
24022
+ const doFetch = options.fetch ?? ((...args) => fetch(...args));
24023
+ const now = options.now ?? (() => Date.now());
24024
+ let cachedToken;
24025
+ const images = /* @__PURE__ */ new Map();
24026
+ const IMAGE_TTL_MS = 36e5;
24027
+ async function fetchToken() {
24028
+ const url2 = `${TOKEN_URL}?grant_type=client_credential&appid=${encodeURIComponent(appId)}&secret=${encodeURIComponent(secret)}`;
24029
+ const response = await doFetch(url2);
24030
+ const body = await response.json();
24031
+ if (typeof body.access_token !== "string" || body.access_token === "") {
24032
+ throw new Error(`\u5FAE\u4FE1 access_token \u83B7\u53D6\u5931\u8D25\uFF1A${body.errcode ?? "?"} ${body.errmsg ?? ""}`.trim());
24033
+ }
24034
+ const ttl = typeof body.expires_in === "number" && body.expires_in > 0 ? body.expires_in * 1e3 : DEFAULT_TOKEN_TTL_MS;
24035
+ cachedToken = { value: body.access_token, expiresAt: now() + Math.max(0, ttl - TOKEN_SKEW_MS) };
24036
+ return body.access_token;
24037
+ }
24038
+ async function token() {
24039
+ if (cachedToken !== void 0 && cachedToken.expiresAt > now()) return cachedToken.value;
24040
+ return fetchToken();
24041
+ }
24042
+ async function requestCode(accessToken, pairingToken2) {
24043
+ const response = await doFetch(`${CODE_URL}?access_token=${encodeURIComponent(accessToken)}`, {
24044
+ method: "POST",
24045
+ headers: { "content-type": "application/json" },
24046
+ body: JSON.stringify({ scene: pairingToken2, page: LANDING_PAGE })
24047
+ });
24048
+ const bytes = new Uint8Array(await response.arrayBuffer());
24049
+ if (bytes.length > 2 && bytes[0] === 255 && bytes[1] === 216) return bytes;
24050
+ try {
24051
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
24052
+ return { errcode: typeof parsed.errcode === "number" ? parsed.errcode : -1 };
24053
+ } catch {
24054
+ return { errcode: -1 };
24055
+ }
24056
+ }
24057
+ return {
24058
+ enabled: appId !== "" && secret !== "",
24059
+ async codeFor(pairingToken2) {
24060
+ if (appId === "" || secret === "") throw new Error("\u672A\u914D\u7F6E DRC_MP_APPID / DRC_MP_SECRET");
24061
+ const hit = images.get(pairingToken2);
24062
+ if (hit !== void 0 && hit.expiresAt > now()) return hit.bytes;
24063
+ let accessToken = await token();
24064
+ let result = await requestCode(accessToken, pairingToken2);
24065
+ if (!(result instanceof Uint8Array) && (result.errcode === 40001 || result.errcode === 42001)) {
24066
+ accessToken = await fetchToken();
24067
+ result = await requestCode(accessToken, pairingToken2);
24068
+ }
24069
+ if (!(result instanceof Uint8Array)) {
24070
+ throw new Error(`\u5C0F\u7A0B\u5E8F\u7801\u751F\u6210\u5931\u8D25\uFF1Aerrcode=${result.errcode}`);
24071
+ }
24072
+ images.set(pairingToken2, { bytes: result, expiresAt: now() + IMAGE_TTL_MS });
24073
+ if (images.size > 512) {
24074
+ for (const [key, value] of images) if (value.expiresAt <= now()) images.delete(key);
24075
+ }
24076
+ return result;
24077
+ }
24078
+ };
24079
+ }
24080
+
24081
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/errors.js
23980
24082
  var WAIT_HINTABLE = /* @__PURE__ */ new Set(["rate_limited", "pair_table_full"]);
23981
24083
  function wantsRetryAfter(code) {
23982
24084
  return typeof code === "string" && WAIT_HINTABLE.has(code);
23983
24085
  }
23984
24086
 
23985
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/platform/sleep.js
24087
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/platform/sleep.js
23986
24088
  var SLEEP_BACKENDS = ["caffeinate", "systemd-inhibit", "unsupported"];
23987
24089
 
23988
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/payloads.js
24090
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/payloads.js
23989
24091
  var SESSION_STATES = [
23990
24092
  "idle",
23991
24093
  "running",
@@ -24411,7 +24513,7 @@ var PAYLOAD_TYPES = {
24411
24513
  evSessionHistory: "ev.session_history"
24412
24514
  };
24413
24515
 
24414
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/outbound.js
24516
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/outbound.js
24415
24517
  function helloOkForClient(clientId, protocol) {
24416
24518
  return { t: "hello-ok", role: "client", clientId, ...protocol === void 0 ? {} : { protocol } };
24417
24519
  }
@@ -24427,6 +24529,15 @@ function pairReady(pairingToken2, ttlMs) {
24427
24529
  function paired(sessionId, hostId) {
24428
24530
  return { t: "paired", sessionId, hostId };
24429
24531
  }
24532
+ function pairRequest(pairingToken2, clientPub, sessionId, hostId) {
24533
+ return {
24534
+ t: "pair-request",
24535
+ pairingToken: pairingToken2,
24536
+ clientPub,
24537
+ sessionId,
24538
+ ...hostId === void 0 ? {} : { hostId }
24539
+ };
24540
+ }
24430
24541
  function pairFail(reason, pairingToken2) {
24431
24542
  return { t: "pair-fail", reason, ...pairingToken2 === void 0 ? {} : { pairingToken: pairingToken2 } };
24432
24543
  }
@@ -24489,11 +24600,12 @@ function encBatchToRelay(sessionId, items) {
24489
24600
  return { t: "enc-batch", sessionId, items: items.map((item) => ({ ...item })) };
24490
24601
  }
24491
24602
 
24492
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/registry.js
24603
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/registry.js
24493
24604
  var ENDPOINT_FRAME_NAMES = [
24494
24605
  "hello",
24495
24606
  "pair-begin",
24496
24607
  "pair-begin-client",
24608
+ "pair-grant",
24497
24609
  "resync",
24498
24610
  "enc",
24499
24611
  "enc-batch",
@@ -24503,6 +24615,8 @@ var ENDPOINT_FRAME_NAMES = [
24503
24615
  var RELAY_FRAME_NAMES = [
24504
24616
  "hello-ok",
24505
24617
  "pair-ready",
24618
+ "pair-request",
24619
+ "pair-grant",
24506
24620
  "paired",
24507
24621
  "pair-fail",
24508
24622
  "peer-joined",
@@ -24551,7 +24665,7 @@ var EV_TYPES = [
24551
24665
  ];
24552
24666
  var PAYLOAD_TYPE_SET = /* @__PURE__ */ new Set([...CMD_TYPES, ...EV_TYPES]);
24553
24667
 
24554
- // node_modules/.pnpm/@dsh-remote+protocol@3.1.6/node_modules/@dsh-remote/protocol/dist/src/wire/classify.js
24668
+ // node_modules/.pnpm/@dsh-remote+protocol@3.1.9/node_modules/@dsh-remote/protocol/dist/src/wire/classify.js
24555
24669
  function frameTypeOf(value) {
24556
24670
  if (typeof value !== "object" || value === null || Array.isArray(value))
24557
24671
  return void 0;
@@ -25616,6 +25730,12 @@ function createRelay(config2) {
25616
25730
  }
25617
25731
  sendToPeer(peer, paired(claimed.conversationId, claimed.hostId));
25618
25732
  sendToPeer(peerOfHost(claimed.hostId), peerJoinedForHost(claimed.conversationId, peer.clientId, frame.pairingToken));
25733
+ if (frame.clientPub !== void 0) {
25734
+ const hostPeer = peerOfHost(claimed.hostId);
25735
+ if (hostPeer !== void 0) {
25736
+ sendToPeer(hostPeer, pairRequest(frame.pairingToken, frame.clientPub, claimed.conversationId, claimed.hostId));
25737
+ }
25738
+ }
25619
25739
  markStateChanged();
25620
25740
  log.info("paired", {
25621
25741
  sessionId: claimed.conversationId,
@@ -25701,6 +25821,29 @@ function createRelay(config2) {
25701
25821
  return handlePairBegin(peer, frame);
25702
25822
  case "pair-begin-client":
25703
25823
  return handlePairClaim(peer, frame);
25824
+ /**
25825
+ * 主机 → 中继 → 手机:psk 的密文(2026-10-21)。
25826
+ *
25827
+ * **只有主机能发**;中继按 `sessionId` 找会话成员,把帧原样转过去。
25828
+ * 它不解析 `wrappedPsk`,也没有能解开的密钥 —— 零知识的前提不变。
25829
+ */
25830
+ case "pair-grant": {
25831
+ if (peer.role !== "host" || !peer.hostId) {
25832
+ sendError(peer, "need_host");
25833
+ return;
25834
+ }
25835
+ const conv = state.conversations.get(frame.sessionId);
25836
+ if (conv === void 0) {
25837
+ log.info("pair-grant dropped, unknown session", { sessionId: frame.sessionId });
25838
+ return;
25839
+ }
25840
+ for (const clientId of conv.clients) {
25841
+ const clientPeer = state.clients.get(clientId);
25842
+ const socket = clientPeer?.ws;
25843
+ if (socket !== void 0) sendTo(socket, frame);
25844
+ }
25845
+ return;
25846
+ }
25704
25847
  case "resync": {
25705
25848
  if (peer.role !== "host" || !peer.hostId) {
25706
25849
  sendError(peer, "need_host");
@@ -26059,7 +26202,7 @@ function createRelay(config2) {
26059
26202
  log.info("host token claimed", { ip, hostId: rawHostId, hostTokens: state.hostTokens.size });
26060
26203
  res.end(JSON.stringify({ token, hostId: rawHostId, rotatedAt: new Date(now).toISOString() }));
26061
26204
  }
26062
- function httpHandler(req, res) {
26205
+ async function httpHandler(req, res) {
26063
26206
  try {
26064
26207
  const url2 = new URL(req.url ?? "/", "http://relay.invalid");
26065
26208
  res.setHeader("content-type", "application/json; charset=utf-8");
@@ -26078,6 +26221,31 @@ function createRelay(config2) {
26078
26221
  });
26079
26222
  return;
26080
26223
  }
26224
+ if (url2.pathname === "/code") {
26225
+ if (!mpCodes.enabled) {
26226
+ res.statusCode = 501;
26227
+ res.end(JSON.stringify({ error: "mp_not_configured" }));
26228
+ return;
26229
+ }
26230
+ const token = url2.searchParams.get("token") ?? "";
26231
+ if (!/^\d{6}$/.test(token)) {
26232
+ res.statusCode = 400;
26233
+ res.end(JSON.stringify({ error: "bad_token" }));
26234
+ return;
26235
+ }
26236
+ try {
26237
+ const bytes = await mpCodes.codeFor(token);
26238
+ res.statusCode = 200;
26239
+ res.setHeader("content-type", "image/jpeg");
26240
+ res.setHeader("cache-control", "no-store");
26241
+ res.end(Buffer.from(bytes));
26242
+ } catch (error62) {
26243
+ log.warn("mp code generation failed", { error: String(error62?.message ?? error62).slice(0, 160) });
26244
+ res.statusCode = 502;
26245
+ res.end(JSON.stringify({ error: "mp_code_failed" }));
26246
+ }
26247
+ return;
26248
+ }
26081
26249
  if (url2.pathname === "/api/info") {
26082
26250
  res.end(JSON.stringify({ publicUrl: config2.publicUrl, protocol: PROTOCOL_VERSION }));
26083
26251
  return;
@@ -26113,6 +26281,7 @@ function createRelay(config2) {
26113
26281
  }
26114
26282
  }
26115
26283
  }
26284
+ const mpCodes = createMpCodeIssuer({ appId: config2.mpAppId, secret: config2.mpSecret });
26116
26285
  const http = createServer(httpHandler);
26117
26286
  http.on("error", (error62) => {
26118
26287
  log.warn("http server error", { message: String(error62?.message ?? error62) });
@@ -26264,7 +26433,7 @@ function registerSignalHandlers(shutdown) {
26264
26433
 
26265
26434
  // src/main.ts
26266
26435
  function readVersion() {
26267
- if ("3.1.7") return "3.1.7";
26436
+ if ("3.1.9") return "3.1.9";
26268
26437
  try {
26269
26438
  const pkg = JSON.parse(readFileSync2(new URL("../../package.json", import.meta.url), "utf8"));
26270
26439
  if (typeof pkg.version === "string") return pkg.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-remote/relay",
3
- "version": "3.1.7",
3
+ "version": "3.1.9",
4
4
  "description": "DSH Remote Control 的零知识 WebSocket 中继:只做路由,不碰载荷明文。可自托管的单文件产物。",
5
5
  "type": "module",
6
6
  "license": "MIT",