@ours.network/messenger-server 1.0.31-nightly.21 → 1.0.31-nightly.22
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.
|
|
41
|
+
define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.22", sha: "043d87a3fc5b8bc2bc8ae5900f9ca7518d2b424c", 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-
|
|
13
|
+
} from "./chunk-MJMLX7IL.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(
|
|
5909
|
-
return typeof
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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(
|
|
11204
|
-
|
|
11205
|
-
const
|
|
11206
|
-
|
|
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((
|
|
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.
|
|
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) =>
|
|
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((
|
|
11248
|
+
if (!this.entries.some(same(entry.id))) continue;
|
|
11232
11249
|
if (value === void 0) {
|
|
11233
|
-
this.persist(this.entries.filter((current) =>
|
|
11250
|
+
this.persist(this.entries.filter((current) => !same(entry.id)(current)));
|
|
11234
11251
|
continue;
|
|
11235
11252
|
}
|
|
11236
|
-
if (
|
|
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) =>
|
|
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(
|
|
11283
|
-
if (typeof
|
|
11284
|
-
const trimmed =
|
|
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
|
|
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",
|
|
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
|
-
|
|
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,
|
|
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",
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
11436
|
-
const line = roomLineForContact(announcedContact,
|
|
11437
|
-
return line ? roomMessagePreview(line) : String(
|
|
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,
|
|
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
|
|
11473
|
-
if (!lead) return
|
|
11474
|
-
return
|
|
11491
|
+
const text2 = collapse(line.text);
|
|
11492
|
+
if (!lead) return text2;
|
|
11493
|
+
return text2 ? `${lead} \xB7 ${text2}` : lead;
|
|
11475
11494
|
}
|
|
11476
|
-
function collapse(
|
|
11477
|
-
return String(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
11727
|
+
target: { kind: "contact", contactId: record.sender_id },
|
|
11728
|
+
anchor: { kind: "messenger-message", wireId: record.wire_id }
|
|
11717
11729
|
};
|
|
11718
|
-
}, warn, async (
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
return
|
|
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.
|
|
11727
|
-
const
|
|
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
|
-
|
|
11737
|
-
|
|
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
|
|
11754
|
+
if (typeof record?.sender_id === "string" && contactIdentity(record.sender_id) === contactIdentity(cid)) return true;
|
|
11740
11755
|
if (!payload) return false;
|
|
11741
|
-
return
|
|
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
|
|
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,
|
|
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 +=
|
|
17541
|
+
prevNode.value += text2;
|
|
17527
17542
|
return;
|
|
17528
17543
|
}
|
|
17529
17544
|
}
|
|
17530
|
-
defaultTreeAdapter.appendChild(parentNode, defaultTreeAdapter.createTextNode(
|
|
17545
|
+
defaultTreeAdapter.appendChild(parentNode, defaultTreeAdapter.createTextNode(text2));
|
|
17531
17546
|
},
|
|
17532
|
-
insertTextBefore(parentNode,
|
|
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 +=
|
|
17550
|
+
prevNode.value += text2;
|
|
17536
17551
|
} else {
|
|
17537
|
-
defaultTreeAdapter.insertBefore(parentNode, defaultTreeAdapter.createTextNode(
|
|
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
|
|
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 (
|
|
21570
|
-
|
|
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 (!
|
|
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 (!
|
|
21605
|
+
while (!closed2) {
|
|
21591
21606
|
const event = await subscription.next();
|
|
21592
|
-
if (event === null ||
|
|
21607
|
+
if (event === null || closed2) break;
|
|
21593
21608
|
if (drainWait) await drainWait;
|
|
21594
|
-
if (
|
|
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
|
|
21697
|
+
const cleanup2 = () => {
|
|
21683
21698
|
req.removeListener("aborted", abort);
|
|
21684
21699
|
res.removeListener("close", responseClosed);
|
|
21685
|
-
res.removeListener("finish",
|
|
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
|
-
|
|
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",
|
|
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)
|
|
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
|
-
|
|
24456
|
+
cleanup2();
|
|
24442
24457
|
resolveReady(message.port);
|
|
24443
24458
|
};
|
|
24444
24459
|
const onError = (error) => {
|
|
24445
|
-
|
|
24460
|
+
cleanup2();
|
|
24446
24461
|
rejectReady(error);
|
|
24447
24462
|
};
|
|
24448
24463
|
const onExit = (code) => {
|
|
24449
|
-
|
|
24464
|
+
cleanup2();
|
|
24450
24465
|
rejectReady(failure ?? new Error(`startup readiness worker exited before listen (code ${code})`));
|
|
24451
24466
|
};
|
|
24452
|
-
const
|
|
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
|
-
|
|
24486
|
+
cleanup2();
|
|
24472
24487
|
resolveClose();
|
|
24473
24488
|
};
|
|
24474
24489
|
const onError = (error) => {
|
|
24475
|
-
|
|
24490
|
+
cleanup2();
|
|
24476
24491
|
rejectClose(error);
|
|
24477
24492
|
};
|
|
24478
24493
|
const onExit = (code) => {
|
|
24479
|
-
|
|
24494
|
+
cleanup2();
|
|
24480
24495
|
rejectClose(failure ?? new Error(`startup readiness worker exited during close (code ${code})`));
|
|
24481
24496
|
};
|
|
24482
|
-
const
|
|
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
|
|
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:
|
|
24638
|
+
return { port, runtime, close: cleanup2 };
|
|
24624
24639
|
} catch (error) {
|
|
24625
24640
|
try {
|
|
24626
|
-
await
|
|
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-
|
|
10
|
+
} from "./chunks/chunk-MJMLX7IL.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-
|
|
134
|
+
const { start } = await import("./chunks/server-ZSVT3GXU.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.
|
|
3
|
+
"version": "1.0.31-nightly.22",
|
|
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",
|