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