@threadbase-sh/streamer 1.59.1 → 1.61.0

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/dist/cli.cjs CHANGED
@@ -31270,7 +31270,7 @@ var require_websocket = __commonJS({
31270
31270
  var http = require("http");
31271
31271
  var net = require("net");
31272
31272
  var tls = require("tls");
31273
- var { randomBytes: randomBytes5, createHash: createHash8 } = require("crypto");
31273
+ var { randomBytes: randomBytes5, createHash: createHash9 } = require("crypto");
31274
31274
  var { Duplex, Readable: Readable2 } = require("stream");
31275
31275
  var { URL: URL2 } = require("url");
31276
31276
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -31938,7 +31938,7 @@ var require_websocket = __commonJS({
31938
31938
  abortHandshake(websocket, socket, "Invalid Upgrade header");
31939
31939
  return;
31940
31940
  }
31941
- const digest = createHash8("sha1").update(key + GUID).digest("base64");
31941
+ const digest = createHash9("sha1").update(key + GUID).digest("base64");
31942
31942
  if (res.headers["sec-websocket-accept"] !== digest) {
31943
31943
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
31944
31944
  return;
@@ -32307,7 +32307,7 @@ var require_websocket_server = __commonJS({
32307
32307
  var EventEmitter5 = require("events");
32308
32308
  var http = require("http");
32309
32309
  var { Duplex } = require("stream");
32310
- var { createHash: createHash8 } = require("crypto");
32310
+ var { createHash: createHash9 } = require("crypto");
32311
32311
  var extension2 = require_extension();
32312
32312
  var PerMessageDeflate2 = require_permessage_deflate();
32313
32313
  var subprotocol2 = require_subprotocol();
@@ -32614,7 +32614,7 @@ var require_websocket_server = __commonJS({
32614
32614
  );
32615
32615
  }
32616
32616
  if (this._state > RUNNING) return abortHandshake(socket, 503);
32617
- const digest = createHash8("sha1").update(key + GUID).digest("base64");
32617
+ const digest = createHash9("sha1").update(key + GUID).digest("base64");
32618
32618
  const headers = [
32619
32619
  "HTTP/1.1 101 Switching Protocols",
32620
32620
  "Upgrade: websocket",
@@ -97360,6 +97360,81 @@ var init_platform = __esm({
97360
97360
  }
97361
97361
  });
97362
97362
 
97363
+ // src/server-identity.ts
97364
+ function serverIdentityKeyPath() {
97365
+ const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path3.join)((0, import_os5.homedir)(), ".threadbase");
97366
+ return (0, import_path3.join)(dir, "keys", "server-identity.key");
97367
+ }
97368
+ function loadOrCreateServerIdentity() {
97369
+ const path2 = serverIdentityKeyPath();
97370
+ let raw2;
97371
+ try {
97372
+ raw2 = (0, import_fs4.readFileSync)(path2, "utf-8");
97373
+ } catch (err) {
97374
+ if (err.code !== "ENOENT") throw err;
97375
+ return generateIdentity(path2);
97376
+ }
97377
+ let privateKey;
97378
+ try {
97379
+ privateKey = (0, import_crypto4.createPrivateKey)({ key: JSON.parse(raw2).key, format: "jwk" });
97380
+ } catch {
97381
+ throw new Error(
97382
+ `Server identity key at ${path2} could not be read. Refusing to generate a new one \u2014 that would invalidate every paired device. Repair or delete the file deliberately.`
97383
+ );
97384
+ }
97385
+ return { publicKey: publicKeyOf(privateKey), privateKey };
97386
+ }
97387
+ function serverIdentityPublicKey() {
97388
+ return loadOrCreateServerIdentity().publicKey;
97389
+ }
97390
+ function serverIdentityFingerprint(publicKeyBase64url) {
97391
+ const raw2 = Buffer.from(publicKeyBase64url, "base64url");
97392
+ const hex3 = (0, import_crypto4.createHash)("sha256").update(raw2).digest().subarray(0, 16).toString("hex");
97393
+ return hex3.match(/.{4}/g)?.join(" ") ?? hex3;
97394
+ }
97395
+ function currentServerIdentityFingerprint() {
97396
+ return serverIdentityFingerprint(serverIdentityPublicKey());
97397
+ }
97398
+ function generateIdentity(path2) {
97399
+ const { privateKey } = (0, import_crypto4.generateKeyPairSync)("x25519");
97400
+ const file2 = {
97401
+ v: IDENTITY_FILE_VERSION,
97402
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
97403
+ key: privateKey.export({ format: "jwk" })
97404
+ };
97405
+ (0, import_fs4.mkdirSync)((0, import_path3.dirname)(path2), { recursive: true, mode: 448 });
97406
+ const tmp = `${path2}.tmp`;
97407
+ (0, import_fs4.writeFileSync)(tmp, `${JSON.stringify(file2)}
97408
+ `, { encoding: "utf-8", mode: 384 });
97409
+ (0, import_fs4.chmodSync)(tmp, 384);
97410
+ (0, import_fs4.renameSync)(tmp, path2);
97411
+ const publicKey = publicKeyOf(privateKey);
97412
+ getLogger("identity").info(`Generated server identity key ${publicKey}`, {
97413
+ event: "identity.key_generated",
97414
+ path: path2
97415
+ });
97416
+ return { publicKey, privateKey };
97417
+ }
97418
+ function publicKeyOf(privateKey) {
97419
+ const jwk = (0, import_crypto4.createPublicKey)(privateKey).export({ format: "jwk" });
97420
+ if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
97421
+ throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
97422
+ }
97423
+ return jwk.x;
97424
+ }
97425
+ var import_crypto4, import_fs4, import_os5, import_path3, IDENTITY_FILE_VERSION;
97426
+ var init_server_identity = __esm({
97427
+ "src/server-identity.ts"() {
97428
+ "use strict";
97429
+ import_crypto4 = require("crypto");
97430
+ import_fs4 = require("fs");
97431
+ import_os5 = require("os");
97432
+ import_path3 = require("path");
97433
+ init_logger();
97434
+ IDENTITY_FILE_VERSION = 1;
97435
+ }
97436
+ });
97437
+
97363
97438
  // node_modules/fast-glob/out/utils/array.js
97364
97439
  var require_array = __commonJS({
97365
97440
  "node_modules/fast-glob/out/utils/array.js"(exports2) {
@@ -128273,6 +128348,43 @@ var init_repo = __esm({
128273
128348
  }
128274
128349
  });
128275
128350
 
128351
+ // cli/identity.ts
128352
+ var identity_exports = {};
128353
+ __export(identity_exports, {
128354
+ formatIdentityBanner: () => formatIdentityBanner,
128355
+ runIdentity: () => runIdentity
128356
+ });
128357
+ function runIdentity(deps) {
128358
+ const keyPath = (deps.keyPath ?? serverIdentityKeyPath)();
128359
+ const fingerprint2 = deps.fingerprint ?? currentServerIdentityFingerprint;
128360
+ let value;
128361
+ try {
128362
+ value = fingerprint2();
128363
+ } catch (err) {
128364
+ deps.log.error(
128365
+ `Could not read the server identity key: ${err instanceof Error ? err.message : String(err)}`
128366
+ );
128367
+ return 1;
128368
+ }
128369
+ deps.log.info(formatIdentityBanner(value, keyPath));
128370
+ return 0;
128371
+ }
128372
+ function formatIdentityBanner(fingerprint2, keyPath) {
128373
+ return [
128374
+ `Server identity (X25519, ${keyPath})`,
128375
+ "",
128376
+ ` ${fingerprint2}`,
128377
+ "",
128378
+ "Compare this with the fingerprint your phone shows when you add this server."
128379
+ ].join("\n");
128380
+ }
128381
+ var init_identity = __esm({
128382
+ "cli/identity.ts"() {
128383
+ "use strict";
128384
+ init_server_identity();
128385
+ }
128386
+ });
128387
+
128276
128388
  // cli/setKey.ts
128277
128389
  var setKey_exports = {};
128278
128390
  __export(setKey_exports, {
@@ -135627,67 +135739,7 @@ var PushRepository = class {
135627
135739
 
135628
135740
  // src/api/routes/misc.routes.ts
135629
135741
  init_logger();
135630
-
135631
- // src/server-identity.ts
135632
- var import_crypto4 = require("crypto");
135633
- var import_fs4 = require("fs");
135634
- var import_os5 = require("os");
135635
- var import_path3 = require("path");
135636
- init_logger();
135637
- var IDENTITY_FILE_VERSION = 1;
135638
- function serverIdentityKeyPath() {
135639
- const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path3.join)((0, import_os5.homedir)(), ".threadbase");
135640
- return (0, import_path3.join)(dir, "keys", "server-identity.key");
135641
- }
135642
- function loadOrCreateServerIdentity() {
135643
- const path2 = serverIdentityKeyPath();
135644
- let raw2;
135645
- try {
135646
- raw2 = (0, import_fs4.readFileSync)(path2, "utf-8");
135647
- } catch (err) {
135648
- if (err.code !== "ENOENT") throw err;
135649
- return generateIdentity(path2);
135650
- }
135651
- let privateKey;
135652
- try {
135653
- privateKey = (0, import_crypto4.createPrivateKey)({ key: JSON.parse(raw2).key, format: "jwk" });
135654
- } catch {
135655
- throw new Error(
135656
- `Server identity key at ${path2} could not be read. Refusing to generate a new one \u2014 that would invalidate every paired device. Repair or delete the file deliberately.`
135657
- );
135658
- }
135659
- return { publicKey: publicKeyOf(privateKey), privateKey };
135660
- }
135661
- function serverIdentityPublicKey() {
135662
- return loadOrCreateServerIdentity().publicKey;
135663
- }
135664
- function generateIdentity(path2) {
135665
- const { privateKey } = (0, import_crypto4.generateKeyPairSync)("x25519");
135666
- const file2 = {
135667
- v: IDENTITY_FILE_VERSION,
135668
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
135669
- key: privateKey.export({ format: "jwk" })
135670
- };
135671
- (0, import_fs4.mkdirSync)((0, import_path3.dirname)(path2), { recursive: true, mode: 448 });
135672
- const tmp = `${path2}.tmp`;
135673
- (0, import_fs4.writeFileSync)(tmp, `${JSON.stringify(file2)}
135674
- `, { encoding: "utf-8", mode: 384 });
135675
- (0, import_fs4.chmodSync)(tmp, 384);
135676
- (0, import_fs4.renameSync)(tmp, path2);
135677
- const publicKey = publicKeyOf(privateKey);
135678
- getLogger("identity").info(`Generated server identity key ${publicKey}`, {
135679
- event: "identity.key_generated",
135680
- path: path2
135681
- });
135682
- return { publicKey, privateKey };
135683
- }
135684
- function publicKeyOf(privateKey) {
135685
- const jwk = (0, import_crypto4.createPublicKey)(privateKey).export({ format: "jwk" });
135686
- if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
135687
- throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
135688
- }
135689
- return jwk.x;
135690
- }
135742
+ init_server_identity();
135691
135743
 
135692
135744
  // src/services/push/apnsClient.ts
135693
135745
  var import_node_crypto = require("crypto");
@@ -150263,6 +150315,9 @@ function seal(plaintext, recipientPublicKeyBase64) {
150263
150315
  };
150264
150316
  }
150265
150317
 
150318
+ // src/server.ts
150319
+ init_server_identity();
150320
+
150266
150321
  // src/server-wiring.ts
150267
150322
  var import_fs31 = require("fs");
150268
150323
 
@@ -155134,6 +155189,9 @@ function parseDirScanDebounceEnv(raw2) {
155134
155189
  return Number.isNaN(parsed) || parsed < 0 ? void 0 : parsed;
155135
155190
  }
155136
155191
 
155192
+ // cli/index.ts
155193
+ init_server_identity();
155194
+
155137
155195
  // src/updater/check-update.ts
155138
155196
  var import_semver = __toESM(require_semver2(), 1);
155139
155197
 
@@ -158775,6 +158833,7 @@ init_platform2();
158775
158833
  init_logger();
158776
158834
  init_protocol();
158777
158835
  init_socket();
158836
+ init_server_identity();
158778
158837
  var log11 = getLogger("prod");
158779
158838
  function clearSupervisorLogs() {
158780
158839
  let paths;
@@ -158827,6 +158886,7 @@ async function runProdStatus() {
158827
158886
  async function runProdDoctor(opts, deps = {}) {
158828
158887
  const findings = [];
158829
158888
  const repairs = [];
158889
+ let identity;
158830
158890
  let ptyHost;
158831
158891
  const marker = readMarker();
158832
158892
  if (marker && !marker.userHeld && !isPidAlive(marker.devPid)) {
@@ -158839,6 +158899,15 @@ async function runProdDoctor(opts, deps = {}) {
158839
158899
  if (!getSupervisor().isAgentLoaded()) {
158840
158900
  findings.push("launchd agent is not loaded \u2014 prod is fully down");
158841
158901
  }
158902
+ if (deps.serverIdentityFingerprint) {
158903
+ try {
158904
+ identity = deps.serverIdentityFingerprint();
158905
+ } catch (err) {
158906
+ findings.push(
158907
+ `server identity key at ${serverIdentityKeyPath()} is unreadable: ` + (err instanceof Error ? err.message : String(err))
158908
+ );
158909
+ }
158910
+ }
158842
158911
  const conflicts = detectConflictingAgents();
158843
158912
  for (const c of conflicts) {
158844
158913
  if (c.resolution === "uninstall-homebrew") {
@@ -158867,7 +158936,7 @@ async function runProdDoctor(opts, deps = {}) {
158867
158936
  );
158868
158937
  }
158869
158938
  }
158870
- return { findings, repairs, ...ptyHost ? { ptyHost } : {} };
158939
+ return { findings, repairs, ...identity ? { identity } : {}, ...ptyHost ? { ptyHost } : {} };
158871
158940
  }
158872
158941
  function toPowerShellLiteral(value) {
158873
158942
  return `'${value.replace(/'/g, "''")}'`;
@@ -159016,7 +159085,13 @@ function registerProdCommands(program3) {
159016
159085
  log11.info(what, void 0, "console");
159017
159086
  });
159018
159087
  prod.command("doctor").description("Detect stale markers, missing agents, and pty-host health").option("--fix", "Apply repairs (default is dry-run)", false).action(async (opts) => {
159019
- const r = await runProdDoctor({ fix: opts.fix === true });
159088
+ const r = await runProdDoctor(
159089
+ { fix: opts.fix === true },
159090
+ { serverIdentityFingerprint: currentServerIdentityFingerprint }
159091
+ );
159092
+ if (r.identity) {
159093
+ log11.info(`identity: ${r.identity}`, void 0, "console");
159094
+ }
159020
159095
  if (r.ptyHost) {
159021
159096
  log11.info(
159022
159097
  r.ptyHost.reachable ? `pty-host: reachable, protocol=${r.ptyHost.protocolVersion}, sessions=${r.ptyHost.sessionCount}` : `pty-host: unreachable (${r.ptyHost.error})`,
@@ -159408,6 +159483,16 @@ program2.command("pair").description("Print a pairing QR code (server must alrea
159408
159483
  const publicUrl = loadPublicUrl() ?? null;
159409
159484
  await printServerBanner({ port, apiKey, publicUrl, includeQr: true });
159410
159485
  });
159486
+ program2.command("identity").description("Print this server's identity fingerprint for out-of-band verification").action(async () => {
159487
+ const { runIdentity: runIdentity2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
159488
+ const code = runIdentity2({
159489
+ log: {
159490
+ info: (msg) => log14.info(msg, void 0, "console"),
159491
+ error: (msg) => log14.error(msg, void 0, "console")
159492
+ }
159493
+ });
159494
+ process.exit(code);
159495
+ });
159411
159496
  program2.command("set-key [key]").description("Set the streamer API key in ~/.threadbase/server.yaml").action(async (key) => {
159412
159497
  const { runSetKey: runSetKey2 } = await Promise.resolve().then(() => (init_setKey(), setKey_exports));
159413
159498
  const code = await runSetKey2(