phone-lens 0.3.5 → 0.3.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/lib/dev.js CHANGED
@@ -1,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { basename, join, resolve, sep } from "node:path";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
4
4
  import { createServer } from "node:http";
@@ -301,6 +301,60 @@ var DeviceStore = class {
301
301
  }
302
302
  };
303
303
 
304
+ //#endregion
305
+ //#region src/store/settings.ts
306
+ const SAVE_MODES = [
307
+ "composer",
308
+ "folder",
309
+ "both"
310
+ ];
311
+ /**
312
+ * User-facing capture settings, persisted as JSON under the plugin data dir.
313
+ * Stored host-side on purpose: the folder save happens on this machine, and
314
+ * every loopback view (overlay, /view.html) must see the same mode.
315
+ */
316
+ var AppSettingsStore = class {
317
+ data;
318
+ constructor(file, defaultSaveDir) {
319
+ this.file = file;
320
+ this.defaultSaveDir = defaultSaveDir;
321
+ this.data = {
322
+ saveMode: "composer",
323
+ saveDir: ""
324
+ };
325
+ this.load();
326
+ }
327
+ load() {
328
+ if (!existsSync(this.file)) return;
329
+ try {
330
+ const raw = JSON.parse(readFileSync(this.file, "utf8"));
331
+ if (SAVE_MODES.includes(raw.saveMode)) this.data.saveMode = raw.saveMode;
332
+ if (typeof raw.saveDir === "string") this.data.saveDir = raw.saveDir;
333
+ } catch {}
334
+ }
335
+ get() {
336
+ return { ...this.data };
337
+ }
338
+ /** Effective absolute folder (resolved default when unset). */
339
+ effectiveSaveDir() {
340
+ return this.data.saveDir.trim() || this.defaultSaveDir;
341
+ }
342
+ set(patch) {
343
+ if (patch.saveMode && SAVE_MODES.includes(patch.saveMode)) this.data.saveMode = patch.saveMode;
344
+ if (typeof patch.saveDir === "string") this.data.saveDir = patch.saveDir.replace(/^["']|["']$/g, "").trim();
345
+ this.persist();
346
+ return this.get();
347
+ }
348
+ persist() {
349
+ try {
350
+ mkdirSync(join(this.file, ".."), { recursive: true });
351
+ const tmp = `${this.file}.tmp`;
352
+ writeFileSync(tmp, JSON.stringify(this.data, null, 2), "utf8");
353
+ renameSync(tmp, this.file);
354
+ } catch {}
355
+ }
356
+ };
357
+
304
358
  //#endregion
305
359
  //#region src/types.ts
306
360
  const ERROR_CODES = {
@@ -698,6 +752,8 @@ function queryOf(req) {
698
752
 
699
753
  //#endregion
700
754
  //#region src/server/http.ts
755
+ /** Process-lifetime marker: surfaces service rebuilds via /status. */
756
+ const SERVER_BOOT_AT = Date.now();
701
757
  /** Boot the receiver: HTTP routes + two websocket endpoints. */
702
758
  async function startLensServer(deps) {
703
759
  const { config: config$1, pairing: pairing$1, devices: devices$1, hub: hub$1, targets: targets$1, sink: sink$1, log: log$1 } = deps;
@@ -764,18 +820,19 @@ async function startLensServer(deps) {
764
820
  done(404, "Not Found");
765
821
  });
766
822
  const heartbeat = setInterval(() => hub$1.pingAll(), 2e4);
767
- await new Promise((resolve, reject) => {
823
+ await new Promise((resolve$1, reject) => {
768
824
  server.once("error", reject);
769
- server.listen(config$1.server.port, config$1.server.host, () => resolve());
825
+ server.listen(config$1.server.port, config$1.server.host, () => resolve$1());
770
826
  });
771
827
  log$1("info", `phone-lens listening on ${config$1.server.host}:${config$1.server.port} (paired devices: ${devices$1.count()})`);
772
828
  return {
773
829
  port: config$1.server.port,
774
830
  dispose: async () => {
775
831
  clearInterval(heartbeat);
832
+ hub$1.dispose();
776
833
  hub$1.detachAll();
777
834
  for (const client of wss.clients) client.terminate();
778
- await new Promise((resolve) => server.close(() => resolve()));
835
+ await new Promise((resolve$1) => server.close(() => resolve$1()));
779
836
  }
780
837
  };
781
838
  }
@@ -793,10 +850,10 @@ async function handle$1(deps, req, res, ctx) {
793
850
  }
794
851
  if (method === "GET" && path === "/info") return sendJson(res, 200, {
795
852
  name: "PhoneLens 直连取景",
796
- version: "0.3.5",
853
+ version: "0.3.7",
797
854
  requiresPairing: true
798
855
  }, cors);
799
- if (!loop && (path === "/" || path === "/view.html" || path === "/qr.json" || path === "/qr.png" || path === "/app-qr.json")) return sendError(res, 403, ERROR_CODES.LOOPBACK_ONLY, "preview surface is loopback-only");
856
+ if (!loop && (path === "/" || path === "/view.html" || path === "/qr.json" || path === "/qr.png" || path === "/app-qr.json" || path === "/app-settings")) return sendError(res, 403, ERROR_CODES.LOOPBACK_ONLY, "preview surface is loopback-only");
800
857
  if (method === "GET" && (path === "/" || path === "/view.html")) {
801
858
  res.writeHead(200, {
802
859
  "content-type": "text/html; charset=utf-8",
@@ -891,7 +948,8 @@ async function handle$1(deps, req, res, ctx) {
891
948
  const captureId = q.get("captureId");
892
949
  const note = q.get("note") ?? (captureId ? hub$1.noteFor(captureId) : void 0);
893
950
  if (captureId) hub$1.consumeCapture(captureId);
894
- const name = (q.get("name") ?? `shot_${new Date().toISOString().replace(/[:.]/g, "-")}.${mediaType === "image/png" ? "png" : "jpg"}`).slice(0, 120);
951
+ const rawName = (q.get("name") ?? `shot_${new Date().toISOString().replace(/[:.]/g, "-")}.${mediaType === "image/png" ? "png" : "jpg"}`).slice(0, 120);
952
+ const name = sanitizeFileName(rawName);
895
953
  let admitted;
896
954
  try {
897
955
  admitted = await admitImage({
@@ -905,18 +963,43 @@ async function handle$1(deps, req, res, ctx) {
905
963
  }
906
964
  log$1("info", `stored ${name} (${buf.byteLength}B) → ${admitted.storage}:${admitted.ref.attachmentId}`);
907
965
  if (admitted.storage === "file") await pruneUploads(deps.fallbackDir, config$1.limits.maxStoredUploads).catch((error) => log$1("warn", `upload pruning failed: ${String(error)}`));
908
- const pendingPath = join(deps.pendingDir, `${admitted.ref.attachmentId}.jpg`);
909
- try {
910
- mkdirSync(deps.pendingDir, { recursive: true });
911
- writeFileSync(pendingPath, buf);
912
- log$1("info", `staged for composer: ${pendingPath}`);
913
- } catch (error) {
914
- log$1("warn", `staging failed: ${String(error)}`);
966
+ const st = deps.appSettings.get();
967
+ const wantFolder = st.saveMode !== "composer";
968
+ const wantComposer = st.saveMode !== "folder";
969
+ let savedDir = null;
970
+ if (wantFolder) {
971
+ const dir = deps.appSettings.effectiveSaveDir();
972
+ try {
973
+ mkdirSync(dir, { recursive: true });
974
+ const fileName = nextAvailableName(dir, name);
975
+ const target = resolveUnder(dir, fileName);
976
+ if (!target) throw new Error("resolved path escapes the save directory");
977
+ writeFileSync(target, buf);
978
+ savedDir = dir;
979
+ log$1("info", `folder-mode saved: ${target}`);
980
+ } catch (error) {
981
+ log$1("warn", `folder save failed (${String(error)}) — falling back to composer staging`);
982
+ }
915
983
  }
916
- hub$1.broadcastToViews({
917
- type: "pending_image",
918
- attachmentId: admitted.ref.attachmentId,
919
- name
984
+ if (wantComposer || !savedDir) {
985
+ const pendingPath = resolveUnder(deps.pendingDir, pendingFileName(admitted.ref.attachmentId));
986
+ try {
987
+ if (!pendingPath) throw new Error("resolved path escapes the pending directory");
988
+ mkdirSync(deps.pendingDir, { recursive: true });
989
+ writeFileSync(pendingPath, buf);
990
+ log$1("info", `staged for composer: ${pendingPath}`);
991
+ } catch (error) {
992
+ log$1("warn", `staging failed: ${String(error)}`);
993
+ }
994
+ hub$1.broadcastToViews({
995
+ type: "pending_image",
996
+ attachmentId: admitted.ref.attachmentId,
997
+ name
998
+ });
999
+ } else hub$1.broadcastToViews({
1000
+ type: "upload_saved",
1001
+ name,
1002
+ dir: savedDir
920
1003
  });
921
1004
  return sendJson(res, 200, {
922
1005
  ok: true,
@@ -926,12 +1009,13 @@ async function handle$1(deps, req, res, ctx) {
926
1009
  bytes: admitted.ref.bytes,
927
1010
  storage: admitted.storage,
928
1011
  delivered: null,
929
- deliverReason: "staged-in-composer"
1012
+ deliverReason: savedDir ? wantComposer ? "saved-and-staged" : "saved-to-folder" : "staged-in-composer"
930
1013
  });
931
1014
  }
932
1015
  if (method === "GET" && path.startsWith("/pending/") && loop) {
933
1016
  const id = path.slice(9).split("/")[0] ?? "";
934
- const file = join(deps.pendingDir, `${id}.jpg`);
1017
+ const file = resolveUnder(deps.pendingDir, pendingFileName(id));
1018
+ if (!file) return sendError(res, 404, ERROR_CODES.BAD_REQUEST, "pending image not found");
935
1019
  try {
936
1020
  const data = await readFile(file);
937
1021
  res.writeHead(200, {
@@ -948,6 +1032,7 @@ async function handle$1(deps, req, res, ctx) {
948
1032
  if (method === "GET" && path === "/status") {
949
1033
  const cameraDeviceId = ctx.getCurrentCamera();
950
1034
  return sendJson(res, 200, {
1035
+ bootAt: SERVER_BOOT_AT,
951
1036
  devices: devices$1.list().map((d) => ({
952
1037
  id: d.deviceId,
953
1038
  name: d.name,
@@ -969,6 +1054,28 @@ async function handle$1(deps, req, res, ctx) {
969
1054
  targets: targets$1.list(),
970
1055
  default: targets$1.resolve()?.sessionId ?? null
971
1056
  }, cors);
1057
+ if (method === "GET" && path === "/app-settings") {
1058
+ const st = deps.appSettings.get();
1059
+ return sendJson(res, 200, {
1060
+ saveMode: st.saveMode,
1061
+ saveDir: st.saveDir,
1062
+ effectiveSaveDir: deps.appSettings.effectiveSaveDir()
1063
+ }, cors);
1064
+ }
1065
+ if (method === "POST" && path === "/app-settings") {
1066
+ const body = await readJsonBody(req, 4096);
1067
+ if (!body) return sendError(res, 400, ERROR_CODES.BAD_REQUEST, "invalid JSON body");
1068
+ const patch = {};
1069
+ if (typeof body.saveMode === "string" && SAVE_MODES.includes(body.saveMode)) patch.saveMode = body.saveMode;
1070
+ if (typeof body.saveDir === "string") patch.saveDir = body.saveDir;
1071
+ const st = deps.appSettings.set(patch);
1072
+ log$1("info", `app settings updated: mode=${st.saveMode} dir=${st.saveDir ? JSON.stringify(st.saveDir) : "(default)"}`);
1073
+ return sendJson(res, 200, {
1074
+ saveMode: st.saveMode,
1075
+ saveDir: st.saveDir,
1076
+ effectiveSaveDir: deps.appSettings.effectiveSaveDir()
1077
+ }, cors);
1078
+ }
972
1079
  if (method === "POST" && path === "/capture" && loop) {
973
1080
  const captureId = randomUUID();
974
1081
  const ok = hub$1.requestCapture(captureId, queryOf(req).get("note") ?? void 0);
@@ -977,6 +1084,37 @@ async function handle$1(deps, req, res, ctx) {
977
1084
  }
978
1085
  return sendError(res, 404, ERROR_CODES.BAD_REQUEST, `no route ${method} ${path}`);
979
1086
  }
1087
+ /** Strip path separators / reserved characters so a phone-supplied upload
1088
+ * name can never escape the target directory or hide as a dotfile. */
1089
+ function sanitizeFileName(input) {
1090
+ const base = basename(input).replace(/[<>:"|?*\u0000-\u001F]/g, "_").replace(/^[\s.]+/, "").trim();
1091
+ return base || "shot.jpg";
1092
+ }
1093
+ /** Pick `name`, then `name (2).ext`, `name (3).ext`, … — folder saves never overwrite. */
1094
+ function nextAvailableName(dir, name) {
1095
+ if (!existsSync(join(dir, name))) return name;
1096
+ const dot = name.lastIndexOf(".");
1097
+ const stem = dot > 0 ? name.slice(0, dot) : name;
1098
+ const ext = dot > 0 ? name.slice(dot) : "";
1099
+ for (let n = 2;; n++) {
1100
+ const candidate = `${stem} (${n})${ext}`;
1101
+ if (!existsSync(join(dir, candidate))) return candidate;
1102
+ }
1103
+ }
1104
+ /** Pending-staging filename for an attachment id. Ids may be URN-ish
1105
+ * ("file:<digest>" without the dsh attachment service) and Windows forbids
1106
+ * ":" in filenames — flatten to a portable name, write and read alike. */
1107
+ function pendingFileName(attachmentId) {
1108
+ return `${attachmentId.replace(/[^A-Za-z0-9._-]/g, "_")}.jpg`;
1109
+ }
1110
+ /** Resolve `name` under `baseDir` and refuse anything that escapes it
1111
+ * (defense in depth on top of sanitizeFileName — e.g. a crafted name that
1112
+ * survives sanitization but still resolves outside the base). */
1113
+ function resolveUnder(baseDir, name) {
1114
+ const base = resolve(baseDir);
1115
+ const target = resolve(base, name);
1116
+ return target === base || target.startsWith(base + sep) ? target : null;
1117
+ }
980
1118
  function sendJson(res, status, body, extraHeaders) {
981
1119
  res.writeHead(status, {
982
1120
  "content-type": "application/json; charset=utf-8",
@@ -1002,7 +1140,7 @@ async function readJsonBody(req, limit) {
1002
1140
  }
1003
1141
  }
1004
1142
  function readRawBody(req, maxBytes) {
1005
- return new Promise((resolve, reject) => {
1143
+ return new Promise((resolve$1, reject) => {
1006
1144
  const chunks = [];
1007
1145
  let size = 0;
1008
1146
  let truncated = false;
@@ -1012,7 +1150,7 @@ function readRawBody(req, maxBytes) {
1012
1150
  truncated = true;
1013
1151
  chunks.length = 0;
1014
1152
  req.destroy();
1015
- resolve({
1153
+ resolve$1({
1016
1154
  buf: Buffer.alloc(0),
1017
1155
  truncated
1018
1156
  });
@@ -1020,7 +1158,7 @@ function readRawBody(req, maxBytes) {
1020
1158
  }
1021
1159
  chunks.push(chunk);
1022
1160
  });
1023
- req.on("end", () => resolve({
1161
+ req.on("end", () => resolve$1({
1024
1162
  buf: Buffer.concat(chunks),
1025
1163
  truncated
1026
1164
  }));
@@ -1052,6 +1190,22 @@ async function pruneUploads(dir, max) {
1052
1190
  //#region src/server/hub.ts
1053
1191
  /**
1054
1192
 
1193
+ * How long the ACTIVE device's stream may go silent before views are told the
1194
+
1195
+ * preview was turned off. The phone app never announces "preview off" — it
1196
+
1197
+ * just stops pushing frames (the WS stays up) — so the host derives the state:
1198
+
1199
+ * a live, open camera uplink with no frames for this long means the user
1200
+
1201
+ * switched preview off on the phone. Device disconnects are signalled
1202
+
1203
+ * separately via the `devices` list.
1204
+
1205
+ */
1206
+ const PREVIEW_STALL_MS = 3e3;
1207
+ /**
1208
+
1055
1209
  * The viewfinder hub: MULTIPLE camera uplinks (one per phone, keyed by
1056
1210
 
1057
1211
  * deviceId), N loopback view downlinks. The "active" device is auto-selected
@@ -1072,13 +1226,23 @@ var ViewHub = class {
1072
1226
  /** captureId → { note, requestedAt } until the matching upload lands or timeout. */
1073
1227
  pendingCaptures = new Map();
1074
1228
  captureTimeoutMs = 6e4;
1229
+ /** Whether the active device is confirmed to be streaming (hello/frame seen). */
1230
+ previewOn = false;
1231
+ stallTimer = null;
1075
1232
  constructor(config$1, log$1) {
1076
1233
  this.config = config$1;
1077
1234
  this.log = log$1;
1235
+ this.stallTimer = setInterval(() => this.checkPreviewStall(), 1e3);
1236
+ }
1237
+ /** Stop the stall watchdog (server dispose). */
1238
+ dispose() {
1239
+ if (this.stallTimer) clearInterval(this.stallTimer);
1240
+ this.stallTimer = null;
1078
1241
  }
1079
1242
  attachCamera(deviceId, ws, name) {
1080
1243
  const prev = this.cameras.get(deviceId);
1081
1244
  if (prev) {
1245
+ this.log("warn", `camera re-attach: kicking previous ws for ${deviceId.slice(0, 8)}`);
1082
1246
  try {
1083
1247
  prev.ws.close(1e3, "new-instance");
1084
1248
  } catch {}
@@ -1099,7 +1263,8 @@ var ViewHub = class {
1099
1263
  if (this.activeDeviceId === deviceId) this.sendControl(deviceId, { type: "resume_preview" });
1100
1264
  else this.sendControl(deviceId, { type: "pause_preview" });
1101
1265
  this.log("info", `camera uplink: ${name} (${deviceId.slice(0, 8)})`);
1102
- ws.on("close", () => {
1266
+ ws.on("close", (code, reason) => {
1267
+ this.log("warn", `camera ws closed: code=${code} reason=${reason.toString("utf8") || "-"} (${name} ${deviceId.slice(0, 8)})`);
1103
1268
  if (this.cameras.get(deviceId)?.ws === ws) this.detachCamera(deviceId);
1104
1269
  });
1105
1270
  ws.on("message", (data, isBinary) => {
@@ -1113,6 +1278,7 @@ var ViewHub = class {
1113
1278
  }
1114
1279
  detachCamera(deviceId) {
1115
1280
  const cam = this.cameras.get(deviceId);
1281
+ this.log("warn", `detachCamera(${deviceId.slice(0, 8)}) had-camera=${!!cam}`);
1116
1282
  if (cam) this.cameras.delete(deviceId);
1117
1283
  if (this.activeDeviceId === deviceId) {
1118
1284
  const next = [...this.cameras.keys()].at(-1) ?? null;
@@ -1145,14 +1311,18 @@ var ViewHub = class {
1145
1311
  fps: msg.fps,
1146
1312
  ...msg.rotation !== void 0 ? { rotation: msg.rotation } : {}
1147
1313
  };
1148
- if (this.activeDeviceId === deviceId) this.broadcastToViews({
1149
- type: "frame_meta",
1150
- width: msg.width,
1151
- height: msg.height,
1152
- ...msg.rotation !== void 0 ? { rotation: msg.rotation } : {}
1153
- });
1314
+ if (this.activeDeviceId === deviceId) {
1315
+ this.markPreviewActive();
1316
+ this.broadcastToViews({
1317
+ type: "frame_meta",
1318
+ width: msg.width,
1319
+ height: msg.height,
1320
+ ...msg.rotation !== void 0 ? { rotation: msg.rotation } : {}
1321
+ });
1322
+ }
1154
1323
  break;
1155
1324
  case "bye":
1325
+ this.log("warn", `camera sent bye: ${deviceId.slice(0, 8)}`);
1156
1326
  this.detachCamera(deviceId);
1157
1327
  break;
1158
1328
  case "claim_active":
@@ -1188,12 +1358,50 @@ var ViewHub = class {
1188
1358
  cam.frameCount = 0;
1189
1359
  }
1190
1360
  if (this.activeDeviceId === deviceId) {
1361
+ this.markPreviewActive();
1191
1362
  for (const view of this.views) if (view.readyState === view.OPEN) view.send(frame, { binary: true });
1192
1363
  }
1193
1364
  }
1365
+ /** Flip the view-side preview state to on (once) when stream activity returns. */
1366
+ markPreviewActive() {
1367
+ if (this.previewOn) return;
1368
+ this.previewOn = true;
1369
+ this.broadcastToViews({
1370
+ type: "preview_state",
1371
+ on: true
1372
+ });
1373
+ }
1374
+ /**
1375
+
1376
+ * Watchdog: an open, ACTIVE camera uplink that has gone silent means the
1377
+
1378
+ * user turned preview off on the phone (the app keeps the WS while only
1379
+
1380
+ * stopping its frame pump). Tell views so they can clear the stale frame.
1381
+
1382
+ * Disconnected devices are NOT handled here — the `devices` broadcast owns
1383
+
1384
+ * the offline state on the view side.
1385
+
1386
+ */
1387
+ checkPreviewStall() {
1388
+ if (!this.previewOn) return;
1389
+ const active = this.activeCam();
1390
+ if (!active || active.ws.readyState !== active.ws.OPEN) return;
1391
+ if (Date.now() - active.lastFrameAt <= PREVIEW_STALL_MS) return;
1392
+ this.previewOn = false;
1393
+ this.broadcastToViews({
1394
+ type: "preview_state",
1395
+ on: false
1396
+ });
1397
+ this.log("info", `active preview stalled >${PREVIEW_STALL_MS}ms — notified views (preview off)`);
1398
+ }
1194
1399
  attachView(ws, hooks = {}) {
1195
1400
  this.views.add(ws);
1196
- ws.on("close", () => this.views.delete(ws));
1401
+ ws.on("close", (code, reason) => {
1402
+ this.log("warn", `view ws closed: code=${code} reason=${reason.toString("utf8") || "-"} (views left: ${this.views.size - 1})`);
1403
+ this.views.delete(ws);
1404
+ });
1197
1405
  ws.on("message", (data, isBinary) => {
1198
1406
  if (isBinary) return;
1199
1407
  const msg = parseViewClient(data.toString());
@@ -1221,7 +1429,11 @@ var ViewHub = class {
1221
1429
  paired: true
1222
1430
  }));
1223
1431
  this.broadcastDevicesTo(ws);
1224
- if (active?.lastFrame && ws.readyState === ws.OPEN) ws.send(active.lastFrame, { binary: true });
1432
+ ws.send(JSON.stringify({
1433
+ type: "preview_state",
1434
+ on: this.previewOn
1435
+ }));
1436
+ if (this.previewOn && active?.lastFrame && ws.readyState === ws.OPEN) ws.send(active.lastFrame, { binary: true });
1225
1437
  }
1226
1438
  viewCount() {
1227
1439
  return this.views.size;
@@ -1250,7 +1462,7 @@ var ViewHub = class {
1250
1462
  }
1251
1463
  pushActiveFrameToViews() {
1252
1464
  const active = this.activeCam();
1253
- if (active?.lastFrame) {
1465
+ if (this.previewOn && active?.lastFrame) {
1254
1466
  for (const view of this.views) if (view.readyState === view.OPEN) view.send(active.lastFrame, { binary: true });
1255
1467
  }
1256
1468
  if (active) this.broadcastToViews({
@@ -1359,6 +1571,7 @@ const dataDir = process.env.LENS_DATA_DIR ?? lensDataDir();
1359
1571
  const log = (level, msg) => console[level](`[phone-lens:dev] ${msg}`);
1360
1572
  const pairing = new PairingStore(config.pairing.codeTtlMs);
1361
1573
  const devices = new DeviceStore(dataDir);
1574
+ const appSettings = new AppSettingsStore(join(dataDir, "settings.json"), join(dataDir, "saved"));
1362
1575
  const hub = new ViewHub(config, (lvl, msg) => log(lvl, msg));
1363
1576
  const targets = new TargetTracker(config);
1364
1577
  const sink = new LoggingSink((lvl, msg) => log(lvl, msg));
@@ -1372,6 +1585,7 @@ const handle = await startLensServer({
1372
1585
  attachments: () => void 0,
1373
1586
  fallbackDir: join(dataDir, "uploads"),
1374
1587
  pendingDir: join(dataDir, "pending"),
1588
+ appSettings,
1375
1589
  log
1376
1590
  });
1377
1591
  const qr = await buildPairingQr(pairing.current().code, pairing.current().expiresAt, config);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;cAuBqB,SAAA,SAAkB,OAAA;;EAAlB,OAAA,MAAU,EAEhB,CAFgB,CAAA,GAAA,EAAA,GAAA,CAAA;EAAA,WAAA,CAAA,GAAA,EAAA,GAAA,EAAA,SAAA,EAAA,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;cAwBqB,SAAA,SAAkB,OAAA;;EAAlB,OAAA,MAAU,EAEhB,CAFgB,CAAA,GAAA,EAAA,GAAA,CAAA;EAAA,WAAA,CAAA,GAAA,EAAA,GAAA,EAAA,SAAA,EAAA,OAAA"}