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

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.21", sha: "b475f9a436290a7a67351d7f7641a92685c9007d", dirty: true };
41
+ define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.23", sha: "110c44f3d58518346a3e1afa5b070bd47cee862a", 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-BB74SVTG.js";
13
+ } from "./chunk-ZLMZUBJY.js";
14
14
 
15
15
  // node_modules/bn.js/lib/bn.js
16
16
  var require_bn = __commonJS({
@@ -5905,8 +5905,8 @@ var require_ece = __commonJS({
5905
5905
  }
5906
5906
  return result;
5907
5907
  }
5908
- function isFunction(object) {
5909
- return typeof object === "function";
5908
+ function isFunction(object2) {
5909
+ return typeof object2 === "function";
5910
5910
  }
5911
5911
  module.exports = {
5912
5912
  decrypt,
@@ -6892,18 +6892,18 @@ var require_parse_proxy_response = __commonJS({
6892
6892
  else
6893
6893
  socket.once("readable", read);
6894
6894
  }
6895
- function cleanup() {
6895
+ function cleanup2() {
6896
6896
  socket.removeListener("end", onend);
6897
6897
  socket.removeListener("error", onerror);
6898
6898
  socket.removeListener("readable", read);
6899
6899
  }
6900
6900
  function onend() {
6901
- cleanup();
6901
+ cleanup2();
6902
6902
  debug("onend");
6903
6903
  reject(new Error("Proxy connection ended before receiving CONNECT response"));
6904
6904
  }
6905
6905
  function onerror(err) {
6906
- cleanup();
6906
+ cleanup2();
6907
6907
  debug("onerror %o", err);
6908
6908
  reject(err);
6909
6909
  }
@@ -6947,7 +6947,7 @@ var require_parse_proxy_response = __commonJS({
6947
6947
  }
6948
6948
  }
6949
6949
  debug("got proxy server response: %o %o", firstLine, headers);
6950
- cleanup();
6950
+ cleanup2();
6951
6951
  resolve4({
6952
6952
  connect: {
6953
6953
  statusCode,
@@ -11133,6 +11133,22 @@ init_define_MESSENGER_BUILD_INFO();
11133
11133
  init_define_MESSENGER_BUILD_INFO();
11134
11134
  import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from "node:fs";
11135
11135
  import { dirname } from "node:path";
11136
+ var object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
11137
+ var closed = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
11138
+ var text = (value, max) => typeof value === "string" && !!value.trim() && value.length <= max;
11139
+ var contactId = (value) => text(value, 512) && !/[\/\\\x00-\x1f\x7f]/.test(value) && value !== "." && value !== "..";
11140
+ var contactIdentity = (id) => /^[a-f0-9]{64}$/i.test(id) ? id.toUpperCase() : id;
11141
+ function contactTarget(value) {
11142
+ return object(value) && closed(value, ["kind", "contactId"]) && value.kind === "contact" && contactId(value.contactId);
11143
+ }
11144
+ function notificationInput(value) {
11145
+ if (!object(value) || !closed(value, ["eventId", "title", "body", "target", "anchor"]) || !text(value.eventId, 512) || !text(value.title, 160) || !text(value.body, 512) || !contactTarget(value.target)) return false;
11146
+ return value.anchor === void 0 || object(value.anchor) && closed(value.anchor, ["kind", "wireId"]) && value.anchor.kind === "messenger-message" && text(value.anchor.wireId, 1024) && !/[\x00-\x1f\x7f]/.test(value.anchor.wireId);
11147
+ }
11148
+ var cleanup = (value) => object(value) && closed(value, ["target"]) && contactTarget(value.target);
11149
+ var has = (entry, key) => Object.hasOwn(entry, key);
11150
+ var supported = (entry) => object(entry) && typeof entry.id === "string" && (has(entry, "operation") ? entry.operation === "delete-target" && cleanup(entry.value) : has(entry, "payload") ? notificationInput(entry.payload) : entry.targetVersion === 1);
11151
+ var same = (id) => (entry) => supported(entry) && entry.id === id;
11136
11152
  function producerConfig(env = process.env) {
11137
11153
  const origin = env.OURS_NOTIFICATIONS_ORIGIN, token = env.OURS_NOTIFICATIONS_PRODUCER_TOKEN;
11138
11154
  if (!origin && !token) return;
@@ -11152,10 +11168,10 @@ var NotificationOutbox = class {
11152
11168
  this.shouldRetire = shouldRetire;
11153
11169
  mkdirSync(dirname(file), { recursive: true, mode: 448 });
11154
11170
  const saved = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : { entries: [] };
11155
- this.entries = Array.isArray(saved) ? saved : saved.entries;
11171
+ if (!saved || typeof saved !== "object" || Array.isArray(saved) || !Array.isArray(saved.entries)) throw new Error("Unsupported notification outbox state");
11172
+ this.entries = saved.entries;
11156
11173
  this.cursor = saved.cursor;
11157
11174
  this.checkpoint = saved.checkpoint;
11158
- if (!Array.isArray(this.entries)) throw new Error("invalid notification outbox");
11159
11175
  this.timer = setInterval(() => {
11160
11176
  void this.drain();
11161
11177
  }, 3e3);
@@ -11192,18 +11208,19 @@ var NotificationOutbox = class {
11192
11208
  this.persist(this.entries, this.cursor, checkpoint);
11193
11209
  }
11194
11210
  enqueue(id, value, cursor = this.cursor) {
11195
- if (this.entries.some((e2) => e2.id === id)) {
11211
+ if (this.entries.some(same(id))) {
11196
11212
  if (cursor !== this.cursor) this.advance(cursor);
11197
11213
  return true;
11198
11214
  }
11199
- if (this.entries.length >= 2e3) return false;
11200
- this.persist([...this.entries, { id, value }], cursor);
11215
+ if (this.entries.filter(supported).length >= 2e3) return false;
11216
+ this.persist([...this.entries, { id, value, targetVersion: 1 }], cursor);
11201
11217
  return true;
11202
11218
  }
11203
- retireTarget(url, matches) {
11204
- const id = `delete-target:${url}`;
11205
- const retained = this.entries.filter((entry) => entry.operation === "delete-target" || !matches(entry.value, entry.payload));
11206
- if (!retained.some((entry) => entry.id === id)) retained.push({ id, value: { url }, operation: "delete-target" });
11219
+ retireTarget(target, matches) {
11220
+ if (!contactTarget(target)) throw new Error("Invalid notification target");
11221
+ const id = `delete-target:${contactIdentity(target.contactId)}`;
11222
+ const retained = this.entries.filter((entry) => !supported(entry) || entry.operation === "delete-target" || !matches(entry.value, entry.payload));
11223
+ if (!retained.some(same(id))) retained.push({ id, value: { target }, operation: "delete-target" });
11207
11224
  this.persist(retained);
11208
11225
  }
11209
11226
  drain() {
@@ -11214,26 +11231,28 @@ var NotificationOutbox = class {
11214
11231
  async drainOnce() {
11215
11232
  let attempts = 0;
11216
11233
  for (const entry of [...this.entries]) {
11234
+ if (!supported(entry)) continue;
11217
11235
  if (attempts >= 32) break;
11218
11236
  try {
11219
- if (!this.entries.some((current) => current.id === entry.id)) continue;
11237
+ if (!this.entries.some(same(entry.id))) continue;
11220
11238
  if (entry.operation === "delete-target" && this.shouldRetire) {
11221
- const retire = await this.shouldRetire(entry.value.url);
11239
+ const retire = await this.shouldRetire(entry.value.target);
11222
11240
  if (retire === void 0) continue;
11223
11241
  if (!retire) {
11224
11242
  attempts++;
11225
- this.persist(this.entries.filter((current) => current.id !== entry.id));
11243
+ this.persist(this.entries.filter((current) => !same(entry.id)(current)));
11226
11244
  continue;
11227
11245
  }
11228
11246
  }
11229
- attempts++;
11230
11247
  const value = entry.operation === "delete-target" ? entry.value : entry.payload ?? await this.project(entry.value, entry.id);
11231
- if (!this.entries.some((current) => current.id === entry.id)) continue;
11248
+ if (!this.entries.some(same(entry.id))) continue;
11232
11249
  if (value === void 0) {
11233
- this.persist(this.entries.filter((current) => current.id !== entry.id));
11250
+ this.persist(this.entries.filter((current) => !same(entry.id)(current)));
11234
11251
  continue;
11235
11252
  }
11236
- if (!entry.operation && !entry.payload) this.persist(this.entries.map((e2) => e2.id === entry.id ? { ...e2, payload: value } : e2));
11253
+ if (entry.operation ? !cleanup(value) : !notificationInput(value)) continue;
11254
+ attempts++;
11255
+ if (!entry.operation && !entry.payload) this.persist(this.entries.map((e2) => same(entry.id)(e2) ? { ...e2, payload: value } : e2));
11237
11256
  const response = await fetch(this.config.origin + (entry.operation === "delete-target" ? "/api/v1/delete-target" : "/api/v1/send"), {
11238
11257
  method: "POST",
11239
11258
  redirect: "error",
@@ -11243,7 +11262,7 @@ var NotificationOutbox = class {
11243
11262
  });
11244
11263
  await response.body?.cancel();
11245
11264
  if (!response.ok) throw new Error("notification service rejected event");
11246
- this.persist(this.entries.filter((e2) => e2.id !== entry.id));
11265
+ this.persist(this.entries.filter((e2) => !same(entry.id)(e2)));
11247
11266
  } catch {
11248
11267
  this.warn("Notification producer delivery pending; retrying from durable outbox");
11249
11268
  break;
@@ -11279,9 +11298,9 @@ var MAX_FUTURE_KIND_CHARACTERS = 80;
11279
11298
  var MAX_FUTURE_TEXT_CHARACTERS = 1e3;
11280
11299
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11281
11300
  var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
11282
- function parseRoomBodyShape(text, requireSignature) {
11283
- if (typeof text !== "string") return null;
11284
- const trimmed = text.trim();
11301
+ function parseRoomBodyShape(text2, requireSignature) {
11302
+ if (typeof text2 !== "string") return null;
11303
+ const trimmed = text2.trim();
11285
11304
  if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
11286
11305
  let parsed;
11287
11306
  try {
@@ -11313,7 +11332,7 @@ function renderRoomMessage(body) {
11313
11332
  if (!isObject(body)) return null;
11314
11333
  const kind = isNonEmptyString(body.kind) ? body.kind : "";
11315
11334
  const author = authorOf(body);
11316
- const text = typeof body.text === "string" ? body.text : "";
11335
+ const text2 = typeof body.text === "string" ? body.text : "";
11317
11336
  const version = Number.isSafeInteger(body.briefing_version) && body.briefing_version > 0 ? body.briefing_version : null;
11318
11337
  const common = {
11319
11338
  roomName: normalizedRoomName(body.room_name) ?? "",
@@ -11321,18 +11340,18 @@ function renderRoomMessage(body) {
11321
11340
  };
11322
11341
  switch (kind) {
11323
11342
  case "room_msg":
11324
- return author.role === ROOM_VOICE_ROLE ? systemLine(kind, "Room message", text || "Room notice", { ...common, authoredBy: author.name }) : { variant: "chat", kind, author: author.name || "Unknown member", role: author.role, label: "", text, ...common };
11343
+ return author.role === ROOM_VOICE_ROLE ? systemLine(kind, "Room message", text2 || "Room notice", { ...common, authoredBy: author.name }) : { variant: "chat", kind, author: author.name || "Unknown member", role: author.role, label: "", text: text2, ...common };
11325
11344
  case "room_briefing":
11326
11345
  return systemLine(
11327
11346
  kind,
11328
11347
  version === null ? "Room briefing" : `Room briefing \xB7 v${version}`,
11329
- text || "The room briefing was updated.",
11348
+ text2 || "The room briefing was updated.",
11330
11349
  { ...common, authoredBy: author.name, presentation: "briefing" }
11331
11350
  );
11332
11351
  case "room_role_briefing": {
11333
11352
  const role = isNonEmptyString(body.briefing_role) ? body.briefing_role : "";
11334
11353
  const label = ["Role briefing", role, version === null ? "" : `v${version}`].filter(Boolean).join(" \xB7 ");
11335
- return systemLine(kind, label, text || "Your role briefing was updated.", {
11354
+ return systemLine(kind, label, text2 || "Your role briefing was updated.", {
11336
11355
  ...common,
11337
11356
  authoredBy: author.name,
11338
11357
  presentation: "role",
@@ -11347,7 +11366,7 @@ function renderRoomMessage(body) {
11347
11366
  isNonEmptyString(membership.role) ? `Role: ${membership.role}` : "",
11348
11367
  Number.isSafeInteger(membership.epoch) && membership.epoch >= 0 ? `Epoch: ${membership.epoch}` : ""
11349
11368
  ].filter(Boolean);
11350
- return systemLine(kind, "Membership", text || membershipFallback(membership), {
11369
+ return systemLine(kind, "Membership", text2 || membershipFallback(membership), {
11351
11370
  ...common,
11352
11371
  authoredBy: author.name,
11353
11372
  presentation: "membership",
@@ -11376,23 +11395,23 @@ function renderRoomMessage(body) {
11376
11395
  details: ["Status: Removed"]
11377
11396
  });
11378
11397
  default:
11379
- return text ? {
11398
+ return text2 ? {
11380
11399
  variant: author.role && author.role !== ROOM_VOICE_ROLE ? "chat" : "system",
11381
11400
  kind,
11382
11401
  author: author.role && author.role !== ROOM_VOICE_ROLE ? author.name || "Unknown member" : "",
11383
11402
  role: author.role && author.role !== ROOM_VOICE_ROLE ? author.role : "",
11384
11403
  label: author.role && author.role !== ROOM_VOICE_ROLE ? "" : humanizeRoomKind(kind),
11385
- text: boundedFutureText(text),
11404
+ text: boundedFutureText(text2),
11386
11405
  ...common
11387
11406
  } : systemLine(kind, humanizeRoomKind(kind), `${humanizeRoomKind(kind)} from the room.`, common);
11388
11407
  }
11389
11408
  }
11390
- function roomLineForContact(announcedContact, text) {
11409
+ function roomLineForContact(announcedContact, text2) {
11391
11410
  if (!isCoworkRoomContact(announcedContact)) return null;
11392
11411
  const expectedRoomId = currentRoomMetadata(announcedContact)?.roomId ?? null;
11393
11412
  const expectedLegacyRoomId = legacyRoomId(announcedContact);
11394
11413
  const namedRoom = namedRoomLabel(announcedContact);
11395
- const body = parseRoomBodyShape(text, expectedRoomId === null && namedRoom === null);
11414
+ const body = parseRoomBodyShape(text2, expectedRoomId === null && namedRoom === null);
11396
11415
  if (body !== null && expectedRoomId !== null && body.room_id !== expectedRoomId) {
11397
11416
  return rejectedEnvelopeLine("Room provenance mismatch");
11398
11417
  }
@@ -11432,12 +11451,12 @@ function isCoworkRoomContact(announced) {
11432
11451
  const v04 = identity.startsWith(ROOM_IDENTITY_PREFIX) && identity.slice(ROOM_IDENTITY_PREFIX.length).trim().length > 0;
11433
11452
  return v04 || namedRoomLabel(announced) !== null || currentRoomMetadata(announced) !== null || legacyRoomId(announced) !== null;
11434
11453
  }
11435
- function contactMessagePreview(announcedContact, text) {
11436
- const line = roomLineForContact(announcedContact, text);
11437
- return line ? roomMessagePreview(line) : String(text ?? "");
11454
+ function contactMessagePreview(announcedContact, text2) {
11455
+ const line = roomLineForContact(announcedContact, text2);
11456
+ return line ? roomMessagePreview(line) : String(text2 ?? "");
11438
11457
  }
11439
- function systemLine(kind, label, text, extra = {}) {
11440
- return { variant: "system", kind, author: "", role: "", label, text, ...extra };
11458
+ function systemLine(kind, label, text2, extra = {}) {
11459
+ return { variant: "system", kind, author: "", role: "", label, text: text2, ...extra };
11441
11460
  }
11442
11461
  function humanizeValue(value) {
11443
11462
  return String(value).replace(/_/g, " ").replace(/^./, (character) => character.toUpperCase());
@@ -11469,12 +11488,12 @@ function membershipFallback(membership) {
11469
11488
  function roomMessagePreview(line) {
11470
11489
  if (!line) return "";
11471
11490
  const lead = line.variant === "chat" ? line.author : line.label;
11472
- const text = collapse(line.text);
11473
- if (!lead) return text;
11474
- return text ? `${lead} \xB7 ${text}` : lead;
11491
+ const text2 = collapse(line.text);
11492
+ if (!lead) return text2;
11493
+ return text2 ? `${lead} \xB7 ${text2}` : lead;
11475
11494
  }
11476
- function collapse(text) {
11477
- return String(text ?? "").replace(/\s+/g, " ").trim();
11495
+ function collapse(text2) {
11496
+ return String(text2 ?? "").replace(/\s+/g, " ").trim();
11478
11497
  }
11479
11498
  function roomContactLabel(announced) {
11480
11499
  const identity = String(announced ?? "").trim();
@@ -11508,7 +11527,10 @@ function notificationKind(record) {
11508
11527
  return "file";
11509
11528
  }
11510
11529
  async function projectPushEvent(client, job) {
11511
- const url = `/chats/${encodeURIComponent(job.contactId)}#chat-message-${encodeURIComponent(job.wireId)}`;
11530
+ if (!contactId(job.contactId) || !bounded(job.wireId, 1024) || /[\x00-\x1f\x7f]/.test(job.wireId))
11531
+ throw new Error("invalid push destination");
11532
+ const target = { kind: "contact", contactId: job.contactId };
11533
+ const anchor = { kind: "messenger-message", wireId: job.wireId };
11512
11534
  if (job.kind === "message") {
11513
11535
  if (!client.getHistoryItem) throw new Error("message projection is unavailable");
11514
11536
  const message = await client.getHistoryItem({ wire_id: job.wireId });
@@ -11523,7 +11545,8 @@ async function projectPushEvent(client, job) {
11523
11545
  body: contactMessagePreview(announcedSender, message.text),
11524
11546
  contact_id: job.contactId,
11525
11547
  wire_id: job.wireId,
11526
- url
11548
+ target,
11549
+ anchor
11527
11550
  };
11528
11551
  }
11529
11552
  if (!client.getFileInfo) throw new Error("file projection is unavailable");
@@ -11540,7 +11563,8 @@ async function projectPushEvent(client, job) {
11540
11563
  body: `${label}: ${file.filename}`,
11541
11564
  contact_id: job.contactId,
11542
11565
  wire_id: job.wireId,
11543
- url
11566
+ target,
11567
+ anchor
11544
11568
  };
11545
11569
  }
11546
11570
  var PushDeliveryQueue = class {
@@ -11583,7 +11607,7 @@ var PushDeliveryQueue = class {
11583
11607
  return this.admit(record).status === "queued";
11584
11608
  }
11585
11609
  admit(record, notificationPageNextIndex) {
11586
- if (!bounded(record.sender_id, 512) || !bounded(record.wire_id, 1024)) return { status: "no_targets" };
11610
+ if (!contactId(record.sender_id) || !bounded(record.wire_id, 1024) || /[\x00-\x1f\x7f]/.test(record.wire_id)) return { status: "no_targets" };
11587
11611
  const kind = notificationKind(record);
11588
11612
  if (!kind || !this.store.hasBindings()) return { status: "no_targets" };
11589
11613
  const admission = this.store.admitJob({
@@ -11678,19 +11702,6 @@ var PushDeliveryQueue = class {
11678
11702
  };
11679
11703
 
11680
11704
  // src/notification-producer.ts
11681
- function contactTargetId(path2) {
11682
- try {
11683
- if (typeof path2 !== "string" || /[\\\x00-\x20]|%(?:2f|5c|00|0[ad])/i.test(path2)) return null;
11684
- const match2 = /^\/fleet\/chats\/contacts\/([^/?#]+)(?:[?#]|$)/.exec(path2);
11685
- if (!match2) return null;
11686
- const url = new URL(path2, "https://ours.invalid");
11687
- if (["source", "chat", "task", "detail"].some((key) => url.searchParams.has(key))) return null;
11688
- const id = decodeURIComponent(match2[1]);
11689
- return id && id !== "." && id !== ".." && !/[\x00-\x1f\x7f]/.test(id) ? id : null;
11690
- } catch {
11691
- return null;
11692
- }
11693
- }
11694
11705
  var MessengerNotificationProducer = class {
11695
11706
  constructor(stateDir, config, identityCid, client, warn) {
11696
11707
  this.identityCid = identityCid;
@@ -11698,7 +11709,7 @@ var MessengerNotificationProducer = class {
11698
11709
  if (!client.listContacts) return void 0;
11699
11710
  const view = await client.listContacts();
11700
11711
  if (!Array.isArray(view.contacts) || !Array.isArray(view.pending) || [...view.contacts, ...view.pending].some((contact) => typeof contact?.container_id !== "string")) throw Error("Contact inventory unavailable");
11701
- return [...view.contacts, ...view.pending].some((contact) => contact.container_id.toLowerCase() === cid.toLowerCase());
11712
+ return [...view.contacts, ...view.pending].some((contact) => contactIdentity(contact.container_id) === contactIdentity(cid));
11702
11713
  };
11703
11714
  this.outbox = new NotificationOutbox(join(stateDir, "notification-outbox.json"), config, async (value, eventId) => {
11704
11715
  const record = value;
@@ -11713,18 +11724,21 @@ var MessengerNotificationProducer = class {
11713
11724
  eventId,
11714
11725
  title: event.title.slice(0, 160),
11715
11726
  body: event.body.slice(0, 512) || "New message",
11716
- url: `/fleet/chats/contacts/${encodeURIComponent(record.sender_id)}#chat-message-${encodeURIComponent(record.wire_id)}`
11727
+ target: { kind: "contact", contactId: record.sender_id },
11728
+ anchor: { kind: "messenger-message", wireId: record.wire_id }
11717
11729
  };
11718
- }, warn, async (url) => {
11719
- const cid = contactTargetId(url);
11720
- if (!cid) return void 0;
11721
- return await knownContact(cid) !== true;
11730
+ }, warn, async (target) => {
11731
+ if (!contactTarget(target)) return void 0;
11732
+ const known = await knownContact(target.contactId);
11733
+ return known === void 0 ? void 0 : !known;
11722
11734
  });
11723
11735
  }
11724
11736
  outbox;
11725
11737
  admit(record) {
11726
- if (!["message_received", "file_received"].includes(String(record.event)) || typeof record.sender_id !== "string" || record.sender_id.length > 512 || typeof record.wire_id !== "string" || record.wire_id.length > 1024) return { status: "no_targets" };
11727
- const admitted = this.outbox.enqueue(`${this.identityCid}:${record.wire_id}:${record.event}`, {
11738
+ if (!["message_received", "file_received"].includes(String(record.event)) || !contactId(record.sender_id) || typeof record.wire_id !== "string" || !record.wire_id.trim() || record.wire_id.length > 1024 || /[\x00-\x1f\x7f]/.test(record.wire_id)) return { status: "no_targets" };
11739
+ const eventId = `${this.identityCid}:${record.wire_id}:${record.event}`;
11740
+ if (eventId.length > 512) return { status: "no_targets" };
11741
+ const admitted = this.outbox.enqueue(eventId, {
11728
11742
  event: record.event,
11729
11743
  sender_id: record.sender_id,
11730
11744
  wire_id: record.wire_id,
@@ -11733,12 +11747,13 @@ var MessengerNotificationProducer = class {
11733
11747
  return { status: admitted ? "queued" : "saturated" };
11734
11748
  }
11735
11749
  retireContact(cid) {
11736
- const url = `/fleet/chats/contacts/${encodeURIComponent(cid)}`;
11737
- this.outbox.retireTarget(url, (value, payload) => {
11750
+ if (!contactId(cid)) throw new Error("Invalid notification contact");
11751
+ const target = { kind: "contact", contactId: cid };
11752
+ this.outbox.retireTarget(target, (value, payload) => {
11738
11753
  const record = value;
11739
- if (typeof record?.sender_id === "string" && record.sender_id.toLowerCase() === cid.toLowerCase()) return true;
11754
+ if (typeof record?.sender_id === "string" && contactIdentity(record.sender_id) === contactIdentity(cid)) return true;
11740
11755
  if (!payload) return false;
11741
- return contactTargetId(payload.url)?.toLowerCase() === cid.toLowerCase();
11756
+ return contactIdentity(payload.target.contactId) === contactIdentity(cid);
11742
11757
  });
11743
11758
  void this.outbox.drain();
11744
11759
  }
@@ -12516,7 +12531,7 @@ var ResourceConnections = class {
12516
12531
  open(response) {
12517
12532
  const id = randomUUID2(), controller = new AbortController();
12518
12533
  let resolveClosed;
12519
- const closed = new Promise((resolve4) => {
12534
+ const closed2 = new Promise((resolve4) => {
12520
12535
  resolveClosed = resolve4;
12521
12536
  });
12522
12537
  let connection;
@@ -12587,7 +12602,7 @@ data: ${JSON.stringify({
12587
12602
  })}
12588
12603
 
12589
12604
  `);
12590
- return { connectionId: id, close, closed };
12605
+ return { connectionId: id, close, closed: closed2 };
12591
12606
  }
12592
12607
  async register(connectionId, viewId, epoch, input, signal) {
12593
12608
  const connection = this.#get(connectionId);
@@ -17519,22 +17534,22 @@ var defaultTreeAdapter = {
17519
17534
  node.parentNode = null;
17520
17535
  }
17521
17536
  },
17522
- insertText(parentNode, text) {
17537
+ insertText(parentNode, text2) {
17523
17538
  if (parentNode.childNodes.length > 0) {
17524
17539
  const prevNode = parentNode.childNodes[parentNode.childNodes.length - 1];
17525
17540
  if (defaultTreeAdapter.isTextNode(prevNode)) {
17526
- prevNode.value += text;
17541
+ prevNode.value += text2;
17527
17542
  return;
17528
17543
  }
17529
17544
  }
17530
- defaultTreeAdapter.appendChild(parentNode, defaultTreeAdapter.createTextNode(text));
17545
+ defaultTreeAdapter.appendChild(parentNode, defaultTreeAdapter.createTextNode(text2));
17531
17546
  },
17532
- insertTextBefore(parentNode, text, referenceNode) {
17547
+ insertTextBefore(parentNode, text2, referenceNode) {
17533
17548
  const prevNode = parentNode.childNodes[parentNode.childNodes.indexOf(referenceNode) - 1];
17534
17549
  if (prevNode && defaultTreeAdapter.isTextNode(prevNode)) {
17535
- prevNode.value += text;
17550
+ prevNode.value += text2;
17536
17551
  } else {
17537
- defaultTreeAdapter.insertBefore(parentNode, defaultTreeAdapter.createTextNode(text), referenceNode);
17552
+ defaultTreeAdapter.insertBefore(parentNode, defaultTreeAdapter.createTextNode(text2), referenceNode);
17538
17553
  }
17539
17554
  },
17540
17555
  adoptAttributes(recipient, attrs) {
@@ -21548,7 +21563,7 @@ async function serveEvents(req, res, deps) {
21548
21563
  });
21549
21564
  res.flushHeaders?.();
21550
21565
  const subscription = deps.events.subscribe(deps.sseQueueLimit ?? 64);
21551
- let closed = false;
21566
+ let closed2 = false;
21552
21567
  let drainWait = null;
21553
21568
  let releaseDrain = null;
21554
21569
  const onDrain = () => {
@@ -21566,8 +21581,8 @@ async function serveEvents(req, res, deps) {
21566
21581
  return drainWait;
21567
21582
  };
21568
21583
  const close = () => {
21569
- if (closed) return;
21570
- closed = true;
21584
+ if (closed2) return;
21585
+ closed2 = true;
21571
21586
  subscription.close();
21572
21587
  res.removeListener("drain", onDrain);
21573
21588
  const release = releaseDrain;
@@ -21578,7 +21593,7 @@ async function serveEvents(req, res, deps) {
21578
21593
  req.once("close", close);
21579
21594
  res.once("close", close);
21580
21595
  const heartbeat = setInterval(() => {
21581
- if (!closed && !res.destroyed && !drainWait && !res.write(": keepalive\n\n")) {
21596
+ if (!closed2 && !res.destroyed && !drainWait && !res.write(": keepalive\n\n")) {
21582
21597
  void waitForDrain();
21583
21598
  }
21584
21599
  }, deps.sseHeartbeatMs ?? 2e4);
@@ -21587,11 +21602,11 @@ async function serveEvents(req, res, deps) {
21587
21602
  if (!writeSse(res, { type: "sync_required", reason: "connected" }, deps.identityCid)) {
21588
21603
  await waitForDrain();
21589
21604
  }
21590
- while (!closed) {
21605
+ while (!closed2) {
21591
21606
  const event = await subscription.next();
21592
- if (event === null || closed) break;
21607
+ if (event === null || closed2) break;
21593
21608
  if (drainWait) await drainWait;
21594
- if (closed || res.destroyed) break;
21609
+ if (closed2 || res.destroyed) break;
21595
21610
  if (!writeSse(res, event)) await waitForDrain();
21596
21611
  }
21597
21612
  } finally {
@@ -21679,22 +21694,22 @@ var ROUTES = {
21679
21694
  }
21680
21695
  const controller = new AbortController();
21681
21696
  let installed = false;
21682
- const cleanup = () => {
21697
+ const cleanup2 = () => {
21683
21698
  req.removeListener("aborted", abort);
21684
21699
  res.removeListener("close", responseClosed);
21685
- res.removeListener("finish", cleanup);
21700
+ res.removeListener("finish", cleanup2);
21686
21701
  };
21687
21702
  const abort = () => {
21688
21703
  controller.abort();
21689
21704
  if (installed) resourceService(deps).remove(connectionId, viewId, epoch);
21690
- cleanup();
21705
+ cleanup2();
21691
21706
  };
21692
21707
  req.once("aborted", abort);
21693
21708
  const responseClosed = () => {
21694
21709
  if (!res.writableFinished) abort();
21695
21710
  };
21696
21711
  res.once("close", responseClosed);
21697
- res.once("finish", cleanup);
21712
+ res.once("finish", cleanup2);
21698
21713
  try {
21699
21714
  const registered = await resourceService(deps).register(connectionId, viewId, epoch, body.descriptor, controller.signal);
21700
21715
  installed = true;
@@ -21707,7 +21722,7 @@ var ROUTES = {
21707
21722
  if (controller.signal.aborted) return void 0;
21708
21723
  throw error;
21709
21724
  } finally {
21710
- if (controller.signal.aborted || res.writableFinished || res.destroyed) cleanup();
21725
+ if (controller.signal.aborted || res.writableFinished || res.destroyed) cleanup2();
21711
21726
  }
21712
21727
  },
21713
21728
  "DELETE /api/resource-subscriptions": async ({ deps, body }) => {
@@ -24438,18 +24453,18 @@ async function startStartupProbe(cfg, buildInfo) {
24438
24453
  const port = await new Promise((resolveReady, rejectReady) => {
24439
24454
  const onMessage = (message) => {
24440
24455
  if (message?.type !== "listening" || !Number.isInteger(message.port)) return;
24441
- cleanup();
24456
+ cleanup2();
24442
24457
  resolveReady(message.port);
24443
24458
  };
24444
24459
  const onError = (error) => {
24445
- cleanup();
24460
+ cleanup2();
24446
24461
  rejectReady(error);
24447
24462
  };
24448
24463
  const onExit = (code) => {
24449
- cleanup();
24464
+ cleanup2();
24450
24465
  rejectReady(failure ?? new Error(`startup readiness worker exited before listen (code ${code})`));
24451
24466
  };
24452
- const cleanup = () => {
24467
+ const cleanup2 = () => {
24453
24468
  worker.off("message", onMessage);
24454
24469
  worker.off("error", onError);
24455
24470
  worker.off("exit", onExit);
@@ -24468,18 +24483,18 @@ async function startStartupProbe(cfg, buildInfo) {
24468
24483
  await new Promise((resolveClose, rejectClose) => {
24469
24484
  const onMessage = (message) => {
24470
24485
  if (message?.type !== "closed") return;
24471
- cleanup();
24486
+ cleanup2();
24472
24487
  resolveClose();
24473
24488
  };
24474
24489
  const onError = (error) => {
24475
- cleanup();
24490
+ cleanup2();
24476
24491
  rejectClose(error);
24477
24492
  };
24478
24493
  const onExit = (code) => {
24479
- cleanup();
24494
+ cleanup2();
24480
24495
  rejectClose(failure ?? new Error(`startup readiness worker exited during close (code ${code})`));
24481
24496
  };
24482
- const cleanup = () => {
24497
+ const cleanup2 = () => {
24483
24498
  worker.off("message", onMessage);
24484
24499
  worker.off("error", onError);
24485
24500
  worker.off("exit", onExit);
@@ -24506,7 +24521,7 @@ async function start(cfg, buildInfo, dependencies = DEFAULT_START_DEPENDENCIES)
24506
24521
  let presenceServer;
24507
24522
  const pendingHandlers = /* @__PURE__ */ new Set();
24508
24523
  let closePromise;
24509
- const cleanup = () => {
24524
+ const cleanup2 = () => {
24510
24525
  closePromise ??= (async () => {
24511
24526
  await presenceServer?.close().catch((error) => reportFailure(log.warn, "presence close", error));
24512
24527
  presenceServer = void 0;
@@ -24620,10 +24635,10 @@ async function start(cfg, buildInfo, dependencies = DEFAULT_START_DEPENDENCIES)
24620
24635
  });
24621
24636
  });
24622
24637
  log.info("service ready");
24623
- return { port, runtime, close: cleanup };
24638
+ return { port, runtime, close: cleanup2 };
24624
24639
  } catch (error) {
24625
24640
  try {
24626
- await cleanup();
24641
+ await cleanup2();
24627
24642
  } catch (cleanupError) {
24628
24643
  reportFailure(log.warn, "startup rollback", cleanupError);
24629
24644
  }
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  normalizeBasePath,
8
8
  operatorError,
9
9
  reportFailure
10
- } from "./chunks/chunk-BB74SVTG.js";
10
+ } from "./chunks/chunk-ZLMZUBJY.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-BHTSTSPS.js");
134
+ const { start } = await import("./chunks/server-KK2HKPON.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.21",
3
+ "version": "1.0.31-nightly.23",
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",
@@ -33,7 +33,7 @@
33
33
  "typecheck": "tsc -p tsconfig.json --noEmit",
34
34
  "test:startup": "node --import tsx tests/startup-readiness.test.mjs",
35
35
  "test:runtime-catalog-candidate": "node --import tsx tests/runtime-command-catalog-reopen-e2e.test.mjs",
36
- "test:offline": "for f in tests/resource-views.test.mjs tests/resource-connections.test.mjs tests/resource-http.test.mjs tests/workspace-profile-preservation.test.mjs tests/notification-producer.test.mjs tests/notification-contact-retirement.test.mjs tests/workspace-root-retirement.test.mjs tests/base-path.test.mjs tests/build-epoch.test.mjs tests/owner-session.test.mjs tests/v1-runtime.test.mjs tests/typed-commands-core.test.mjs tests/shared-daemon-contract.test.mjs tests/room-envelope-matrix.test.mjs tests/bundle-smoke.test.mjs tests/build-metadata.test.mjs tests/production-security.test.mjs tests/conversation-page.test.mjs tests/media-response.test.mjs tests/html-preview-transform.test.mjs tests/html-preview-response.test.mjs tests/push-store.test.mjs tests/push-store-hardening.test.mjs tests/push-delivery.test.mjs tests/push-queue-saturation.test.mjs tests/events.test.mjs tests/watch-cursor.test.mjs tests/watch-reconcile.test.mjs tests/introduction-send-guard.test.mjs tests/sse-e2e.test.mjs tests/presence.test.mjs tests/static-hosting.test.mjs tests/room-preview.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
36
+ "test:offline": "for f in tests/resource-views.test.mjs tests/resource-connections.test.mjs tests/resource-http.test.mjs tests/workspace-profile-preservation.test.mjs tests/notification-producer.test.mjs tests/notification-contact-retirement.test.mjs tests/notification-outbox-contract.test.mjs tests/workspace-root-retirement.test.mjs tests/base-path.test.mjs tests/build-epoch.test.mjs tests/owner-session.test.mjs tests/v1-runtime.test.mjs tests/typed-commands-core.test.mjs tests/shared-daemon-contract.test.mjs tests/room-envelope-matrix.test.mjs tests/bundle-smoke.test.mjs tests/build-metadata.test.mjs tests/production-security.test.mjs tests/conversation-page.test.mjs tests/media-response.test.mjs tests/html-preview-transform.test.mjs tests/html-preview-response.test.mjs tests/push-store.test.mjs tests/push-store-hardening.test.mjs tests/push-delivery.test.mjs tests/push-queue-saturation.test.mjs tests/events.test.mjs tests/watch-cursor.test.mjs tests/watch-reconcile.test.mjs tests/introduction-send-guard.test.mjs tests/sse-e2e.test.mjs tests/presence.test.mjs tests/static-hosting.test.mjs tests/room-preview.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
37
37
  "test:loopback": "for f in tests/shared-daemon-e2e.test.mjs tests/typed-command-loopback-e2e.test.mjs tests/introduction-send.test.mjs tests/rest-e2e.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
38
38
  "test": "npm run build && npm run test:startup && npm run test:offline && npm run test:loopback",
39
39
  "test:resource-daemon": "node --import tsx tests/resource-daemon.test.mjs",
@@ -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.1",
44
+ "@ours.network/sdk": "4.1.1-nightly.2",
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.1",
51
- "@ours.network/daemon": "4.1.1-nightly.1",
50
+ "@ours.network/cli": "3.1.1-nightly.2",
51
+ "@ours.network/daemon": "4.1.1-nightly.2",
52
52
  "@playwright/test": "^1.62.1",
53
53
  "@types/node": "^20.14.0",
54
54
  "@types/web-push": "^3.6.4",