@glyphteck/veyl 0.60.0 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account.js +319 -176
- package/dist/cli.js +485 -200
- package/dist/index.js +485 -200
- package/docs/agents.md +1 -1
- package/docs/api.md +1 -1
- package/examples/bot-fleet/policy.js +136 -17
- package/examples/bot-fleet/readme.md +5 -3
- package/examples/bot-fleet/runtime.js +6 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11130,6 +11130,20 @@ function normalizeCurrentEpoch(value) {
|
|
|
11130
11130
|
settings: normalizeChatSettingsProjection(value.settings)
|
|
11131
11131
|
};
|
|
11132
11132
|
}
|
|
11133
|
+
function normalizeOwnerRetirement(value, current) {
|
|
11134
|
+
if (value == null)
|
|
11135
|
+
return null;
|
|
11136
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11137
|
+
throw new Error("invalid chat owner retirement");
|
|
11138
|
+
}
|
|
11139
|
+
const kind = cleanText(value.kind);
|
|
11140
|
+
const epochVersion = value.epochVersion;
|
|
11141
|
+
if (!Object.values(CHAT_OWNER_RETIREMENT_KINDS).includes(kind) || !Number.isSafeInteger(epochVersion) || epochVersion < current.manifest.epochVersion) {
|
|
11142
|
+
throw new Error("invalid chat owner retirement");
|
|
11143
|
+
}
|
|
11144
|
+
const proposalId = kind === CHAT_OWNER_RETIREMENT_KINDS.LEAVE ? cleanTransitionMessageId(value.proposalId) : null;
|
|
11145
|
+
return { kind, epochVersion, proposalId };
|
|
11146
|
+
}
|
|
11133
11147
|
function normalizeOwnerEntry(value) {
|
|
11134
11148
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.v !== CHAT_ENTRY_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
11135
11149
|
throw new Error("invalid chat entry");
|
|
@@ -11150,7 +11164,8 @@ function normalizeOwnerEntry(value) {
|
|
|
11150
11164
|
saved: value.saved || null,
|
|
11151
11165
|
startMs: Number.isFinite(value.startMs) ? value.startMs : null,
|
|
11152
11166
|
deliveryRegistered: value.deliveryRegistered === true,
|
|
11153
|
-
notificationTag: cleanText(value.notificationTag) || null
|
|
11167
|
+
notificationTag: cleanText(value.notificationTag) || null,
|
|
11168
|
+
retirement: normalizeOwnerRetirement(value.retirement, current)
|
|
11154
11169
|
};
|
|
11155
11170
|
}
|
|
11156
11171
|
async function sealOwnChatEntry(chatPrivateKey, entryId, entry) {
|
|
@@ -11191,7 +11206,8 @@ function makeOwnChatEntry(epoch, fields = {}) {
|
|
|
11191
11206
|
saved: fields.saved || null,
|
|
11192
11207
|
startMs: Number.isFinite(fields.startMs) ? fields.startMs : null,
|
|
11193
11208
|
deliveryRegistered: fields.deliveryRegistered === true,
|
|
11194
|
-
notificationTag: cleanText(fields.notificationTag) || null
|
|
11209
|
+
notificationTag: cleanText(fields.notificationTag) || null,
|
|
11210
|
+
retirement: fields.retirement || null
|
|
11195
11211
|
});
|
|
11196
11212
|
}
|
|
11197
11213
|
function normalizeOwnerEpochRecord(value) {
|
|
@@ -11199,6 +11215,7 @@ function normalizeOwnerEpochRecord(value) {
|
|
|
11199
11215
|
throw new Error("invalid owner epoch entry");
|
|
11200
11216
|
}
|
|
11201
11217
|
const manifest = normalizeEpochManifest(value.manifest);
|
|
11218
|
+
const leaveProposalId = value.leaveProposalId == null ? null : cleanTransitionMessageId(value.leaveProposalId);
|
|
11202
11219
|
return {
|
|
11203
11220
|
v: CHAT_OWNER_EPOCH_ENTRY_VERSION,
|
|
11204
11221
|
protocol: CHAT_PROTOCOL_VERSION,
|
|
@@ -11208,8 +11225,9 @@ function normalizeOwnerEpochRecord(value) {
|
|
|
11208
11225
|
manifest,
|
|
11209
11226
|
epochSecret: cleanChatHex(value.epochSecret, "epoch secret"),
|
|
11210
11227
|
cutoffMs: Number.isFinite(value.cutoffMs) ? value.cutoffMs : null,
|
|
11211
|
-
successorTransitionCommitment: cleanTransitionCommitment(value.successorTransitionCommitment),
|
|
11212
|
-
successorTransitionMessageId: cleanTransitionMessageId(value.successorTransitionMessageId)
|
|
11228
|
+
successorTransitionCommitment: leaveProposalId ? null : cleanTransitionCommitment(value.successorTransitionCommitment),
|
|
11229
|
+
successorTransitionMessageId: leaveProposalId ? null : cleanTransitionMessageId(value.successorTransitionMessageId),
|
|
11230
|
+
leaveProposalId
|
|
11213
11231
|
};
|
|
11214
11232
|
}
|
|
11215
11233
|
function makeOwnerEpochRecord(manifest, epochSecret, fields = {}) {
|
|
@@ -11220,7 +11238,8 @@ function makeOwnerEpochRecord(manifest, epochSecret, fields = {}) {
|
|
|
11220
11238
|
epochSecret: secretHex(epochSecret, "epoch secret"),
|
|
11221
11239
|
cutoffMs: Number.isFinite(fields.cutoffMs) ? fields.cutoffMs : null,
|
|
11222
11240
|
successorTransitionCommitment: fields.successorTransitionCommitment,
|
|
11223
|
-
successorTransitionMessageId: fields.successorTransitionMessageId
|
|
11241
|
+
successorTransitionMessageId: fields.successorTransitionMessageId,
|
|
11242
|
+
leaveProposalId: fields.leaveProposalId
|
|
11224
11243
|
});
|
|
11225
11244
|
}
|
|
11226
11245
|
async function sealOwnerEpochRecord(chatPrivateKey, entryId, epochEntryId, record) {
|
|
@@ -11244,7 +11263,7 @@ async function openOwnerEpochRecord(chatPrivateKey, entryId, epochEntryId, body)
|
|
|
11244
11263
|
function ownEpochEntryId(chatPrivateKey, chatId, epochId) {
|
|
11245
11264
|
return ownerEpochEntryId(chatPrivateKey, chatId, epochId);
|
|
11246
11265
|
}
|
|
11247
|
-
var CHAT_ENTRY_VERSION = 4, CHAT_OWNER_EPOCH_ENTRY_VERSION = 2;
|
|
11266
|
+
var CHAT_ENTRY_VERSION = 4, CHAT_OWNER_EPOCH_ENTRY_VERSION = 2, CHAT_OWNER_RETIREMENT_KINDS;
|
|
11248
11267
|
var init_entry = __esm(() => {
|
|
11249
11268
|
init_canonical();
|
|
11250
11269
|
init_core();
|
|
@@ -11256,6 +11275,10 @@ var init_entry = __esm(() => {
|
|
|
11256
11275
|
init_state2();
|
|
11257
11276
|
init_settings2();
|
|
11258
11277
|
"use client";
|
|
11278
|
+
CHAT_OWNER_RETIREMENT_KINDS = Object.freeze({
|
|
11279
|
+
LEAVE: "leave",
|
|
11280
|
+
REMOVED: "removed"
|
|
11281
|
+
});
|
|
11259
11282
|
});
|
|
11260
11283
|
|
|
11261
11284
|
// ../../core/chat/epochs/bootstrap.js
|
|
@@ -11396,6 +11419,32 @@ async function mutateOwnChatEntry(cloud, identity, entryId, current, update) {
|
|
|
11396
11419
|
const committed = await cloud.user.chats.mutate(identity.uid, entryId, mutation);
|
|
11397
11420
|
return committed?.result ?? mutation.result ?? null;
|
|
11398
11421
|
}
|
|
11422
|
+
async function retireOwnChatEntry(cloud, identity, entryId, current, fields = {}) {
|
|
11423
|
+
const expectedEpochVersion = fields.expectedEpochVersion;
|
|
11424
|
+
const retirement = fields.retirement;
|
|
11425
|
+
if (!Number.isSafeInteger(expectedEpochVersion) || expectedEpochVersion < 1 || !retirement?.kind || !Number.isSafeInteger(retirement.epochVersion)) {
|
|
11426
|
+
throw new Error("chat owner retirement required");
|
|
11427
|
+
}
|
|
11428
|
+
return mutateOwnChatEntry(cloud, identity, entryId, current, (latest) => {
|
|
11429
|
+
if (latest?.retirement?.kind === retirement.kind && latest.retirement.epochVersion >= retirement.epochVersion) {
|
|
11430
|
+
return { result: latest };
|
|
11431
|
+
}
|
|
11432
|
+
if (latest?.retirement || latest?.current?.manifest?.epochVersion !== expectedEpochVersion) {
|
|
11433
|
+
throw new Error("chat owner epoch changed");
|
|
11434
|
+
}
|
|
11435
|
+
return {
|
|
11436
|
+
entry: {
|
|
11437
|
+
...latest,
|
|
11438
|
+
routes: {},
|
|
11439
|
+
deliveryRegistered: false,
|
|
11440
|
+
notificationTag: null,
|
|
11441
|
+
retirement
|
|
11442
|
+
},
|
|
11443
|
+
epochs: fields.epoch ? [fields.epoch] : [],
|
|
11444
|
+
mlsDeletes: fields.mlsStateId ? [fields.mlsStateId] : []
|
|
11445
|
+
};
|
|
11446
|
+
});
|
|
11447
|
+
}
|
|
11399
11448
|
async function openOwnChatMutationEntry(identity, entryId, record) {
|
|
11400
11449
|
if (!record)
|
|
11401
11450
|
return null;
|
|
@@ -12118,48 +12167,6 @@ function cleanFrontier(value) {
|
|
|
12118
12167
|
throw new Error("chat receipt checkpoint frontier time required");
|
|
12119
12168
|
return Object.freeze({ id, at: value.at, seenAt: value.seenAt });
|
|
12120
12169
|
}
|
|
12121
|
-
function maskBytes(memberCount2) {
|
|
12122
|
-
return Math.ceil(memberCount2 / 8);
|
|
12123
|
-
}
|
|
12124
|
-
function cleanMask(value, memberCount2, label) {
|
|
12125
|
-
const mask = cleanText(value).toLowerCase();
|
|
12126
|
-
const size = maskBytes(memberCount2);
|
|
12127
|
-
if (!new RegExp(`^[0-9a-f]{${size * 2}}$`, "u").test(mask)) {
|
|
12128
|
-
throw new Error(`${label} invalid`);
|
|
12129
|
-
}
|
|
12130
|
-
const unused = size * 8 - memberCount2;
|
|
12131
|
-
if (unused > 0 && Number.parseInt(mask.slice(-2), 16) & (1 << unused) - 1 << 8 - unused) {
|
|
12132
|
-
throw new Error(`${label} overflow`);
|
|
12133
|
-
}
|
|
12134
|
-
return mask;
|
|
12135
|
-
}
|
|
12136
|
-
function makeMask(indices, memberCount2) {
|
|
12137
|
-
const bytes = new Uint8Array(maskBytes(memberCount2));
|
|
12138
|
-
for (const index of indices)
|
|
12139
|
-
bytes[Math.floor(index / 8)] |= 1 << index % 8;
|
|
12140
|
-
return [...bytes].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
12141
|
-
}
|
|
12142
|
-
function maskHas(mask, index) {
|
|
12143
|
-
const offset = Math.floor(index / 8) * 2;
|
|
12144
|
-
return (Number.parseInt(mask.slice(offset, offset + 2), 16) & 1 << index % 8) !== 0;
|
|
12145
|
-
}
|
|
12146
|
-
function maskCount(mask, memberCount2) {
|
|
12147
|
-
let count = 0;
|
|
12148
|
-
for (let index = 0;index < memberCount2; index += 1) {
|
|
12149
|
-
if (maskHas(mask, index))
|
|
12150
|
-
count += 1;
|
|
12151
|
-
}
|
|
12152
|
-
return count;
|
|
12153
|
-
}
|
|
12154
|
-
function frontierWithSeenAt(frontier, states) {
|
|
12155
|
-
if (!frontier)
|
|
12156
|
-
return null;
|
|
12157
|
-
return {
|
|
12158
|
-
id: frontier.id,
|
|
12159
|
-
at: frontier.at,
|
|
12160
|
-
seenAt: Math.max(frontier.seenAt, ...states.map((state) => Number(state?.readFrontier?.seenAt) || 0))
|
|
12161
|
-
};
|
|
12162
|
-
}
|
|
12163
12170
|
function normalizeChatReceiptProjection(value) {
|
|
12164
12171
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
12165
12172
|
throw new Error("chat receipt checkpoint projection required");
|
|
@@ -12168,41 +12175,13 @@ function normalizeChatReceiptProjection(value) {
|
|
|
12168
12175
|
if (!Array.isArray(value.revisions) || value.revisions.length !== memberCount2) {
|
|
12169
12176
|
throw new Error("chat receipt checkpoint revisions invalid");
|
|
12170
12177
|
}
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
const highFrontier = cleanFrontier(value.highFrontier);
|
|
12174
|
-
const nextHighFrontier = cleanFrontier(value.nextHighFrontier);
|
|
12175
|
-
const lowMask = cleanMask(value.lowMask, memberCount2, "chat receipt checkpoint low mask");
|
|
12176
|
-
const highMask = cleanMask(value.highMask, memberCount2, "chat receipt checkpoint high mask");
|
|
12177
|
-
const nextHighMask = cleanMask(value.nextHighMask, memberCount2, "chat receipt checkpoint next high mask");
|
|
12178
|
-
if (!highFrontier && maskCount(highMask, memberCount2) !== 0) {
|
|
12179
|
-
throw new Error("chat receipt checkpoint high frontier missing");
|
|
12180
|
-
}
|
|
12181
|
-
if (highFrontier && maskCount(highMask, memberCount2) === 0) {
|
|
12182
|
-
throw new Error("chat receipt checkpoint high readers missing");
|
|
12183
|
-
}
|
|
12184
|
-
if (!nextHighFrontier && maskCount(nextHighMask, memberCount2) !== 0) {
|
|
12185
|
-
throw new Error("chat receipt checkpoint next high frontier missing");
|
|
12186
|
-
}
|
|
12187
|
-
if (nextHighFrontier && maskCount(nextHighMask, memberCount2) === 0) {
|
|
12188
|
-
throw new Error("chat receipt checkpoint next high readers missing");
|
|
12189
|
-
}
|
|
12190
|
-
if (lowFrontier && compareReadFrontiers(lowFrontier, highFrontier) > 0) {
|
|
12191
|
-
throw new Error("chat receipt checkpoint frontier order invalid");
|
|
12192
|
-
}
|
|
12193
|
-
if (nextLowFrontier && compareReadFrontiers(nextLowFrontier, highFrontier) > 0) {
|
|
12194
|
-
throw new Error("chat receipt checkpoint next frontier order invalid");
|
|
12178
|
+
if (!Array.isArray(value.frontiers) || value.frontiers.length !== memberCount2) {
|
|
12179
|
+
throw new Error("chat receipt checkpoint frontiers invalid");
|
|
12195
12180
|
}
|
|
12196
12181
|
return Object.freeze({
|
|
12197
12182
|
memberCount: memberCount2,
|
|
12198
12183
|
revisions: Object.freeze(value.revisions.map(cleanRevision3)),
|
|
12199
|
-
|
|
12200
|
-
lowMask,
|
|
12201
|
-
nextLowFrontier,
|
|
12202
|
-
highFrontier,
|
|
12203
|
-
highMask,
|
|
12204
|
-
nextHighFrontier,
|
|
12205
|
-
nextHighMask
|
|
12184
|
+
frontiers: Object.freeze(value.frontiers.map(cleanFrontier))
|
|
12206
12185
|
});
|
|
12207
12186
|
}
|
|
12208
12187
|
function buildChatReceiptProjection(manifest, states) {
|
|
@@ -12210,36 +12189,66 @@ function buildChatReceiptProjection(manifest, states) {
|
|
|
12210
12189
|
const memberCount2 = cleanCount(members.length);
|
|
12211
12190
|
const values = members.map((member) => states?.get?.(member.chatPK) || null);
|
|
12212
12191
|
const revisions = values.map((state) => Math.max(0, Number(state?.revision) || 0));
|
|
12213
|
-
const missing = [];
|
|
12214
|
-
const present = [];
|
|
12215
|
-
values.forEach((state, index) => {
|
|
12216
|
-
if (state?.readFrontier)
|
|
12217
|
-
present.push({ index, state, frontier: state.readFrontier });
|
|
12218
|
-
else
|
|
12219
|
-
missing.push(index);
|
|
12220
|
-
});
|
|
12221
|
-
present.sort((left, right) => compareReadFrontiers(left.frontier, right.frontier));
|
|
12222
|
-
const minimum = present[0]?.frontier || null;
|
|
12223
|
-
const maximum = present.at(-1)?.frontier || null;
|
|
12224
|
-
const lowIndices = missing.length ? missing : present.filter((item) => compareReadFrontiers(item.frontier, minimum) === 0).map((item) => item.index);
|
|
12225
|
-
const nextLowItems = missing.length ? present : present.filter((item) => compareReadFrontiers(item.frontier, minimum) > 0);
|
|
12226
|
-
const nextLow = nextLowItems[0]?.frontier || null;
|
|
12227
|
-
const highItems = maximum ? present.filter((item) => compareReadFrontiers(item.frontier, maximum) === 0) : [];
|
|
12228
|
-
const belowHigh = maximum ? present.filter((item) => compareReadFrontiers(item.frontier, maximum) < 0) : [];
|
|
12229
|
-
const nextHigh = belowHigh.at(-1)?.frontier || null;
|
|
12230
|
-
const nextHighItems = nextHigh ? belowHigh.filter((item) => compareReadFrontiers(item.frontier, nextHigh) === 0) : [];
|
|
12231
12192
|
return normalizeChatReceiptProjection({
|
|
12232
12193
|
memberCount: memberCount2,
|
|
12233
12194
|
revisions,
|
|
12234
|
-
|
|
12235
|
-
lowMask: makeMask(lowIndices, memberCount2),
|
|
12236
|
-
nextLowFrontier: frontierWithSeenAt(nextLow, nextLowItems.map((item) => item.state)),
|
|
12237
|
-
highFrontier: frontierWithSeenAt(maximum, highItems.map((item) => item.state)),
|
|
12238
|
-
highMask: makeMask(highItems.map((item) => item.index), memberCount2),
|
|
12239
|
-
nextHighFrontier: frontierWithSeenAt(nextHigh, nextHighItems.map((item) => item.state)),
|
|
12240
|
-
nextHighMask: makeMask(nextHighItems.map((item) => item.index), memberCount2)
|
|
12195
|
+
frontiers: values.map((state) => state?.readFrontier || null)
|
|
12241
12196
|
});
|
|
12242
12197
|
}
|
|
12198
|
+
function mergeChatReceiptProjections(current, incoming) {
|
|
12199
|
+
if (!current)
|
|
12200
|
+
return normalizeChatReceiptProjection(incoming);
|
|
12201
|
+
if (!incoming)
|
|
12202
|
+
return normalizeChatReceiptProjection(current);
|
|
12203
|
+
const left = normalizeChatReceiptProjection(current);
|
|
12204
|
+
const right = normalizeChatReceiptProjection(incoming);
|
|
12205
|
+
if (left.memberCount !== right.memberCount) {
|
|
12206
|
+
throw new Error("chat receipt checkpoint member count mismatch");
|
|
12207
|
+
}
|
|
12208
|
+
return normalizeChatReceiptProjection({
|
|
12209
|
+
memberCount: left.memberCount,
|
|
12210
|
+
revisions: left.revisions.map((revision, index) => Math.max(revision, right.revisions[index])),
|
|
12211
|
+
frontiers: left.frontiers.map((frontier, index) => {
|
|
12212
|
+
const other = right.frontiers[index];
|
|
12213
|
+
const comparison = compareReadFrontiers(frontier, other);
|
|
12214
|
+
if (comparison > 0)
|
|
12215
|
+
return frontier;
|
|
12216
|
+
if (comparison < 0)
|
|
12217
|
+
return other;
|
|
12218
|
+
if (!frontier)
|
|
12219
|
+
return null;
|
|
12220
|
+
return {
|
|
12221
|
+
...frontier,
|
|
12222
|
+
seenAt: Math.max(frontier.seenAt, other.seenAt)
|
|
12223
|
+
};
|
|
12224
|
+
})
|
|
12225
|
+
});
|
|
12226
|
+
}
|
|
12227
|
+
function mergeChatReceiptCheckpointsByEpoch(current, incoming) {
|
|
12228
|
+
if (!(incoming instanceof Map) || !incoming.size) {
|
|
12229
|
+
return current instanceof Map ? current : new Map;
|
|
12230
|
+
}
|
|
12231
|
+
if (!(current instanceof Map) || !current.size)
|
|
12232
|
+
return new Map(incoming);
|
|
12233
|
+
const merged = new Map(current instanceof Map ? current : []);
|
|
12234
|
+
let changed = false;
|
|
12235
|
+
for (const [epochId, value] of incoming instanceof Map ? incoming : []) {
|
|
12236
|
+
const existing = merged.get(epochId);
|
|
12237
|
+
if (existing === value)
|
|
12238
|
+
continue;
|
|
12239
|
+
const exact = isChatReceiptCheckpoint(existing) && isChatReceiptCheckpoint(value) && existing.epochId === value.epochId && existing.memberChatPKs.length === value.memberChatPKs.length && existing.memberChatPKs.every((chatPK, index) => chatPK === value.memberChatPKs[index]);
|
|
12240
|
+
const next = exact ? Object.freeze({
|
|
12241
|
+
...value,
|
|
12242
|
+
publishedAt: Math.max(Number(existing.publishedAt) || 0, Number(value.publishedAt) || 0) || null,
|
|
12243
|
+
projection: mergeChatReceiptProjections(existing.projection, value.projection)
|
|
12244
|
+
}) : value;
|
|
12245
|
+
if (exact && sameChatReceiptCheckpoint(existing, next) && existing.publishedAt === next.publishedAt)
|
|
12246
|
+
continue;
|
|
12247
|
+
merged.set(epochId, next);
|
|
12248
|
+
changed = true;
|
|
12249
|
+
}
|
|
12250
|
+
return changed ? merged : current;
|
|
12251
|
+
}
|
|
12243
12252
|
function projectChatReceiptCheckpoint(manifest, value) {
|
|
12244
12253
|
const projection = normalizeChatReceiptProjection(value?.projection || value);
|
|
12245
12254
|
if (projection.memberCount !== manifest?.members?.length) {
|
|
@@ -12272,13 +12281,14 @@ function chatReceiptCheckpointCoversState(checkpoint, manifest, state) {
|
|
|
12272
12281
|
function chatReceiptAllSeenFrontier(checkpoint, authorChatPK) {
|
|
12273
12282
|
if (!isChatReceiptCheckpoint(checkpoint))
|
|
12274
12283
|
return null;
|
|
12275
|
-
const
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12284
|
+
const readers = checkpoint.projection.frontiers.filter((_frontier, index) => checkpoint.memberChatPKs[index] !== authorChatPK);
|
|
12285
|
+
if (!readers.length || readers.some((frontier) => !frontier))
|
|
12286
|
+
return null;
|
|
12287
|
+
const minimum = readers.reduce((current, frontier) => !current || compareReadFrontiers(frontier, current) < 0 ? frontier : current, null);
|
|
12288
|
+
return Object.freeze({
|
|
12289
|
+
...minimum,
|
|
12290
|
+
seenAt: Math.max(...readers.map((frontier) => frontier.seenAt))
|
|
12291
|
+
});
|
|
12282
12292
|
}
|
|
12283
12293
|
function chatReceiptProjectionFingerprint(value) {
|
|
12284
12294
|
return JSON.stringify(normalizeChatReceiptProjection(value));
|
|
@@ -12637,7 +12647,7 @@ function getLatestLocalReadTarget(messages, chatPK) {
|
|
|
12637
12647
|
function getLatestReadStateTarget(messages) {
|
|
12638
12648
|
for (let i = (messages?.length || 0) - 1;i >= 0; i -= 1) {
|
|
12639
12649
|
const message = messages[i];
|
|
12640
|
-
if (isServerConfirmedMsg(message) &&
|
|
12650
|
+
if (isServerConfirmedMsg(message) && !isSystemMsg(message) && (canShowMsg(message) || isReactionMsg(message)))
|
|
12641
12651
|
return message;
|
|
12642
12652
|
}
|
|
12643
12653
|
return null;
|
|
@@ -14787,10 +14797,6 @@ async function prepareOwnerChatMlsSnapshot(identity, entryId, epochState, snapsh
|
|
|
14787
14797
|
function ownerChatMlsSnapshotId(identity, entryId, epochState) {
|
|
14788
14798
|
return stateAddress(identity, entryId, epochState).stateId;
|
|
14789
14799
|
}
|
|
14790
|
-
async function discardOwnerChatMlsSnapshot(cloud, identity, entryId, epochState) {
|
|
14791
|
-
const address = stateAddress(identity, entryId, epochState);
|
|
14792
|
-
await cloud.user.chats.mls.delete(identity.uid, entryId, address.stateId).catch(() => false);
|
|
14793
|
-
}
|
|
14794
14800
|
var init_owner2 = __esm(() => {
|
|
14795
14801
|
init_state3();
|
|
14796
14802
|
"use client";
|
|
@@ -16596,7 +16602,45 @@ function reviveMarker(marker) {
|
|
|
16596
16602
|
function serializeKeys(keys) {
|
|
16597
16603
|
return [...keySet(keys)].filter(Boolean).sort();
|
|
16598
16604
|
}
|
|
16599
|
-
function
|
|
16605
|
+
function cleanReceiptCheckpoint(value) {
|
|
16606
|
+
if (!isChatReceiptCheckpoint(value))
|
|
16607
|
+
return null;
|
|
16608
|
+
const epochId = cleanText(value.epochId);
|
|
16609
|
+
const memberChatPKs = (value.memberChatPKs || []).map(cleanText).filter(Boolean);
|
|
16610
|
+
if (!epochId || memberChatPKs.length !== value.memberChatPKs?.length)
|
|
16611
|
+
return null;
|
|
16612
|
+
try {
|
|
16613
|
+
return projectChatReceiptCheckpoint({
|
|
16614
|
+
epochId,
|
|
16615
|
+
members: memberChatPKs.map((chatPK) => ({ chatPK }))
|
|
16616
|
+
}, value);
|
|
16617
|
+
} catch {
|
|
16618
|
+
return null;
|
|
16619
|
+
}
|
|
16620
|
+
}
|
|
16621
|
+
function serializeReceiptCheckpoints(statesByEpoch, epochIds) {
|
|
16622
|
+
const allowed = new Set(epochIds || []);
|
|
16623
|
+
const checkpoints = [];
|
|
16624
|
+
for (const [epochId, value] of statesByEpoch instanceof Map ? statesByEpoch : []) {
|
|
16625
|
+
if (allowed.size && !allowed.has(epochId))
|
|
16626
|
+
continue;
|
|
16627
|
+
const checkpoint = cleanReceiptCheckpoint(value);
|
|
16628
|
+
if (checkpoint)
|
|
16629
|
+
checkpoints.push(checkpoint);
|
|
16630
|
+
}
|
|
16631
|
+
return checkpoints.sort((left, right) => left.epochId.localeCompare(right.epochId));
|
|
16632
|
+
}
|
|
16633
|
+
function reviveReceiptCheckpoints(values) {
|
|
16634
|
+
return new Map((Array.isArray(values) ? values : []).map((value) => cleanReceiptCheckpoint(value)).filter(Boolean).map((checkpoint) => [checkpoint.epochId, checkpoint]));
|
|
16635
|
+
}
|
|
16636
|
+
function receiptEpochIds(messages, fallbackEpochId = "") {
|
|
16637
|
+
const ids = new Set((messages || []).map((message) => cleanText(message?.epochId)).filter(Boolean));
|
|
16638
|
+
const fallback = cleanText(fallbackEpochId);
|
|
16639
|
+
if (fallback)
|
|
16640
|
+
ids.add(fallback);
|
|
16641
|
+
return ids;
|
|
16642
|
+
}
|
|
16643
|
+
function serializeBatch(batch, { expiredKeys, deletedKeys, epochIds } = {}) {
|
|
16600
16644
|
if (!batch && !expiredKeys?.size && !deletedKeys?.size) {
|
|
16601
16645
|
return null;
|
|
16602
16646
|
}
|
|
@@ -16606,14 +16650,16 @@ function serializeBatch(batch, { expiredKeys, deletedKeys } = {}) {
|
|
|
16606
16650
|
...Number.isSafeInteger(batch?.epochVersion) ? { epochVersion: batch.epochVersion } : {},
|
|
16607
16651
|
...Number.isFinite(batch?.firstMs) ? { firstMs: batch.firstMs } : {},
|
|
16608
16652
|
...Number.isFinite(batch?.lastMs) ? { lastMs: batch.lastMs } : {},
|
|
16653
|
+
receiptCheckpoints: serializeReceiptCheckpoints(batch?.memberStatesByEpoch, epochIds),
|
|
16609
16654
|
expiredKeys: serializeKeys(expiredKeys || batch?.expiredKeys),
|
|
16610
16655
|
deletedKeys: serializeKeys(deletedKeys || batch?.deletedKeys)
|
|
16611
16656
|
};
|
|
16612
16657
|
}
|
|
16613
|
-
function batchFromLive(latestMessages, expiredKeys, deletedKeys, fallback) {
|
|
16658
|
+
function batchFromLive(latestMessages, expiredKeys, deletedKeys, fallback, receiptMessages = latestMessages) {
|
|
16614
16659
|
const source = latestMessages || [];
|
|
16660
|
+
const epochIds = receiptEpochIds(receiptMessages, fallback?.epochId);
|
|
16615
16661
|
if (!source.length) {
|
|
16616
|
-
return serializeBatch(fallback, { expiredKeys, deletedKeys });
|
|
16662
|
+
return serializeBatch(fallback, { expiredKeys, deletedKeys, epochIds });
|
|
16617
16663
|
}
|
|
16618
16664
|
const firstMs = getMessageOrderMs(source[0]);
|
|
16619
16665
|
const lastMs = getMessageOrderMs(source[source.length - 1]);
|
|
@@ -16621,8 +16667,9 @@ function batchFromLive(latestMessages, expiredKeys, deletedKeys, fallback) {
|
|
|
16621
16667
|
firstMs,
|
|
16622
16668
|
lastMs,
|
|
16623
16669
|
epochId: fallback?.epochId,
|
|
16624
|
-
epochVersion: fallback?.epochVersion
|
|
16625
|
-
|
|
16670
|
+
epochVersion: fallback?.epochVersion,
|
|
16671
|
+
memberStatesByEpoch: fallback?.memberStatesByEpoch
|
|
16672
|
+
} : fallback, { expiredKeys, deletedKeys, epochIds });
|
|
16626
16673
|
}
|
|
16627
16674
|
function reviveBatch(batch, expiredKeys, deletedKeys) {
|
|
16628
16675
|
const hasRange = Number.isFinite(batch?.firstMs) && Number.isFinite(batch?.lastMs);
|
|
@@ -16634,6 +16681,7 @@ function reviveBatch(batch, expiredKeys, deletedKeys) {
|
|
|
16634
16681
|
...cleanText(batch?.epochId) ? { epochId: cleanText(batch.epochId) } : {},
|
|
16635
16682
|
...Number.isSafeInteger(batch?.epochVersion) ? { epochVersion: batch.epochVersion } : {},
|
|
16636
16683
|
...hasRange ? { firstMs: batch.firstMs, lastMs: batch.lastMs } : {},
|
|
16684
|
+
memberStatesByEpoch: reviveReceiptCheckpoints(batch?.receiptCheckpoints),
|
|
16637
16685
|
expiredKeys,
|
|
16638
16686
|
deletedKeys
|
|
16639
16687
|
};
|
|
@@ -16650,10 +16698,20 @@ function mergeBatches(left, right) {
|
|
|
16650
16698
|
...Number.isSafeInteger(right?.epochVersion ?? left?.epochVersion) ? { epochVersion: right?.epochVersion ?? left?.epochVersion } : {},
|
|
16651
16699
|
...firstValues.length ? { firstMs: Math.min(...firstValues) } : {},
|
|
16652
16700
|
...lastValues.length ? { lastMs: Math.max(...lastValues) } : {},
|
|
16701
|
+
memberStatesByEpoch: mergeChatReceiptCheckpointsByEpoch(left?.memberStatesByEpoch, right?.memberStatesByEpoch),
|
|
16653
16702
|
expiredKeys: keySet([...left?.expiredKeys || [], ...right?.expiredKeys || []]),
|
|
16654
16703
|
deletedKeys: keySet([...left?.deletedKeys || [], ...right?.deletedKeys || []])
|
|
16655
16704
|
};
|
|
16656
16705
|
}
|
|
16706
|
+
function cachedReceiptCheckpointSignature(statesByEpoch) {
|
|
16707
|
+
const values = [];
|
|
16708
|
+
for (const [epochId, value] of statesByEpoch instanceof Map ? statesByEpoch : []) {
|
|
16709
|
+
const checkpoint = cleanReceiptCheckpoint(value);
|
|
16710
|
+
if (checkpoint)
|
|
16711
|
+
values.push(`${epochId}:${chatReceiptProjectionFingerprint(checkpoint.projection)}`);
|
|
16712
|
+
}
|
|
16713
|
+
return values.sort().join("|");
|
|
16714
|
+
}
|
|
16657
16715
|
function olderMarker(left, right) {
|
|
16658
16716
|
if (!left)
|
|
16659
16717
|
return right || null;
|
|
@@ -17056,7 +17114,7 @@ function writeCachedMessageState(cache, { stateVersion = null, chatId, selfChatP
|
|
|
17056
17114
|
historyStartReached: nextHistoryStartReached === true,
|
|
17057
17115
|
olderThan: serializeMarker(storedOlderThan),
|
|
17058
17116
|
olderLoaded: nextOlderLoaded === true || compacted.historyMessages.length > 0,
|
|
17059
|
-
latestServerPage: batchFromLive(compacted.latestMessages, expiredKeys, deletedKeys, nextServerBatch),
|
|
17117
|
+
latestServerPage: batchFromLive(compacted.latestMessages, expiredKeys, deletedKeys, nextServerBatch, compacted.all),
|
|
17060
17118
|
deletedKeys: serializeKeys(deletedKeys),
|
|
17061
17119
|
expiredKeys: serializeKeys(expiredKeys),
|
|
17062
17120
|
historyMessages: compacted.historyMessages,
|
|
@@ -17085,6 +17143,7 @@ var init_messages2 = __esm(() => {
|
|
|
17085
17143
|
init_state();
|
|
17086
17144
|
init_messagekeys();
|
|
17087
17145
|
init_compact();
|
|
17146
|
+
init_receipt();
|
|
17088
17147
|
init_filename();
|
|
17089
17148
|
init_time();
|
|
17090
17149
|
init_core();
|
|
@@ -19723,6 +19782,9 @@ var init_chatlive = __esm(() => {
|
|
|
19723
19782
|
});
|
|
19724
19783
|
|
|
19725
19784
|
// ../../core/chat/live.js
|
|
19785
|
+
function makeChatCompositionId() {
|
|
19786
|
+
return toHex(randomBytes3(16));
|
|
19787
|
+
}
|
|
19726
19788
|
function chatMessageCompositionId(message) {
|
|
19727
19789
|
const value = String(message?.compositionId || "").trim().toLowerCase();
|
|
19728
19790
|
return CHAT_COMPOSITION_ID_RE.test(value) ? value : "";
|
|
@@ -19906,10 +19968,16 @@ function createChatLive({
|
|
|
19906
19968
|
onActivity?.(entry.chatId, EMPTY_ACTIVITY);
|
|
19907
19969
|
};
|
|
19908
19970
|
const enterChatActivity = async (chatId) => {
|
|
19909
|
-
if (!enabled || !chatId
|
|
19971
|
+
if (!enabled || !chatId)
|
|
19910
19972
|
return false;
|
|
19973
|
+
const current = rooms.get(chatId);
|
|
19974
|
+
if (current && !current.closed) {
|
|
19975
|
+
current.retainers += 1;
|
|
19976
|
+
return true;
|
|
19977
|
+
}
|
|
19911
19978
|
const entry = {
|
|
19912
19979
|
chatId,
|
|
19980
|
+
retainers: 1,
|
|
19913
19981
|
epochId: "",
|
|
19914
19982
|
material: null,
|
|
19915
19983
|
connection: null,
|
|
@@ -19985,6 +20053,9 @@ function createChatLive({
|
|
|
19985
20053
|
const entry = rooms.get(chatId);
|
|
19986
20054
|
if (!entry)
|
|
19987
20055
|
return false;
|
|
20056
|
+
entry.retainers -= 1;
|
|
20057
|
+
if (entry.retainers > 0)
|
|
20058
|
+
return true;
|
|
19988
20059
|
rooms.delete(chatId);
|
|
19989
20060
|
destroy(entry);
|
|
19990
20061
|
return true;
|
|
@@ -20032,13 +20103,26 @@ function createChatLive({
|
|
|
20032
20103
|
publish(entry);
|
|
20033
20104
|
return scheduleReadSnapshot(entry);
|
|
20034
20105
|
};
|
|
20106
|
+
const flushChatReadFrontier = async (chatId) => {
|
|
20107
|
+
const entry = rooms.get(chatId);
|
|
20108
|
+
if (!entry || entry.closed || !entry.connected || !entry.readFrontier)
|
|
20109
|
+
return false;
|
|
20110
|
+
if (!sendSnapshot(entry))
|
|
20111
|
+
return false;
|
|
20112
|
+
const pending = entry.sendChain;
|
|
20113
|
+
await pending;
|
|
20114
|
+
return !entry.closed;
|
|
20115
|
+
};
|
|
20035
20116
|
const leaveAllChatActivity = () => {
|
|
20036
|
-
for (const chatId of [...rooms
|
|
20037
|
-
|
|
20117
|
+
for (const [chatId, entry] of [...rooms]) {
|
|
20118
|
+
rooms.delete(chatId);
|
|
20119
|
+
destroy(entry);
|
|
20120
|
+
}
|
|
20038
20121
|
};
|
|
20039
20122
|
return Object.freeze({
|
|
20040
20123
|
close: leaveAllChatActivity,
|
|
20041
20124
|
enterChatActivity,
|
|
20125
|
+
flushChatReadFrontier,
|
|
20042
20126
|
leaveChatActivity,
|
|
20043
20127
|
leaveAllChatActivity,
|
|
20044
20128
|
markChatReadFrontier,
|
|
@@ -23660,7 +23744,7 @@ async function openChatReceiptCheckpoint(epoch, record) {
|
|
|
23660
23744
|
cleanBytes(keys.key, plaintext);
|
|
23661
23745
|
}
|
|
23662
23746
|
}
|
|
23663
|
-
var CHAT_RECEIPT_CHECKPOINT_VERSION =
|
|
23747
|
+
var CHAT_RECEIPT_CHECKPOINT_VERSION = 2, CHAT_RECEIPT_CHECKPOINT_MAX_BYTES, CHECKPOINT_BODY_VERSION = 1, CHECKPOINT_BODY_SUITE = 1, CHECKPOINT_HEADER_BYTES, CHECKPOINT_SIGN_SCOPE;
|
|
23664
23748
|
var init_chatreceipt = __esm(() => {
|
|
23665
23749
|
init_box();
|
|
23666
23750
|
init_canonical();
|
|
@@ -23672,8 +23756,9 @@ var init_chatreceipt = __esm(() => {
|
|
|
23672
23756
|
init_protocol();
|
|
23673
23757
|
init_receipt();
|
|
23674
23758
|
"use client";
|
|
23759
|
+
CHAT_RECEIPT_CHECKPOINT_MAX_BYTES = 16 * 1024;
|
|
23675
23760
|
CHECKPOINT_HEADER_BYTES = 2 + BOX_NONCE_BYTES;
|
|
23676
|
-
CHECKPOINT_SIGN_SCOPE = encoder.encode("veyl-chat-receipt-checkpoint-
|
|
23761
|
+
CHECKPOINT_SIGN_SCOPE = encoder.encode("veyl-chat-receipt-checkpoint-v4-sign");
|
|
23677
23762
|
});
|
|
23678
23763
|
|
|
23679
23764
|
// ../../core/chat/receiptsession.js
|
|
@@ -23760,6 +23845,7 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
23760
23845
|
let closed = false;
|
|
23761
23846
|
let closing = false;
|
|
23762
23847
|
let checkpoint = null;
|
|
23848
|
+
let displayedProjection = null;
|
|
23763
23849
|
let sourceProjection = null;
|
|
23764
23850
|
let sourceStop = null;
|
|
23765
23851
|
let sourceLeaseTimer = null;
|
|
@@ -23825,8 +23911,10 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
23825
23911
|
checkpoint = next;
|
|
23826
23912
|
if (closing || closed)
|
|
23827
23913
|
return;
|
|
23828
|
-
if (next)
|
|
23829
|
-
|
|
23914
|
+
if (next) {
|
|
23915
|
+
displayedProjection = mergeChatReceiptProjections(displayedProjection, next.projection);
|
|
23916
|
+
onUpdate?.(projected({ ...next, projection: displayedProjection }));
|
|
23917
|
+
}
|
|
23830
23918
|
if (coversDesired(next)) {
|
|
23831
23919
|
desiredState = null;
|
|
23832
23920
|
clearStalledClaimTimer();
|
|
@@ -23839,10 +23927,10 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
23839
23927
|
else
|
|
23840
23928
|
armSourceExpiry();
|
|
23841
23929
|
};
|
|
23842
|
-
const writeCheckpoint = async (mode, expectedVersion = null) => {
|
|
23843
|
-
if (closed || closing)
|
|
23930
|
+
const writeCheckpoint = async (mode, expectedVersion = null, projectionOverride = null) => {
|
|
23931
|
+
if (closed || closing && mode !== "release")
|
|
23844
23932
|
return false;
|
|
23845
|
-
const desiredProjection = mode === "publish" ? sourceProjection : null;
|
|
23933
|
+
const desiredProjection = mode === "publish" || mode === "release" ? projectionOverride || sourceProjection : null;
|
|
23846
23934
|
const result = await cloud.chat.memberState.write({
|
|
23847
23935
|
epochId: epoch.epochId,
|
|
23848
23936
|
lane: address.lane,
|
|
@@ -23853,19 +23941,22 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
23853
23941
|
const current = record ? await openChatReceiptCheckpoint(epoch, record).catch(() => null) : null;
|
|
23854
23942
|
const heldByOther = !holdsLease(current, now) && Number(current?.leaseUntil) > now;
|
|
23855
23943
|
const stalledTakeover = mode === "claim" && expectedVersion && checkpointVersion(current) === expectedVersion && !coversDesired(current);
|
|
23944
|
+
if (mode === "release" && !holdsLease(current, now)) {
|
|
23945
|
+
return { result: { won: false, checkpoint: current } };
|
|
23946
|
+
}
|
|
23856
23947
|
if (heldByOther && !stalledTakeover)
|
|
23857
23948
|
return { result: { won: false, checkpoint: current } };
|
|
23858
|
-
const projection = desiredProjection
|
|
23949
|
+
const projection = desiredProjection ? mergeChatReceiptProjections(current?.projection, desiredProjection) : current?.projection || buildChatReceiptProjection(epoch.manifest, new Map);
|
|
23859
23950
|
if (mode === "publish" && holdsLease(current, now) && chatReceiptProjectionFingerprint(current.projection) === chatReceiptProjectionFingerprint(projection)) {
|
|
23860
23951
|
return { result: { won: true, checkpoint: current } };
|
|
23861
23952
|
}
|
|
23862
23953
|
const term = current ? current.term + (!holdsLease(current, now) || Number(current.leaseUntil) <= now ? 1 : 0) : 1;
|
|
23863
23954
|
const sealed = await sealChatReceiptCheckpoint(epoch, {
|
|
23864
|
-
v:
|
|
23955
|
+
v: CHAT_RECEIPT_CHECKPOINT_VERSION,
|
|
23865
23956
|
coordinator: epoch.actor.chatPK,
|
|
23866
23957
|
holder,
|
|
23867
23958
|
term,
|
|
23868
|
-
leaseUntil: now + RECEIPT_COORDINATOR_LEASE_MS,
|
|
23959
|
+
leaseUntil: mode === "release" ? now : now + RECEIPT_COORDINATOR_LEASE_MS,
|
|
23869
23960
|
publishedAt: now,
|
|
23870
23961
|
projection
|
|
23871
23962
|
});
|
|
@@ -23915,10 +24006,10 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
23915
24006
|
sourceStop = watchChatMemberStateEvidence(cloud, epochState, ({ states }) => {
|
|
23916
24007
|
if (closed || closing || !holdsLease(checkpoint))
|
|
23917
24008
|
return;
|
|
23918
|
-
|
|
23919
|
-
|
|
24009
|
+
sourceProjection = mergeChatReceiptProjections(checkpoint?.projection, buildChatReceiptProjection(epoch.manifest, states));
|
|
24010
|
+
displayedProjection = mergeChatReceiptProjections(displayedProjection, sourceProjection);
|
|
23920
24011
|
onUpdate?.(projectChatReceiptCheckpoint(epoch.manifest, {
|
|
23921
|
-
projection:
|
|
24012
|
+
projection: displayedProjection,
|
|
23922
24013
|
publishedAt: Date.now()
|
|
23923
24014
|
}));
|
|
23924
24015
|
schedulePublish();
|
|
@@ -24008,6 +24099,8 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
24008
24099
|
if (closed || closing)
|
|
24009
24100
|
return;
|
|
24010
24101
|
closing = true;
|
|
24102
|
+
const finalProjection = sourceProjection;
|
|
24103
|
+
const release = holdsLease(checkpoint) ? track(writeCheckpoint("release", null, finalProjection)) : null;
|
|
24011
24104
|
watchRun += 1;
|
|
24012
24105
|
clearClaimTimer();
|
|
24013
24106
|
clearStalledClaimTimer();
|
|
@@ -24021,7 +24114,7 @@ function createChatReceiptSession({ cloud, epochState, identity, onUpdate, onErr
|
|
|
24021
24114
|
closing = false;
|
|
24022
24115
|
closeReceiptEpoch(opened);
|
|
24023
24116
|
};
|
|
24024
|
-
Promise.allSettled([...operations]).finally(finish);
|
|
24117
|
+
Promise.allSettled([...operations, release].filter(Boolean)).finally(finish);
|
|
24025
24118
|
}
|
|
24026
24119
|
};
|
|
24027
24120
|
}
|
|
@@ -25093,6 +25186,62 @@ var init_session2 = __esm(() => {
|
|
|
25093
25186
|
init_receiptsession();
|
|
25094
25187
|
});
|
|
25095
25188
|
|
|
25189
|
+
// ../../core/chat/retirement.js
|
|
25190
|
+
function planChatRetirement(chat, selfChatPK) {
|
|
25191
|
+
const self2 = cleanText(selfChatPK);
|
|
25192
|
+
const members = Array.isArray(chat?.members) ? chat.members : [];
|
|
25193
|
+
const remaining = members.filter((member) => cleanText(member?.chatPK) !== self2);
|
|
25194
|
+
const direct = chat?.lineage === CHAT_LINEAGES.DIRECT && members.length === 2;
|
|
25195
|
+
return {
|
|
25196
|
+
action: direct || !remaining.length ? CHAT_RETIREMENT_ACTIONS.DELETE : CHAT_RETIREMENT_ACTIONS.LEAVE,
|
|
25197
|
+
remaining
|
|
25198
|
+
};
|
|
25199
|
+
}
|
|
25200
|
+
async function retireChatMembershipOwner(cloud, identity, entry, fields = {}) {
|
|
25201
|
+
const parent = entry?.current?.manifest;
|
|
25202
|
+
const kind = fields.kind;
|
|
25203
|
+
const epochVersion = fields.epochVersion;
|
|
25204
|
+
if (!parent?.chatId || !identity?.uid || !identity?.chatPrivateKey || !Object.values(CHAT_OWNER_RETIREMENT_KINDS).includes(kind) || !Number.isSafeInteger(epochVersion)) {
|
|
25205
|
+
throw new Error("chat membership retirement required");
|
|
25206
|
+
}
|
|
25207
|
+
const entryId = ownChatEntryId(identity.chatPrivateKey, parent.chatId);
|
|
25208
|
+
const historyId = ownEpochEntryId(identity.chatPrivateKey, parent.chatId, parent.epochId);
|
|
25209
|
+
const cutoffMs = Number.isFinite(fields.cutoffMs) ? fields.cutoffMs : Date.now();
|
|
25210
|
+
const history = makeOwnerEpochRecord(parent, entry.current.epochSecret, {
|
|
25211
|
+
cutoffMs,
|
|
25212
|
+
successorTransitionCommitment: fields.successorTransitionCommitment,
|
|
25213
|
+
successorTransitionMessageId: fields.successorTransitionMessageId,
|
|
25214
|
+
leaveProposalId: fields.leaveProposalId
|
|
25215
|
+
});
|
|
25216
|
+
return retireOwnChatEntry(cloud, identity, entryId, entry, {
|
|
25217
|
+
expectedEpochVersion: parent.epochVersion,
|
|
25218
|
+
retirement: {
|
|
25219
|
+
kind,
|
|
25220
|
+
epochVersion,
|
|
25221
|
+
...kind === CHAT_OWNER_RETIREMENT_KINDS.LEAVE ? { proposalId: fields.leaveProposalId } : {}
|
|
25222
|
+
},
|
|
25223
|
+
epoch: {
|
|
25224
|
+
id: historyId,
|
|
25225
|
+
record: {
|
|
25226
|
+
body: await sealOwnerEpochRecord(identity.chatPrivateKey, entryId, historyId, history),
|
|
25227
|
+
tsMs: cutoffMs
|
|
25228
|
+
}
|
|
25229
|
+
},
|
|
25230
|
+
mlsStateId: ownerChatMlsSnapshotId(identity, entryId, entry.current)
|
|
25231
|
+
});
|
|
25232
|
+
}
|
|
25233
|
+
var CHAT_RETIREMENT_ACTIONS;
|
|
25234
|
+
var init_retirement = __esm(() => {
|
|
25235
|
+
init_entry();
|
|
25236
|
+
init_manifest();
|
|
25237
|
+
init_owner2();
|
|
25238
|
+
init_owner();
|
|
25239
|
+
CHAT_RETIREMENT_ACTIONS = Object.freeze({
|
|
25240
|
+
DELETE: "delete",
|
|
25241
|
+
LEAVE: "leave"
|
|
25242
|
+
});
|
|
25243
|
+
});
|
|
25244
|
+
|
|
25096
25245
|
// ../../core/chat/inbox.js
|
|
25097
25246
|
function normalizeChatPreview(record, message) {
|
|
25098
25247
|
if (!message || typeof message !== "object")
|
|
@@ -25394,7 +25543,7 @@ function sameRoutes(left, right) {
|
|
|
25394
25543
|
return leftKeys.length === rightKeys.length && leftKeys.every((key) => sameRoute(left[key], right[key]));
|
|
25395
25544
|
}
|
|
25396
25545
|
function sameStableEntry(left, right) {
|
|
25397
|
-
return !!left && !!right && left.current?.manifest?.epochId === right.current?.manifest?.epochId && left.current?.settings?.digest === right.current?.settings?.digest && sameRoutes(left.routes, right.routes) && left.deliveryRegistered === right.deliveryRegistered && left.notificationTag === right.notificationTag;
|
|
25546
|
+
return !!left && !!right && left.current?.manifest?.epochId === right.current?.manifest?.epochId && left.current?.settings?.digest === right.current?.settings?.digest && sameRoutes(left.routes, right.routes) && left.deliveryRegistered === right.deliveryRegistered && left.notificationTag === right.notificationTag && left.retirement?.kind === right.retirement?.kind && left.retirement?.epochVersion === right.retirement?.epochVersion;
|
|
25398
25547
|
}
|
|
25399
25548
|
function projectOwnChatEntry(entry, entryId, userChatPK, ts, activity = {}) {
|
|
25400
25549
|
const manifest = entry.current.manifest;
|
|
@@ -25444,13 +25593,17 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25444
25593
|
}
|
|
25445
25594
|
let knownMember = entry?.current?.manifest?.members?.find((member) => member.chatPK === payload.senderChatPK) || null;
|
|
25446
25595
|
const senderProfile = openedProfile || (knownMember ? { ...knownMember, uid: knownMember.uid } : null) || await resolveSenderProfile(cloud, payload);
|
|
25447
|
-
|
|
25596
|
+
let restoringMembership = !!entry?.retirement;
|
|
25597
|
+
if (restoringMembership && (payload.kind !== "welcome" || payload.epochVersion <= entry.retirement.epochVersion)) {
|
|
25598
|
+
return "stale";
|
|
25599
|
+
}
|
|
25600
|
+
if (entry && !restoringMembership && payload.epochVersion === entry.current.manifest.epochVersion && cleanText(payload.messageId) && cleanText(currentChat?.inboxMessageId) === cleanText(payload.messageId)) {
|
|
25448
25601
|
return "duplicate";
|
|
25449
25602
|
}
|
|
25450
|
-
if (entry && payload.epochVersion < entry.current.manifest.epochVersion) {
|
|
25603
|
+
if (entry && !restoringMembership && payload.epochVersion < entry.current.manifest.epochVersion) {
|
|
25451
25604
|
return "stale";
|
|
25452
25605
|
}
|
|
25453
|
-
if (entry && payload.epochVersion > entry.current.manifest.epochVersion) {
|
|
25606
|
+
if (entry && !restoringMembership && payload.epochVersion > entry.current.manifest.epochVersion) {
|
|
25454
25607
|
let advanced;
|
|
25455
25608
|
try {
|
|
25456
25609
|
advanced = await advanceEntryToEpoch(cloud, uid, identity, entry, payload.epochVersion, options);
|
|
@@ -25467,11 +25620,16 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25467
25620
|
}
|
|
25468
25621
|
}
|
|
25469
25622
|
if (advanced?.removed) {
|
|
25470
|
-
|
|
25471
|
-
|
|
25623
|
+
if (payload.kind !== "welcome" || payload.epochVersion <= advanced.entry?.retirement?.epochVersion) {
|
|
25624
|
+
options.onPingDelete?.(payload.chatId);
|
|
25625
|
+
return "deleted";
|
|
25626
|
+
}
|
|
25627
|
+
entry = advanced.entry;
|
|
25628
|
+
restoringMembership = true;
|
|
25629
|
+
} else {
|
|
25630
|
+
entry = advanced;
|
|
25631
|
+
knownMember = entry.current.manifest.members.find((member) => member.chatPK === payload.senderChatPK) || null;
|
|
25472
25632
|
}
|
|
25473
|
-
entry = advanced;
|
|
25474
|
-
knownMember = entry.current.manifest.members.find((member) => member.chatPK === payload.senderChatPK) || null;
|
|
25475
25633
|
}
|
|
25476
25634
|
if (payload.kind === "chat_deleted") {
|
|
25477
25635
|
if (!entry || entry.current.manifest.epochId !== payload.epochId || entry.current.manifest.epochVersion !== payload.epochVersion || !knownMember) {
|
|
@@ -25491,7 +25649,7 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25491
25649
|
return "deleted";
|
|
25492
25650
|
}
|
|
25493
25651
|
let epochState = entry?.current || null;
|
|
25494
|
-
if (!entry && payload.kind === "welcome") {
|
|
25652
|
+
if ((!entry || restoringMembership) && payload.kind === "welcome") {
|
|
25495
25653
|
epochState = await welcomedEpochFromPing(cloud, identity, senderProfile, payload, options);
|
|
25496
25654
|
} else if (!entry) {
|
|
25497
25655
|
throw new Error("chat mls welcome required");
|
|
@@ -25539,7 +25697,7 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25539
25697
|
const routes = withSenderRoute(entry, payload, senderProfile, epochState);
|
|
25540
25698
|
const preview2 = nextPreview(currentChat, record, message, payload);
|
|
25541
25699
|
const nowMs2 = timestampMs(record?.ts, null) ?? timestampMs(payload.ts, Date.now()) ?? Date.now();
|
|
25542
|
-
const membershipWelcome = !entry && payload.kind === "welcome";
|
|
25700
|
+
const membershipWelcome = (!entry || restoringMembership) && payload.kind === "welcome";
|
|
25543
25701
|
const projectedTsMs = membershipWelcome ? Date.now() : nowMs2;
|
|
25544
25702
|
let deliveryRegistered = entry?.deliveryRegistered === true;
|
|
25545
25703
|
if (!deliveryRegistered)
|
|
@@ -25557,7 +25715,8 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25557
25715
|
},
|
|
25558
25716
|
routes,
|
|
25559
25717
|
deliveryRegistered,
|
|
25560
|
-
notificationTag: notificationChatTag(epochState.stateCapability, payload.chatId, identity.chatPK)
|
|
25718
|
+
notificationTag: notificationChatTag(epochState.stateCapability, payload.chatId, identity.chatPK),
|
|
25719
|
+
retirement: null
|
|
25561
25720
|
} : makeOwnChatEntry(epochState, {
|
|
25562
25721
|
routes,
|
|
25563
25722
|
startMs: nowMs2,
|
|
@@ -25573,6 +25732,9 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25573
25732
|
...welcomeMlsState ? { mlsWrites: [{ id: welcomeMlsState.stateId, value: welcomeMlsState.value }] } : {}
|
|
25574
25733
|
};
|
|
25575
25734
|
}
|
|
25735
|
+
if (membershipWelcome && current.retirement && epochState.manifest.epochVersion <= current.retirement.epochVersion) {
|
|
25736
|
+
throw new Error("chat welcome is not newer than retirement");
|
|
25737
|
+
}
|
|
25576
25738
|
if (current.current.manifest.epochVersion > epochState.manifest.epochVersion) {
|
|
25577
25739
|
return { result: current };
|
|
25578
25740
|
}
|
|
@@ -25581,7 +25743,8 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25581
25743
|
current: nextEntry.current,
|
|
25582
25744
|
routes: withSenderRoute(current, payload, senderProfile, epochState),
|
|
25583
25745
|
deliveryRegistered: current.deliveryRegistered || nextEntry.deliveryRegistered,
|
|
25584
|
-
notificationTag: nextEntry.notificationTag
|
|
25746
|
+
notificationTag: nextEntry.notificationTag,
|
|
25747
|
+
retirement: membershipWelcome ? null : current.retirement
|
|
25585
25748
|
};
|
|
25586
25749
|
if (sameStableEntry(current, candidate) && !welcomeMlsState)
|
|
25587
25750
|
return { result: current };
|
|
@@ -25591,6 +25754,9 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25591
25754
|
...welcomeMlsState ? { mlsWrites: [{ id: welcomeMlsState.stateId, value: welcomeMlsState.value }] } : {}
|
|
25592
25755
|
};
|
|
25593
25756
|
}) : entry;
|
|
25757
|
+
if (committedEntry?.retirement) {
|
|
25758
|
+
throw new Error("chat membership remains retired");
|
|
25759
|
+
}
|
|
25594
25760
|
if (membershipWelcome && epochState.mlsSnapshot) {
|
|
25595
25761
|
if (!epochState.mlsKeyPackageLastResort) {
|
|
25596
25762
|
await cloud.user.mls.keyPackages.deleteState(identity.uid, epochState.mlsKeyPackageId).catch(() => false);
|
|
@@ -25695,15 +25861,19 @@ async function applyEpochTransitionAction(cloud, uid, identityValue, entry, acti
|
|
|
25695
25861
|
});
|
|
25696
25862
|
await cloud.delivery.revoke(capability).catch(() => false);
|
|
25697
25863
|
}
|
|
25698
|
-
|
|
25699
|
-
|
|
25700
|
-
|
|
25701
|
-
|
|
25864
|
+
markDiag(options.diag, "chat.owner.retire", { reason: "membership" });
|
|
25865
|
+
const retiredEntry = await retireChatMembershipOwner(cloud, identity, entry, {
|
|
25866
|
+
kind: CHAT_OWNER_RETIREMENT_KINDS.REMOVED,
|
|
25867
|
+
epochVersion: confirmed.nextEpochVersion,
|
|
25868
|
+
cutoffMs: Date.now(),
|
|
25869
|
+
successorTransitionCommitment: confirmed.transitionCommitment,
|
|
25870
|
+
successorTransitionMessageId: messageId
|
|
25871
|
+
});
|
|
25702
25872
|
if (leaveProposalId) {
|
|
25703
25873
|
options.leaveProposalObservedAt?.delete(`${parent.chatId}:${leaveProposalId}`);
|
|
25704
25874
|
}
|
|
25705
25875
|
options.onRemoved?.(parent.chatId, confirmed.nextEpochVersion);
|
|
25706
|
-
return { removed: true, chatId: parent.chatId };
|
|
25876
|
+
return { removed: true, chatId: parent.chatId, entry: retiredEntry };
|
|
25707
25877
|
}
|
|
25708
25878
|
openStage = "mls-package-open";
|
|
25709
25879
|
const opened = await openChatMlsEpochPackage(processed, packageRecord, {
|
|
@@ -25966,6 +26136,7 @@ var init_inbox2 = __esm(() => {
|
|
|
25966
26136
|
init_membership();
|
|
25967
26137
|
init_entry();
|
|
25968
26138
|
init_owner();
|
|
26139
|
+
init_retirement();
|
|
25969
26140
|
init_manifest();
|
|
25970
26141
|
init_state2();
|
|
25971
26142
|
init_transition();
|
|
@@ -26386,6 +26557,9 @@ async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
|
|
|
26386
26557
|
if (entry.ownerRevision !== data?.revision) {
|
|
26387
26558
|
throw new Error("chat owner revision mismatch");
|
|
26388
26559
|
}
|
|
26560
|
+
if (entry.retirement) {
|
|
26561
|
+
return null;
|
|
26562
|
+
}
|
|
26389
26563
|
if (entry.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
26390
26564
|
return null;
|
|
26391
26565
|
}
|
|
@@ -27489,26 +27663,6 @@ var init_listsession = __esm(() => {
|
|
|
27489
27663
|
CHAT_LIST_RESET_ERROR_CODES = new Set(["permission-denied", "unauthenticated"]);
|
|
27490
27664
|
});
|
|
27491
27665
|
|
|
27492
|
-
// ../../core/chat/retirement.js
|
|
27493
|
-
function planChatRetirement(chat, selfChatPK) {
|
|
27494
|
-
const self2 = cleanText(selfChatPK);
|
|
27495
|
-
const members = Array.isArray(chat?.members) ? chat.members : [];
|
|
27496
|
-
const remaining = members.filter((member) => cleanText(member?.chatPK) !== self2);
|
|
27497
|
-
const direct = chat?.lineage === CHAT_LINEAGES.DIRECT && members.length === 2;
|
|
27498
|
-
return {
|
|
27499
|
-
action: direct || !remaining.length ? CHAT_RETIREMENT_ACTIONS.DELETE : CHAT_RETIREMENT_ACTIONS.LEAVE,
|
|
27500
|
-
remaining
|
|
27501
|
-
};
|
|
27502
|
-
}
|
|
27503
|
-
var CHAT_RETIREMENT_ACTIONS;
|
|
27504
|
-
var init_retirement = __esm(() => {
|
|
27505
|
-
init_manifest();
|
|
27506
|
-
CHAT_RETIREMENT_ACTIONS = Object.freeze({
|
|
27507
|
-
DELETE: "delete",
|
|
27508
|
-
LEAVE: "leave"
|
|
27509
|
-
});
|
|
27510
|
-
});
|
|
27511
|
-
|
|
27512
27666
|
// ../../core/utils/appstate.js
|
|
27513
27667
|
function isForegroundAppState(state) {
|
|
27514
27668
|
const value = String(state || "").trim().toLowerCase();
|
|
@@ -27733,6 +27887,7 @@ function createChatSession({
|
|
|
27733
27887
|
deleteAllChats,
|
|
27734
27888
|
restoreDeletedChat,
|
|
27735
27889
|
enterChatActivity,
|
|
27890
|
+
flushChatReadFrontier,
|
|
27736
27891
|
leaveChatActivity,
|
|
27737
27892
|
markChatReadState,
|
|
27738
27893
|
markChatRead,
|
|
@@ -28659,9 +28814,13 @@ function createChatSession({
|
|
|
28659
28814
|
recipientChatPK: chatPK,
|
|
28660
28815
|
generation: chat.epochVersion
|
|
28661
28816
|
});
|
|
28817
|
+
await retireChatMembershipOwner(cloud, transitionIdentity(), chat.ownEntry, {
|
|
28818
|
+
kind: CHAT_OWNER_RETIREMENT_KINDS.LEAVE,
|
|
28819
|
+
epochVersion: chat.epochVersion,
|
|
28820
|
+
cutoffMs: Date.now(),
|
|
28821
|
+
leaveProposalId: proposal.message.cid
|
|
28822
|
+
});
|
|
28662
28823
|
await cloud.delivery.revoke(capability).catch(() => false);
|
|
28663
|
-
await discardOwnerChatMlsSnapshot(cloud, transitionIdentity(), chat.entryId, chat.epochState);
|
|
28664
|
-
await cloud.user.chats.unlink(uid, chat.entryId);
|
|
28665
28824
|
if (retirementStarted) {
|
|
28666
28825
|
deleteActions.commitRemovedRetirement(chatId, chat.epochVersion + 1);
|
|
28667
28826
|
retirementStarted = false;
|
|
@@ -29018,11 +29177,12 @@ function createChatSession({
|
|
|
29018
29177
|
const dropChat = (...args) => deleteActions.dropChat(...args);
|
|
29019
29178
|
const dropUnavailableChat = (...args) => deleteActions.dropUnavailableChat(...args);
|
|
29020
29179
|
const deleteChat = (...args) => deleteActions.deleteChat(...args);
|
|
29021
|
-
const
|
|
29180
|
+
const purgePeerChats = async (affected) => {
|
|
29022
29181
|
for (const chat of affected) {
|
|
29023
29182
|
const retirement = planChatRetirement(chat, chatPK);
|
|
29024
29183
|
if (retirement.action === CHAT_RETIREMENT_ACTIONS.LEAVE) {
|
|
29025
29184
|
await leaveOwnedChat(chat, { optimistic: false });
|
|
29185
|
+
await cloud.user.chats.unlink(uid, chat.entryId);
|
|
29026
29186
|
} else {
|
|
29027
29187
|
await deleteChat(chat, { cleanup: false });
|
|
29028
29188
|
}
|
|
@@ -29032,7 +29192,7 @@ function createChatSession({
|
|
|
29032
29192
|
const retirePeerChat = async (peerChatPK, options = {}) => {
|
|
29033
29193
|
const allChats = await loadAllChats(cloud, uid, chatPK, chatPrivateKey);
|
|
29034
29194
|
const affected = allChats.filter((chat) => chat?.members?.some((member) => member.chatPK === peerChatPK));
|
|
29035
|
-
await
|
|
29195
|
+
await purgePeerChats(affected);
|
|
29036
29196
|
await options.onDeliveryRevoked?.();
|
|
29037
29197
|
return affected.length > 0;
|
|
29038
29198
|
};
|
|
@@ -29041,7 +29201,7 @@ function createChatSession({
|
|
|
29041
29201
|
return 0;
|
|
29042
29202
|
const allChats = await loadAllChats(cloud, uid, chatPK, chatPrivateKey);
|
|
29043
29203
|
const affected = allChats.filter((chat) => chat?.members?.some((member) => member.chatPK !== chatPK && blockedUids.has(member.uid)));
|
|
29044
|
-
return
|
|
29204
|
+
return purgePeerChats(affected);
|
|
29045
29205
|
};
|
|
29046
29206
|
const restoreDeletedChat = (...args) => deleteActions.restoreDeletedChat(...args);
|
|
29047
29207
|
const wasChatDeletedLocally = (...args) => deleteActions.wasChatDeletedLocally(...args);
|
|
@@ -29055,6 +29215,7 @@ function createChatSession({
|
|
|
29055
29215
|
});
|
|
29056
29216
|
return liveActions.enterChatActivity(chatId);
|
|
29057
29217
|
};
|
|
29218
|
+
const flushChatReadFrontier = (chatId) => liveActions.flushChatReadFrontier(chatId);
|
|
29058
29219
|
const leaveChatActivity = (chatId) => {
|
|
29059
29220
|
const flush = seenActions.flushChatRead(chatId);
|
|
29060
29221
|
liveActions.leaveChatActivity(chatId);
|
|
@@ -29628,7 +29789,6 @@ var init_session3 = __esm(() => {
|
|
|
29628
29789
|
init_create();
|
|
29629
29790
|
init_membership();
|
|
29630
29791
|
init_pool();
|
|
29631
|
-
init_owner2();
|
|
29632
29792
|
init_manifest();
|
|
29633
29793
|
init_notifications();
|
|
29634
29794
|
init_state2();
|
|
@@ -39360,6 +39520,8 @@ async function listenAccount(client, options = {}) {
|
|
|
39360
39520
|
const includeChats = options.chats !== false;
|
|
39361
39521
|
const includeTransactions = options.transactions !== false;
|
|
39362
39522
|
const markRead = options.read !== false;
|
|
39523
|
+
const relayReads = markRead && options.relayReads === true;
|
|
39524
|
+
const readLiveIdleMs = Number.isFinite(Number(options.readLiveIdleMs)) ? Math.max(0, Math.floor(Number(options.readLiveIdleMs))) : 15000;
|
|
39363
39525
|
const replay = options.replay === true;
|
|
39364
39526
|
const persistentChatIdleMs = Number.isFinite(Number(options.persistentChatIdleMs)) ? Math.max(0, Math.floor(Number(options.persistentChatIdleMs))) : 0;
|
|
39365
39527
|
const emit2 = typeof options.onEvent === "function" ? options.onEvent : () => {};
|
|
@@ -39370,8 +39532,12 @@ async function listenAccount(client, options = {}) {
|
|
|
39370
39532
|
const chatActivityTimers = new Map;
|
|
39371
39533
|
const subscribedWakeVersions = new Map;
|
|
39372
39534
|
const idleWakeVersions = new Map;
|
|
39535
|
+
const messageBatchTasks = new Map;
|
|
39373
39536
|
const pendingChats = new Map;
|
|
39374
39537
|
const processedWakeVersions = new Map;
|
|
39538
|
+
const readLiveChats = new Set;
|
|
39539
|
+
const readLiveEntering = new Map;
|
|
39540
|
+
const readLiveTimers = new Map;
|
|
39375
39541
|
const wantedWakeVersions = new Map;
|
|
39376
39542
|
let activeChatIds = new Set;
|
|
39377
39543
|
let chatSyncRequested = false;
|
|
@@ -39416,6 +39582,44 @@ async function listenAccount(client, options = {}) {
|
|
|
39416
39582
|
const account = client.accountSummary();
|
|
39417
39583
|
const runtime = client.runtime;
|
|
39418
39584
|
const session = client.session;
|
|
39585
|
+
async function enterReadLive(chatId) {
|
|
39586
|
+
if (!relayReads)
|
|
39587
|
+
return false;
|
|
39588
|
+
if (readLiveChats.has(chatId))
|
|
39589
|
+
return true;
|
|
39590
|
+
const current = readLiveEntering.get(chatId);
|
|
39591
|
+
if (current)
|
|
39592
|
+
return current;
|
|
39593
|
+
const entering = runtime.chat.getSnapshot().enterChatActivity(chatId).then(async (entered) => {
|
|
39594
|
+
if (!entered)
|
|
39595
|
+
return false;
|
|
39596
|
+
if (closed) {
|
|
39597
|
+
await runtime.chat.getSnapshot().leaveChatActivity(chatId);
|
|
39598
|
+
return false;
|
|
39599
|
+
}
|
|
39600
|
+
readLiveChats.add(chatId);
|
|
39601
|
+
return true;
|
|
39602
|
+
}).finally(() => readLiveEntering.delete(chatId));
|
|
39603
|
+
readLiveEntering.set(chatId, entering);
|
|
39604
|
+
return entering;
|
|
39605
|
+
}
|
|
39606
|
+
function releaseReadLiveSoon(chatId) {
|
|
39607
|
+
if (!readLiveChats.has(chatId))
|
|
39608
|
+
return;
|
|
39609
|
+
const current = readLiveTimers.get(chatId);
|
|
39610
|
+
if (current)
|
|
39611
|
+
clearTimeout(current);
|
|
39612
|
+
const timer = setTimeout(() => {
|
|
39613
|
+
if (readLiveTimers.get(chatId) !== timer)
|
|
39614
|
+
return;
|
|
39615
|
+
readLiveTimers.delete(chatId);
|
|
39616
|
+
if (!readLiveChats.delete(chatId))
|
|
39617
|
+
return;
|
|
39618
|
+
runtime.chat.getSnapshot().leaveChatActivity(chatId);
|
|
39619
|
+
}, readLiveIdleMs);
|
|
39620
|
+
timer.unref?.();
|
|
39621
|
+
readLiveTimers.set(chatId, timer);
|
|
39622
|
+
}
|
|
39419
39623
|
function projectedChat(chat) {
|
|
39420
39624
|
const peerChatPK = getChatPeerPK(chat);
|
|
39421
39625
|
const peerByChatPK = runtime.peers.getSnapshot().peerByChatPK;
|
|
@@ -39435,13 +39639,15 @@ async function listenAccount(client, options = {}) {
|
|
|
39435
39639
|
const sourceMessages = batch?.sourceMessages?.length ? batch.sourceMessages : batch?.messages || [];
|
|
39436
39640
|
return projectMessages(sourceMessages, session.chatPK, peerChatPK || session.chatPK, chat?.memberChatPKs).map((message) => simplifyAccountMessage(message, chat, session.chatPK, { raw: true }));
|
|
39437
39641
|
}
|
|
39438
|
-
function applyMessageBatch(chat, batch, emitChanges) {
|
|
39642
|
+
async function applyMessageBatch(chat, batch, emitChanges) {
|
|
39439
39643
|
if (!batch?.ready)
|
|
39440
39644
|
return;
|
|
39441
39645
|
touchChatActivity(chat.id);
|
|
39442
39646
|
const targetChat = projectedChat(chat);
|
|
39647
|
+
const rawMessages = batch?.sourceMessages?.length ? batch.sourceMessages : batch?.messages || [];
|
|
39443
39648
|
const messages = projectMessages2(chat, batch);
|
|
39444
39649
|
const currentKeys = new Set;
|
|
39650
|
+
const messageEvents = [];
|
|
39445
39651
|
for (const message of messages) {
|
|
39446
39652
|
const key = messageKey(message);
|
|
39447
39653
|
if (!key)
|
|
@@ -39452,16 +39658,37 @@ async function listenAccount(client, options = {}) {
|
|
|
39452
39658
|
seenMessages.set(key, version);
|
|
39453
39659
|
if (!emitChanges || previous === version || incomingOnly && message.from !== "peer")
|
|
39454
39660
|
continue;
|
|
39455
|
-
|
|
39456
|
-
}
|
|
39457
|
-
const latestPeer = [...
|
|
39458
|
-
const latestPeerKey = latestPeer ? messageKey(latestPeer) : "";
|
|
39459
|
-
|
|
39460
|
-
|
|
39461
|
-
|
|
39462
|
-
|
|
39463
|
-
|
|
39661
|
+
messageEvents.push(formatMessageEvent(targetChat, message, { compact: compact2 }));
|
|
39662
|
+
}
|
|
39663
|
+
const latestPeer = [...rawMessages].reverse().find((message) => isPeerMsg(message, session.chatPK));
|
|
39664
|
+
const latestPeerKey = latestPeer ? messageKey({ ...latestPeer, chatId: chat.id }) : "";
|
|
39665
|
+
const latestPeerEvent = latestPeer ? formatMessageEvent(targetChat, simplifyAccountMessage(latestPeer, chat, session.chatPK, { raw: true }), { compact: compact2 }) : null;
|
|
39666
|
+
const readTarget = getLatestReadStateTarget(rawMessages);
|
|
39667
|
+
const readTargetKey = readTarget ? messageKey({ ...readTarget, chatId: chat.id }) : "";
|
|
39668
|
+
const alreadyProcessed = latestPeerEvent && options.isProcessedEvent?.(latestPeerEvent) === true;
|
|
39669
|
+
if (markRead && readTargetKey && !(alreadyProcessed && readTargetKey === latestPeerKey) && readMessages.get(chat.id) !== readTargetKey) {
|
|
39670
|
+
readMessages.set(chat.id, readTargetKey);
|
|
39671
|
+
if (relayReads) {
|
|
39672
|
+
try {
|
|
39673
|
+
await enterReadLive(chat.id);
|
|
39674
|
+
await runtime.chat.getSnapshot().markChatReadState(chat.id, readTarget, { messages: rawMessages });
|
|
39675
|
+
await runtime.chat.getSnapshot().flushChatReadFrontier?.(chat.id);
|
|
39676
|
+
} catch (error) {
|
|
39677
|
+
emit2(event("error", {
|
|
39678
|
+
peer: peerTarget(targetChat),
|
|
39679
|
+
message: error?.message || String(error)
|
|
39680
|
+
}));
|
|
39681
|
+
} finally {
|
|
39682
|
+
releaseReadLiveSoon(chat.id);
|
|
39683
|
+
}
|
|
39684
|
+
} else {
|
|
39685
|
+
runtime.chat.getSnapshot().markChatReadState(chat.id, readTarget, {
|
|
39686
|
+
messages: rawMessages
|
|
39687
|
+
});
|
|
39688
|
+
}
|
|
39464
39689
|
}
|
|
39690
|
+
for (const messageEvent of messageEvents)
|
|
39691
|
+
emit2(messageEvent);
|
|
39465
39692
|
const previousForChat = [...seenMessages.keys()].filter((key) => key.startsWith(`${chat.id}:`));
|
|
39466
39693
|
for (const key of previousForChat) {
|
|
39467
39694
|
if (currentKeys.has(key))
|
|
@@ -39480,6 +39707,21 @@ async function listenAccount(client, options = {}) {
|
|
|
39480
39707
|
}
|
|
39481
39708
|
}
|
|
39482
39709
|
}
|
|
39710
|
+
function queueMessageBatch(chat, batch, emitChanges) {
|
|
39711
|
+
const previous = messageBatchTasks.get(chat.id) || Promise.resolve();
|
|
39712
|
+
const task = previous.catch(() => {}).then(() => applyMessageBatch(chat, batch, emitChanges));
|
|
39713
|
+
messageBatchTasks.set(chat.id, task);
|
|
39714
|
+
task.then(() => {
|
|
39715
|
+
if (messageBatchTasks.get(chat.id) === task) {
|
|
39716
|
+
messageBatchTasks.delete(chat.id);
|
|
39717
|
+
}
|
|
39718
|
+
}, () => {
|
|
39719
|
+
if (messageBatchTasks.get(chat.id) === task) {
|
|
39720
|
+
messageBatchTasks.delete(chat.id);
|
|
39721
|
+
}
|
|
39722
|
+
});
|
|
39723
|
+
return task;
|
|
39724
|
+
}
|
|
39483
39725
|
function checkpointBatch(chat, page) {
|
|
39484
39726
|
const sourceMessages = page?.history?.sourceMessages?.length ? page.history.sourceMessages : page?.batch?.sourceMessages?.length ? page.batch.sourceMessages : page?.batch?.messages || [];
|
|
39485
39727
|
const batch = {
|
|
@@ -39566,13 +39808,15 @@ async function listenAccount(client, options = {}) {
|
|
|
39566
39808
|
if (closed || !activeChatIds.has(chat.id) || !page.chat || !transientChats && chatSubscriptions.has(chat.id)) {
|
|
39567
39809
|
return;
|
|
39568
39810
|
}
|
|
39569
|
-
|
|
39811
|
+
await queueMessageBatch(chat, page.batch, emitInitial);
|
|
39570
39812
|
if (transientChats) {
|
|
39571
39813
|
processedWakeVersions.set(chat.id, wakeVersion);
|
|
39572
39814
|
return;
|
|
39573
39815
|
}
|
|
39574
39816
|
const batches = runtime.chat.getSnapshot().messageBatches;
|
|
39575
|
-
const unsubscribe = batches.subscribeMessageBatch(chat.id, (batch) =>
|
|
39817
|
+
const unsubscribe = batches.subscribeMessageBatch(chat.id, (batch) => {
|
|
39818
|
+
queueMessageBatch(chat, batch, ready).catch((error) => emit2(event("error", { peer: peerTarget(targetChat), message: error?.message || String(error) })));
|
|
39819
|
+
});
|
|
39576
39820
|
chatSubscriptions.set(chat.id, () => {
|
|
39577
39821
|
unsubscribe();
|
|
39578
39822
|
page.release?.();
|
|
@@ -39706,6 +39950,13 @@ async function listenAccount(client, options = {}) {
|
|
|
39706
39950
|
for (const timer of chatActivityTimers.values())
|
|
39707
39951
|
clearTimeout(timer);
|
|
39708
39952
|
chatActivityTimers.clear();
|
|
39953
|
+
for (const timer of readLiveTimers.values())
|
|
39954
|
+
clearTimeout(timer);
|
|
39955
|
+
readLiveTimers.clear();
|
|
39956
|
+
await Promise.allSettled(messageBatchTasks.values());
|
|
39957
|
+
await Promise.allSettled(readLiveEntering.values());
|
|
39958
|
+
await Promise.allSettled([...readLiveChats].map((chatId) => runtime.chat.getSnapshot().leaveChatActivity(chatId)));
|
|
39959
|
+
readLiveChats.clear();
|
|
39709
39960
|
processedWakeVersions.clear();
|
|
39710
39961
|
wantedWakeVersions.clear();
|
|
39711
39962
|
}
|
|
@@ -39714,6 +39965,7 @@ var DEFAULT_CHAT_COUNT = 20, DEFAULT_MESSAGE_COUNT = 10, DEFAULT_REPLAY_MESSAGE_
|
|
|
39714
39965
|
var init_listen = __esm(() => {
|
|
39715
39966
|
init_actions2();
|
|
39716
39967
|
init_ids();
|
|
39968
|
+
init_messages();
|
|
39717
39969
|
init_presentation();
|
|
39718
39970
|
init_messages3();
|
|
39719
39971
|
});
|
|
@@ -41573,6 +41825,7 @@ function mergeLatestServerPages(primary, secondary) {
|
|
|
41573
41825
|
return {
|
|
41574
41826
|
...secondary || {},
|
|
41575
41827
|
...primary || {},
|
|
41828
|
+
memberStatesByEpoch: mergeChatReceiptCheckpointsByEpoch(secondary?.memberStatesByEpoch, primary?.memberStatesByEpoch),
|
|
41576
41829
|
expiredKeys: keySet([...primary?.expiredKeys || [], ...secondary?.expiredKeys || []]),
|
|
41577
41830
|
deletedKeys: keySet([...primary?.deletedKeys || [], ...secondary?.deletedKeys || []])
|
|
41578
41831
|
};
|
|
@@ -41720,6 +41973,7 @@ var init_screen = __esm(() => {
|
|
|
41720
41973
|
init_messagekeys();
|
|
41721
41974
|
init_ids();
|
|
41722
41975
|
init_state();
|
|
41976
|
+
init_receipt();
|
|
41723
41977
|
init_time();
|
|
41724
41978
|
init_control();
|
|
41725
41979
|
init_window();
|
|
@@ -42121,10 +42375,7 @@ var init_resolve = __esm(() => {
|
|
|
42121
42375
|
|
|
42122
42376
|
// ../../core/chat/messages/serversync.js
|
|
42123
42377
|
function mergeMemberStates(previous, current) {
|
|
42124
|
-
return
|
|
42125
|
-
...previous instanceof Map ? previous : [],
|
|
42126
|
-
...current instanceof Map ? current : []
|
|
42127
|
-
]);
|
|
42378
|
+
return mergeChatReceiptCheckpointsByEpoch(previous, current);
|
|
42128
42379
|
}
|
|
42129
42380
|
function isEpochBatchCutover(latestServerPage, msgBatch) {
|
|
42130
42381
|
return !!(latestServerPage?.epochId && msgBatch?.epochId && latestServerPage.epochId !== msgBatch.epochId);
|
|
@@ -42348,6 +42599,7 @@ var init_serversync = __esm(() => {
|
|
|
42348
42599
|
init_messagekeys();
|
|
42349
42600
|
init_messages();
|
|
42350
42601
|
init_state();
|
|
42602
|
+
init_receipt();
|
|
42351
42603
|
init_resolve();
|
|
42352
42604
|
init_history();
|
|
42353
42605
|
init_window();
|
|
@@ -42776,6 +43028,7 @@ function conversationStateSignature(state) {
|
|
|
42776
43028
|
cacheMessageListSignature(state.latestMessages),
|
|
42777
43029
|
cacheKeySignature(state.latestServerPage?.expiredKeys),
|
|
42778
43030
|
cacheKeySignature(state.latestServerPage?.deletedKeys),
|
|
43031
|
+
cachedReceiptCheckpointSignature(state.latestServerPage?.memberStatesByEpoch),
|
|
42779
43032
|
reactionControlCacheSignature(state)
|
|
42780
43033
|
].join(`
|
|
42781
43034
|
`);
|
|
@@ -45775,7 +46028,7 @@ function createMessageRouteSession({
|
|
|
45775
46028
|
return;
|
|
45776
46029
|
const localTarget = getLatestLocalReadTarget(viewedMessages, current.selfChatPublicKey);
|
|
45777
46030
|
const localTargetKey = messageKey2(localTarget);
|
|
45778
|
-
const target = getLatestReadStateTarget(viewedMessages);
|
|
46031
|
+
const target = getLatestReadStateTarget(current.rawMessages?.length ? current.rawMessages : viewedMessages);
|
|
45779
46032
|
const targetKey = messageKey2(target);
|
|
45780
46033
|
const localChanged = !!localTargetKey && localTargetKey !== refs.lastSelfReadTarget.current;
|
|
45781
46034
|
const stateChanged = !!targetKey && targetKey !== refs.lastSharedReadTarget.current;
|
|
@@ -47377,6 +47630,31 @@ function createRuntimeChatActions({
|
|
|
47377
47630
|
const route = enteredChats.get(id);
|
|
47378
47631
|
return { exited: route?.leave() === true, chatId: id || null };
|
|
47379
47632
|
}
|
|
47633
|
+
async function enterLive(chatId) {
|
|
47634
|
+
const { runtime, chat } = await chatForId(chatId);
|
|
47635
|
+
const entered2 = await runtime.chat.getSnapshot().enterChatActivity(chat.id);
|
|
47636
|
+
return { entered: entered2 === true, chatId: chat.id };
|
|
47637
|
+
}
|
|
47638
|
+
async function leaveLive(chatId) {
|
|
47639
|
+
const id = cleanText(chatId).toLowerCase();
|
|
47640
|
+
if (!/^[0-9a-f]{64}$/u.test(id))
|
|
47641
|
+
throw new Error("chat id required");
|
|
47642
|
+
const runtime = await getRuntime();
|
|
47643
|
+
await runtime.chat.getSnapshot().leaveChatActivity(id);
|
|
47644
|
+
return { left: true, chatId: id };
|
|
47645
|
+
}
|
|
47646
|
+
async function markTyping(chatId, typing = true, compositionId = "") {
|
|
47647
|
+
const { runtime, chat } = await chatForId(chatId);
|
|
47648
|
+
const nextTyping = typing === true;
|
|
47649
|
+
const nextCompositionId = cleanText(compositionId).toLowerCase() || (nextTyping ? makeChatCompositionId() : "");
|
|
47650
|
+
const marked = runtime.chat.getSnapshot().markChatTyping(chat.id, nextTyping, nextCompositionId);
|
|
47651
|
+
return {
|
|
47652
|
+
marked: marked === true,
|
|
47653
|
+
typing: nextTyping,
|
|
47654
|
+
compositionId: nextCompositionId,
|
|
47655
|
+
chatId: chat.id
|
|
47656
|
+
};
|
|
47657
|
+
}
|
|
47380
47658
|
async function readById(chatId, options = {}) {
|
|
47381
47659
|
const window2 = await messageWindowById(chatId, options);
|
|
47382
47660
|
try {
|
|
@@ -48027,6 +48305,9 @@ function createRuntimeChatActions({
|
|
|
48027
48305
|
readById,
|
|
48028
48306
|
enterById,
|
|
48029
48307
|
exitById,
|
|
48308
|
+
enterLive,
|
|
48309
|
+
leaveLive,
|
|
48310
|
+
markTyping,
|
|
48030
48311
|
sendToChat,
|
|
48031
48312
|
sendAttachmentToChat,
|
|
48032
48313
|
readAttachmentInChat,
|
|
@@ -48446,6 +48727,7 @@ var DEFAULT_TIMEOUT_MS4 = 15000, HISTORY_LOAD_ATTEMPTS = 4, messageWindowSequenc
|
|
|
48446
48727
|
var init_runtime_actions = __esm(() => {
|
|
48447
48728
|
init_messages();
|
|
48448
48729
|
init_ids();
|
|
48730
|
+
init_live();
|
|
48449
48731
|
init_state();
|
|
48450
48732
|
init_actions2();
|
|
48451
48733
|
init_fees();
|
|
@@ -48463,7 +48745,7 @@ var package_default;
|
|
|
48463
48745
|
var init_package2 = __esm(() => {
|
|
48464
48746
|
package_default = {
|
|
48465
48747
|
name: "veyl",
|
|
48466
|
-
version: "0.
|
|
48748
|
+
version: "0.61.0",
|
|
48467
48749
|
private: true,
|
|
48468
48750
|
license: "UNLICENSED",
|
|
48469
48751
|
workspaces: {
|
|
@@ -48797,6 +49079,9 @@ class AccountRuntime {
|
|
|
48797
49079
|
readById: (chatId, payload) => this.runtimeChat.readById(chatId, payload),
|
|
48798
49080
|
enterById: (chatId, payload) => this.runtimeChat.enterById(chatId, payload),
|
|
48799
49081
|
exitById: (chatId) => this.runtimeChat.exitById(chatId),
|
|
49082
|
+
enterLive: (chatId) => this.runtimeChat.enterLive(chatId),
|
|
49083
|
+
leaveLive: (chatId) => this.runtimeChat.leaveLive(chatId),
|
|
49084
|
+
markTyping: (chatId, typing, compositionId) => this.runtimeChat.markTyping(chatId, typing, compositionId),
|
|
48800
49085
|
sendTo: (chatId, message, payload) => this.runtimeChat.sendToChat(chatId, message, payload),
|
|
48801
49086
|
replyIn: (chatId, messageId, message, payload) => this.runtimeChat.replyInChat(chatId, messageId, message, payload),
|
|
48802
49087
|
reactIn: (chatId, messageId, emoji, payload) => this.runtimeChat.reactInChat(chatId, messageId, emoji, payload),
|