@ours.network/messenger-server 1.0.31-nightly.23 → 1.0.31-nightly.24

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.
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  var define_MESSENGER_BUILD_INFO_default;
39
39
  var init_define_MESSENGER_BUILD_INFO = __esm({
40
40
  "<define:__MESSENGER_BUILD_INFO__>"() {
41
- define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.23", sha: "110c44f3d58518346a3e1afa5b070bd47cee862a", dirty: true };
41
+ define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.24", sha: "57c2b0c767bc8bd397c8cccebb781c4c76e70699", dirty: true };
42
42
  }
43
43
  });
44
44
 
@@ -10,7 +10,7 @@ import {
10
10
  publicEngineError,
11
11
  publicInternalError,
12
12
  reportFailure
13
- } from "./chunk-ZLMZUBJY.js";
13
+ } from "./chunk-QA42GHTR.js";
14
14
 
15
15
  // node_modules/bn.js/lib/bn.js
16
16
  var require_bn = __commonJS({
@@ -12117,7 +12117,7 @@ function startResourceObserver(client, identity, callbacks) {
12117
12117
  return {
12118
12118
  ready,
12119
12119
  get healthy() {
12120
- if (healthy && Date.now() - lastCoverage >= 6e3) {
12120
+ if (healthy && Date.now() - lastCoverage >= 45e3) {
12121
12121
  fail(new Error("resource source coverage expired"));
12122
12122
  controller.abort();
12123
12123
  }
@@ -12446,13 +12446,15 @@ var ResourceOutput = class {
12446
12446
  #closed = false;
12447
12447
  #releaseDrain;
12448
12448
  #abort = () => this.close();
12449
- send(envelope) {
12449
+ send(envelope, admitted = () => {
12450
+ }) {
12450
12451
  if (this.#closed) return Promise.reject(new Error("resource output closed"));
12451
12452
  const bytes = Buffer.byteLength(JSON.stringify(envelope));
12452
12453
  if (bytes > RESOURCE_ASSEMBLY_BYTES) return this.#fail(new Error("resource assembly limit exceeded"));
12453
12454
  if (this.#active && this.#pendingBytes + bytes > RESOURCE_PENDING_BYTES) {
12454
12455
  return this.#fail(new Error("resource pending limit exceeded"));
12455
12456
  }
12457
+ admitted();
12456
12458
  return new Promise((resolve4, reject) => {
12457
12459
  const item = { envelope, bytes, resolve: resolve4, reject };
12458
12460
  if (!this.#active) {
@@ -12484,7 +12486,7 @@ var ResourceOutput = class {
12484
12486
  try {
12485
12487
  while (this.#active && !this.#closed) {
12486
12488
  const item = this.#active;
12487
- for (const frame of resourceFrames(item.envelope)) {
12489
+ for (const frame of "views" in item.envelope ? [{ event: "resource-coverage", data: item.envelope }] : resourceFrames(item.envelope)) {
12488
12490
  if (this.#closed) break;
12489
12491
  const writable = this.response.write(`event: ${frame.event}
12490
12492
  data: ${JSON.stringify(frame.data)}
@@ -12553,11 +12555,12 @@ var ResourceConnections = class {
12553
12555
  const observer = startResourceObserver(this.deps.client, this.deps.identity, {
12554
12556
  committed: (frame) => {
12555
12557
  if (!connection || controller.signal.aborted) return;
12558
+ connection.source = frame;
12556
12559
  for (const view of connection.views.values()) {
12557
- if (frame.kind === "coverage") {
12558
- if (view.covered && !view.failed) void this.#emit(connection, view, "coverage", { source: view.covered }).catch(close);
12559
- } else if (!view.covered || frame.position > view.covered.position) this.#schedule(connection, view);
12560
+ if (!view.covered || frame.position > view.covered.position) this.#schedule(connection, view);
12560
12561
  }
12562
+ if (frame.kind === "coverage") void this.#coverage(connection, frame).catch(close);
12563
+ else this.#queueCoverage(connection);
12561
12564
  },
12562
12565
  failed: (error) => {
12563
12566
  if (!connection || controller.signal.aborted) return;
@@ -12583,7 +12586,7 @@ data: ${JSON.stringify({
12583
12586
  } else void Promise.allSettled(writes).then(close);
12584
12587
  }
12585
12588
  });
12586
- connection = { id, controller, output, views: /* @__PURE__ */ new Map(), epochs: /* @__PURE__ */ new Map(), observer, localRevision: 0, projectionTail: Promise.resolve(), close };
12589
+ connection = { id, controller, output, views: /* @__PURE__ */ new Map(), epochs: /* @__PURE__ */ new Map(), observer, localRevision: 0, projectionTail: Promise.resolve(), coverageQueued: false, close };
12587
12590
  this.#connections.set(id, connection);
12588
12591
  response.once("close", close);
12589
12592
  subscribeHumanProfile(this.deps.stateDir, this.deps.identityCid, () => {
@@ -12682,17 +12685,62 @@ data: ${JSON.stringify({
12682
12685
  view.controller.abort();
12683
12686
  view.rejectInitial(new ResourceBindingError(409, "resource binding retired"));
12684
12687
  }
12685
- #emit(connection, view, kind, payload) {
12688
+ #emit(connection, view, kind, payload, source) {
12686
12689
  return connection.output.send({
12687
12690
  protocolVersion: 1,
12688
12691
  producerGeneration: this.producerGeneration,
12689
12692
  viewId: view.descriptor.viewId,
12690
12693
  subscriptionEpoch: view.epoch,
12691
- sequence: ++view.sequence,
12694
+ sequence: view.sequence + 1,
12692
12695
  revision: view.revision,
12693
12696
  baselineId: view.baselineId,
12694
12697
  kind,
12695
- payload
12698
+ payload,
12699
+ ...source ? { source } : {}
12700
+ }, () => {
12701
+ view.sequence++;
12702
+ if (source || kind === "ready") view.qualifiedAt = Date.now();
12703
+ });
12704
+ }
12705
+ #queueCoverage(connection) {
12706
+ if (connection.coverageQueued) return;
12707
+ connection.coverageQueued = true;
12708
+ void (async () => {
12709
+ try {
12710
+ let tail;
12711
+ do {
12712
+ tail = connection.projectionTail;
12713
+ await tail;
12714
+ } while (tail !== connection.projectionTail && !connection.controller.signal.aborted);
12715
+ if (!connection.controller.signal.aborted && connection.source) {
12716
+ void this.#coverage(connection, connection.source).catch(connection.close);
12717
+ }
12718
+ } finally {
12719
+ connection.coverageQueued = false;
12720
+ }
12721
+ })().catch(connection.close);
12722
+ }
12723
+ #coverage(connection, source) {
12724
+ const now = Date.now();
12725
+ if (now - (connection.coverageAt ?? -Infinity) < 15e3 || !connection.observer.healthy) return Promise.resolve();
12726
+ const eligible = [...connection.views.values()].filter((view) => this.#current(connection, view) && !view.failed && !view.running && !view.dirty && view.payload !== void 0 && view.covered && view.covered.producerGeneration === source.producerGeneration && view.covered.position === source.position && (view.descriptor.resource !== "identity" || view.localRevision === connection.localRevision) && now - (view.qualifiedAt ?? 0) >= 15e3);
12727
+ if (!eligible.length) return Promise.resolve();
12728
+ const views = eligible.map((view) => ({
12729
+ protocolVersion: 1,
12730
+ producerGeneration: this.producerGeneration,
12731
+ viewId: view.descriptor.viewId,
12732
+ subscriptionEpoch: view.epoch,
12733
+ baselineId: view.baselineId,
12734
+ sequence: view.sequence + 1,
12735
+ revision: view.revision,
12736
+ source: view.covered
12737
+ }));
12738
+ return connection.output.send({ protocolVersion: 1, producerGeneration: this.producerGeneration, views }, () => {
12739
+ connection.coverageAt = now;
12740
+ for (const view of eligible) {
12741
+ view.sequence++;
12742
+ view.qualifiedAt = now;
12743
+ }
12696
12744
  });
12697
12745
  }
12698
12746
  #schedule(connection, view) {
@@ -12712,7 +12760,6 @@ data: ${JSON.stringify({
12712
12760
  try {
12713
12761
  if (view.dirty && this.#current(connection, view)) {
12714
12762
  view.dirty = false;
12715
- const localRevision = connection.localRevision;
12716
12763
  const captured = await captureResourceProjection(this.deps.client, this.deps.identity, async () => {
12717
12764
  const payload = await this.deps.project(view.descriptor, view.controller.signal);
12718
12765
  const delta = view.descriptor.resource === "messages" && view.payload !== void 0 ? await messageProjectionDelta(
@@ -12730,13 +12777,13 @@ data: ${JSON.stringify({
12730
12777
  const first = view.payload === void 0;
12731
12778
  const changed = first || JSON.stringify(captured.payload.payload) !== JSON.stringify(view.payload);
12732
12779
  view.covered = captured.source;
12733
- view.localRevision = localRevision;
12780
+ view.localRevision = connection.localRevision;
12734
12781
  if (changed) {
12735
12782
  view.revision++;
12736
12783
  if (first || view.descriptor.resource !== "messages") {
12737
- await this.#emit(connection, view, "snapshot", captured.payload.payload);
12784
+ await this.#emit(connection, view, "snapshot", captured.payload.payload, captured.source);
12738
12785
  } else {
12739
- await this.#emit(connection, view, "upsert", captured.payload.delta);
12786
+ await this.#emit(connection, view, "upsert", captured.payload.delta, captured.source);
12740
12787
  }
12741
12788
  if (!this.#current(connection, view)) return;
12742
12789
  view.payload = captured.payload.payload;
@@ -12752,8 +12799,6 @@ data: ${JSON.stringify({
12752
12799
  baselineId: view.baselineId,
12753
12800
  descriptor: view.descriptor
12754
12801
  });
12755
- } else {
12756
- await this.#emit(connection, view, "coverage", { source: captured.source });
12757
12802
  }
12758
12803
  }
12759
12804
  } catch (error) {
@@ -21564,6 +21609,13 @@ async function serveEvents(req, res, deps) {
21564
21609
  res.flushHeaders?.();
21565
21610
  const subscription = deps.events.subscribe(deps.sseQueueLimit ?? 64);
21566
21611
  let closed2 = false;
21612
+ let lastOutput = Date.now();
21613
+ const heartbeatMs = deps.sseHeartbeatMs ?? 2e4;
21614
+ const sendHint = (event, identity) => {
21615
+ const writable = writeSse(res, event, identity);
21616
+ lastOutput = Date.now();
21617
+ return writable;
21618
+ };
21567
21619
  let drainWait = null;
21568
21620
  let releaseDrain = null;
21569
21621
  const onDrain = () => {
@@ -21593,13 +21645,15 @@ async function serveEvents(req, res, deps) {
21593
21645
  req.once("close", close);
21594
21646
  res.once("close", close);
21595
21647
  const heartbeat = setInterval(() => {
21596
- if (!closed2 && !res.destroyed && !drainWait && !res.write(": keepalive\n\n")) {
21597
- void waitForDrain();
21648
+ if (!closed2 && !res.destroyed && !drainWait && Date.now() - lastOutput >= heartbeatMs) {
21649
+ const writable = res.write(": keepalive\n\n");
21650
+ lastOutput = Date.now();
21651
+ if (!writable) void waitForDrain();
21598
21652
  }
21599
- }, deps.sseHeartbeatMs ?? 2e4);
21653
+ }, heartbeatMs);
21600
21654
  heartbeat.unref?.();
21601
21655
  try {
21602
- if (!writeSse(res, { type: "sync_required", reason: "connected" }, deps.identityCid)) {
21656
+ if (!sendHint({ type: "sync_required", reason: "connected" }, deps.identityCid)) {
21603
21657
  await waitForDrain();
21604
21658
  }
21605
21659
  while (!closed2) {
@@ -21607,7 +21661,7 @@ async function serveEvents(req, res, deps) {
21607
21661
  if (event === null || closed2) break;
21608
21662
  if (drainWait) await drainWait;
21609
21663
  if (closed2 || res.destroyed) break;
21610
- if (!writeSse(res, event)) await waitForDrain();
21664
+ if (!sendHint(event)) await waitForDrain();
21611
21665
  }
21612
21666
  } finally {
21613
21667
  clearInterval(heartbeat);
@@ -22302,7 +22356,7 @@ import { randomBytes as randomBytes2 } from "node:crypto";
22302
22356
  // node_modules/@ours.network/sdk/dist/index.js
22303
22357
  init_define_MESSENGER_BUILD_INFO();
22304
22358
 
22305
- // node_modules/@ours.network/sdk/dist/chunk-AKRQMPU6.js
22359
+ // node_modules/@ours.network/sdk/dist/chunk-CJT4XK7F.js
22306
22360
  init_define_MESSENGER_BUILD_INFO();
22307
22361
 
22308
22362
  // node_modules/@ours.network/sdk/dist/chunk-4VLQOZT7.js
@@ -22410,9 +22464,9 @@ var I = 100 * 1024 * 1024;
22410
22464
  var K = { brokerUrl: "wss://broker1.ours.network", port: 3050, stateDir: E(T(), ".ours"), gcIntervalMs: 36e5, historyMaxBytes: I, apiVisibility: "owner", apiAuthMode: "legacy" };
22411
22465
  var $2 = "daemon-token";
22412
22466
 
22413
- // node_modules/@ours.network/sdk/dist/chunk-AKRQMPU6.js
22467
+ // node_modules/@ours.network/sdk/dist/chunk-CJT4XK7F.js
22414
22468
  import { homedir as Ie } from "node:os";
22415
- import { isAbsolute as be, join as _e, resolve as ce } from "node:path";
22469
+ import { isAbsolute as _e, join as be, resolve as ce } from "node:path";
22416
22470
  import { randomBytes as z } from "node:crypto";
22417
22471
  import { readFileSync as Se } from "node:fs";
22418
22472
  import { homedir as Ae } from "node:os";
@@ -22586,12 +22640,12 @@ var U = class {
22586
22640
  if (!u2.ok) throw new L(u2.status);
22587
22641
  if (!u2.body || !u2.headers.get("content-type")?.startsWith("text/event-stream")) throw new Error("Invalid resource stream response");
22588
22642
  l = u2.body.getReader();
22589
- let g2 = new TextDecoder("utf-8", { fatal: true }), y2 = "", I2 = 0, b2 = false, S2, T2 = -1;
22643
+ let g2 = new TextDecoder("utf-8", { fatal: true }), y2 = "", I2 = 0, _ = false, S2, T2 = -1;
22590
22644
  for (; ; ) {
22591
- let { value: _, done: x } = await l.read();
22645
+ let { value: b2, done: x } = await l.read();
22592
22646
  if (c(), x) throw new Error("Resource stream ended before disposal");
22593
- if (I2 += _.byteLength, I2 > 8 * 1024 * 1024) throw new Error("Resource live input exceeds limit");
22594
- for (y2 += g2.decode(_, { stream: true }); ; ) {
22647
+ if (I2 += b2.byteLength, I2 > 8 * 1024 * 1024) throw new Error("Resource live input exceeds limit");
22648
+ for (y2 += g2.decode(b2, { stream: true }); ; ) {
22595
22649
  c();
22596
22650
  let w2 = /\r?\n\r?\n/.exec(y2);
22597
22651
  if (!w2) break;
@@ -22606,8 +22660,8 @@ var U = class {
22606
22660
  let J = A2.kind;
22607
22661
  if (!["ready", "committed", "coverage", "resync"].includes(J)) throw new Error("Invalid resource frame kind");
22608
22662
  if (S2 !== void 0 && (A2.producerGeneration !== S2 || A2.position < T2)) throw new Error("Resource stream continuity changed");
22609
- if (S2 = A2.producerGeneration, T2 = A2.position, J === "ready") b2 = true, a(6e3);
22610
- else if (b2) a(6e3);
22663
+ if (S2 = A2.producerGeneration, T2 = A2.position, J === "ready") _ = true, a(45e3);
22664
+ else if (_) a(45e3);
22611
22665
  else if (J !== "resync") throw new Error("Resource mutation before ready");
22612
22666
  yield A2, c();
22613
22667
  }
@@ -22892,7 +22946,7 @@ function Te(e2 = process.env) {
22892
22946
  if (!e2.OURS_CONFIG.trim()) throw new R2("OURS_CONFIG must name a client profile");
22893
22947
  return ce(e2.OURS_CONFIG);
22894
22948
  }
22895
- return _e(e2.HOME || Ie(), ".ours-client", "profile.json");
22949
+ return be(e2.HOME || Ie(), ".ours-client", "profile.json");
22896
22950
  }
22897
22951
  function Oe(e2) {
22898
22952
  let t = (o) => {
@@ -22914,7 +22968,7 @@ function Oe(e2) {
22914
22968
  } catch {
22915
22969
  return t("endpoint must equal serverUrl/daemon");
22916
22970
  }
22917
- return typeof n.expectedInstanceId != "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(n.expectedInstanceId) ? t("expectedInstanceId must be a lowercase UUID") : typeof n.credentialPath != "string" || !be(n.credentialPath) || ce(n.credentialPath) !== n.credentialPath ? t("credentialPath must be an absolute normalized path") : Object.freeze({ serverUrl: r, endpoint: i, expectedInstanceId: n.expectedInstanceId, credentialPath: n.credentialPath });
22971
+ return typeof n.expectedInstanceId != "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(n.expectedInstanceId) ? t("expectedInstanceId must be a lowercase UUID") : typeof n.credentialPath != "string" || !_e(n.credentialPath) || ce(n.credentialPath) !== n.credentialPath ? t("credentialPath must be an absolute normalized path") : Object.freeze({ serverUrl: r, endpoint: i, expectedInstanceId: n.expectedInstanceId, credentialPath: n.credentialPath });
22918
22972
  }
22919
22973
  function le(e2 = process.env) {
22920
22974
  let t = Te(e2), n;
@@ -23080,15 +23134,15 @@ async function X(e2 = {}) {
23080
23134
  if (o !== void 0) throw new Error("Instance selection cannot use an injected fetch");
23081
23135
  let I2 = h2(g2);
23082
23136
  if (c !== void 0 && !Array.isArray(c)) throw new Error("Instance selection requires a nonempty capability list");
23083
- let b2 = [...c ?? ["external-sessions-v1"]];
23084
- if (!b2.length || b2.some((T2) => typeof T2 != "string" || !T2.trim())) throw new Error("Instance selection requires a nonempty capability list");
23085
- if (i === "external") b2.includes("external-sessions-v1") || b2.push("external-sessions-v1");
23086
- else if (b2.includes("external-sessions-v1")) throw new Error("External capabilities require explicit sessionMode: external; refusing local-mode fallback");
23137
+ let _ = [...c ?? ["external-sessions-v1"]];
23138
+ if (!_.length || _.some((T2) => typeof T2 != "string" || !T2.trim())) throw new Error("Instance selection requires a nonempty capability list");
23139
+ if (i === "external") _.includes("external-sessions-v1") || _.push("external-sessions-v1");
23140
+ else if (_.includes("external-sessions-v1")) throw new Error("External capabilities require explicit sessionMode: external; refusing local-mode fallback");
23087
23141
  {
23088
- let T2 = await globalThis.fetch(m2(I2, "/selection"), { signal: s ? AbortSignal.any([s, AbortSignal.timeout(5e3)]) : AbortSignal.timeout(5e3), redirect: "error" }), _ = await P(T2);
23089
- if (!T2.ok || !_ || Object.keys(_).length !== 3 || Object.keys(_).some((w2) => !["schema", "instanceId", "capabilities"].includes(w2)) || _.schema !== 1 || _.instanceId !== a) throw new Error("Daemon instance selection metadata is absent, incompatible or mismatched");
23090
- let x = _.capabilities;
23091
- if (!Array.isArray(x) || x.some((w2) => typeof w2 != "string") || b2.some((w2) => !x.includes(w2))) throw new Error("Daemon does not support the required capabilities; refusing local-mode fallback");
23142
+ let T2 = await globalThis.fetch(m2(I2, "/selection"), { signal: s ? AbortSignal.any([s, AbortSignal.timeout(5e3)]) : AbortSignal.timeout(5e3), redirect: "error" }), b2 = await P(T2);
23143
+ if (!T2.ok || !b2 || Object.keys(b2).length !== 3 || Object.keys(b2).some((w2) => !["schema", "instanceId", "capabilities"].includes(w2)) || b2.schema !== 1 || b2.instanceId !== a) throw new Error("Daemon instance selection metadata is absent, incompatible or mismatched");
23144
+ let x = b2.capabilities;
23145
+ if (!Array.isArray(x) || x.some((w2) => typeof w2 != "string") || _.some((w2) => !x.includes(w2))) throw new Error("Daemon does not support the required capabilities; refusing local-mode fallback");
23092
23146
  }
23093
23147
  let S2 = l === void 0 ? y2 : S(l);
23094
23148
  if (typeof S2 != "string" || !S2.trim()) throw new Error("Instance selection requires an explicit API token after daemon verification");
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  normalizeBasePath,
8
8
  operatorError,
9
9
  reportFailure
10
- } from "./chunks/chunk-ZLMZUBJY.js";
10
+ } from "./chunks/chunk-QA42GHTR.js";
11
11
 
12
12
  // src/cli.ts
13
13
  init_define_MESSENGER_BUILD_INFO();
@@ -131,7 +131,7 @@ async function main() {
131
131
  }
132
132
  if (process.argv.length !== 3) throw new ConfigurationError("serve does not accept command-line options");
133
133
  const cfg = loadConfig();
134
- const { start } = await import("./chunks/server-KK2HKPON.js");
134
+ const { start } = await import("./chunks/server-3KJ33EX5.js");
135
135
  const handle = await start(cfg, BUILD_INFO);
136
136
  let closing = false;
137
137
  const shutdown = (signal) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/messenger-server",
3
- "version": "1.0.31-nightly.23",
3
+ "version": "1.0.31-nightly.24",
4
4
  "description": "Self-hosted Messenger REST/SSE backend for one identity on a shared Ours daemon.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -41,14 +41,14 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@eslint-community/regexpp": "4.12.2",
44
- "@ours.network/sdk": "4.1.1-nightly.2",
44
+ "@ours.network/sdk": "4.1.1-nightly.3",
45
45
  "parse5": "^8.0.1",
46
46
  "web-push": "^3.6.7",
47
47
  "ws": "^8.21.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@ours.network/cli": "3.1.1-nightly.2",
51
- "@ours.network/daemon": "4.1.1-nightly.2",
50
+ "@ours.network/cli": "3.1.1-nightly.3",
51
+ "@ours.network/daemon": "4.1.1-nightly.3",
52
52
  "@playwright/test": "^1.62.1",
53
53
  "@types/node": "^20.14.0",
54
54
  "@types/web-push": "^3.6.4",