@glyphteck/veyl 0.70.0 → 0.71.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 +616 -181
- package/dist/accountprofiles.js +1 -0
- package/dist/cli.js +681 -265
- package/dist/index.js +681 -265
- package/package.json +1 -1
package/dist/account.js
CHANGED
|
@@ -1127,6 +1127,16 @@ function numberToBytesBE(n, len) {
|
|
|
1127
1127
|
function numberToBytesLE(n, len) {
|
|
1128
1128
|
return numberToBytesBE(n, len).reverse();
|
|
1129
1129
|
}
|
|
1130
|
+
function equalBytes(a, b) {
|
|
1131
|
+
a = abytes2(a);
|
|
1132
|
+
b = abytes2(b);
|
|
1133
|
+
if (a.length !== b.length)
|
|
1134
|
+
return false;
|
|
1135
|
+
let diff = 0;
|
|
1136
|
+
for (let i = 0;i < a.length; i++)
|
|
1137
|
+
diff |= a[i] ^ b[i];
|
|
1138
|
+
return diff === 0;
|
|
1139
|
+
}
|
|
1130
1140
|
function copyBytes(bytes) {
|
|
1131
1141
|
return Uint8Array.from(abytes2(bytes));
|
|
1132
1142
|
}
|
|
@@ -4813,7 +4823,7 @@ function checkOpts2(defaults, opts) {
|
|
|
4813
4823
|
const merged = Object.assign(defaults, opts);
|
|
4814
4824
|
return merged;
|
|
4815
4825
|
}
|
|
4816
|
-
function
|
|
4826
|
+
function equalBytes2(a, b) {
|
|
4817
4827
|
a = abytes3(a);
|
|
4818
4828
|
b = abytes3(b);
|
|
4819
4829
|
if (a.length !== b.length)
|
|
@@ -5251,7 +5261,7 @@ var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength
|
|
|
5251
5261
|
const passedTag = ciphertext.subarray(-tagLength);
|
|
5252
5262
|
const tag = _computeTag(authKey, tagMask, data);
|
|
5253
5263
|
toClean.push(tag);
|
|
5254
|
-
if (!
|
|
5264
|
+
if (!equalBytes2(tag, passedTag)) {
|
|
5255
5265
|
clean2(...toClean);
|
|
5256
5266
|
throw new Error("aes-gcm: invalid tag");
|
|
5257
5267
|
}
|
|
@@ -5704,6 +5714,7 @@ var CHAT_TTL_CLIENT_DELETE_GRACE_MS = MINUTE_MS;
|
|
|
5704
5714
|
var CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500;
|
|
5705
5715
|
var CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250;
|
|
5706
5716
|
var CHAT_LIST_PAGE_SIZE = 20;
|
|
5717
|
+
var CHAT_ADMISSION_CACHE_PEERS = 1024;
|
|
5707
5718
|
var CHAT_LIST_LIVE_COUNT = CHAT_LIST_PAGE_SIZE;
|
|
5708
5719
|
var CHAT_INBOX_PING_PAGE_SIZE = 25;
|
|
5709
5720
|
var CHAT_INBOX_DISCOVERY_PARALLEL_CHATS = CHAT_INBOX_PING_PAGE_SIZE;
|
|
@@ -5877,9 +5888,12 @@ function normalizeChatNotificationMode(value, manifest) {
|
|
|
5877
5888
|
const mode = cleanText(value);
|
|
5878
5889
|
if (!NOTIFICATION_MODES.has(mode))
|
|
5879
5890
|
throw new Error("chat notification mode required");
|
|
5880
|
-
if (manifest?.lineage
|
|
5891
|
+
if (manifest?.lineage === "self")
|
|
5881
5892
|
return CHAT_NOTIFICATION_MODES.ALL;
|
|
5882
|
-
if (
|
|
5893
|
+
if (manifest?.lineage === "direct" && mode === CHAT_NOTIFICATION_MODES.MENTIONS) {
|
|
5894
|
+
throw new Error("mentions notifications require a group chat");
|
|
5895
|
+
}
|
|
5896
|
+
if (mode === CHAT_NOTIFICATION_MODES.ALL && manifest?.lineage === "group" && manifest.members?.length > CHAT_ORDINARY_DELIVERY_MAX_MEMBERS) {
|
|
5883
5897
|
return CHAT_NOTIFICATION_MODES.MENTIONS;
|
|
5884
5898
|
}
|
|
5885
5899
|
return mode;
|
|
@@ -6940,7 +6954,7 @@ var _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
|
6940
6954
|
const data = ciphertext.subarray(0, -tagLength);
|
|
6941
6955
|
const passedTag = ciphertext.subarray(-tagLength);
|
|
6942
6956
|
const tag = computeTag2(xorStream, key, nonce, data, AAD);
|
|
6943
|
-
if (!
|
|
6957
|
+
if (!equalBytes2(passedTag, tag)) {
|
|
6944
6958
|
clean2(tag);
|
|
6945
6959
|
throw new Error("invalid tag");
|
|
6946
6960
|
}
|
|
@@ -8262,35 +8276,68 @@ function chatAdmissionCapabilityCommitment(value) {
|
|
|
8262
8276
|
cleanBytes(capability);
|
|
8263
8277
|
}
|
|
8264
8278
|
}
|
|
8265
|
-
function
|
|
8266
|
-
return deriveDirectAdmissionCapability(chatPrivateKey, chatPK,
|
|
8267
|
-
}
|
|
8268
|
-
function hasDirectAdmissionGrant(chatPrivateKey, chatPK, profile) {
|
|
8269
|
-
const policy = normalizeChatAdmission(profile?.chatAdmission);
|
|
8270
|
-
if (!policy.directGrants.length)
|
|
8271
|
-
return false;
|
|
8272
|
-
const capability = directAdmissionCapabilityForPeer(chatPrivateKey, chatPK, profile);
|
|
8273
|
-
try {
|
|
8274
|
-
return policy.directGrants.includes(chatAdmissionCapabilityCommitment(capability));
|
|
8275
|
-
} finally {
|
|
8276
|
-
cleanBytes(capability);
|
|
8277
|
-
}
|
|
8279
|
+
function deriveDirectAdmissionCommitment(chatPrivateKey, chatPK, peerChatPK, recipientChatPK) {
|
|
8280
|
+
return chatAdmissionCapabilityCommitment(deriveDirectAdmissionCapability(chatPrivateKey, chatPK, peerChatPK, recipientChatPK));
|
|
8278
8281
|
}
|
|
8279
|
-
function
|
|
8280
|
-
|
|
8282
|
+
function directAdmissionDecision(chatPK, profile, ownAdmission, commitmentFor) {
|
|
8283
|
+
const ownPolicy = normalizeChatAdmission(ownAdmission);
|
|
8284
|
+
if (ownAdmission != null && ownPolicy.direct === CHAT_DIRECT_ADMISSION_MODES.CLOSED && !ownPolicy.directGrants.includes(commitmentFor(profile?.chatPK, chatPK)))
|
|
8281
8285
|
return CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8282
|
-
|
|
8286
|
+
const policy = normalizeChatAdmission(profile?.chatAdmission);
|
|
8287
|
+
if (policy.directGrants.length && policy.directGrants.includes(commitmentFor(profile?.chatPK, profile?.chatPK))) {
|
|
8283
8288
|
return CHAT_ADMISSION_DECISIONS.ACCEPT;
|
|
8284
8289
|
}
|
|
8285
|
-
const mode =
|
|
8290
|
+
const mode = policy.direct;
|
|
8286
8291
|
if (mode === CHAT_DIRECT_ADMISSION_MODES.OPEN)
|
|
8287
8292
|
return CHAT_ADMISSION_DECISIONS.ACCEPT;
|
|
8288
8293
|
if (mode === CHAT_DIRECT_ADMISSION_MODES.REQUESTS)
|
|
8289
8294
|
return CHAT_ADMISSION_DECISIONS.REQUEST;
|
|
8290
8295
|
return CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8291
8296
|
}
|
|
8292
|
-
function
|
|
8293
|
-
return
|
|
8297
|
+
function directAdmissionForPeer(chatPrivateKey, chatPK, profile, ownAdmission = null) {
|
|
8298
|
+
return directAdmissionDecision(chatPK, profile, ownAdmission, (peerChatPK, recipientChatPK) => deriveDirectAdmissionCommitment(chatPrivateKey, chatPK, peerChatPK, recipientChatPK));
|
|
8299
|
+
}
|
|
8300
|
+
function createChatAdmission({ getIdentity, deriveCommitment = deriveDirectAdmissionCommitment }) {
|
|
8301
|
+
let chatPK = null;
|
|
8302
|
+
let privateKey = null;
|
|
8303
|
+
let privateKeyBytes = null;
|
|
8304
|
+
const peers = new Map;
|
|
8305
|
+
const reset = () => {
|
|
8306
|
+
peers.clear();
|
|
8307
|
+
cleanBytes(privateKeyBytes);
|
|
8308
|
+
chatPK = null;
|
|
8309
|
+
privateKey = null;
|
|
8310
|
+
privateKeyBytes = null;
|
|
8311
|
+
};
|
|
8312
|
+
const directAdmissionForPeer2 = (profile) => {
|
|
8313
|
+
const identity = getIdentity();
|
|
8314
|
+
if (chatPK !== identity.chatPK || privateKey !== identity.chatPrivateKey || privateKey instanceof Uint8Array && !equalBytes(privateKey, privateKeyBytes)) {
|
|
8315
|
+
reset();
|
|
8316
|
+
chatPK = identity.chatPK;
|
|
8317
|
+
privateKey = identity.chatPrivateKey;
|
|
8318
|
+
privateKeyBytes = privateKey instanceof Uint8Array ? new Uint8Array(privateKey) : null;
|
|
8319
|
+
}
|
|
8320
|
+
return directAdmissionDecision(chatPK, profile, identity.chatAdmission, (peerChatPK, recipientChatPK) => {
|
|
8321
|
+
let commitments = peers.get(peerChatPK);
|
|
8322
|
+
if (!commitments?.has(recipientChatPK)) {
|
|
8323
|
+
const commitment = deriveCommitment(privateKey, chatPK, peerChatPK, recipientChatPK);
|
|
8324
|
+
if (!commitments) {
|
|
8325
|
+
commitments = new Map;
|
|
8326
|
+
if (peers.size >= CHAT_ADMISSION_CACHE_PEERS)
|
|
8327
|
+
peers.delete(peers.keys().next().value);
|
|
8328
|
+
}
|
|
8329
|
+
commitments.set(recipientChatPK, commitment);
|
|
8330
|
+
}
|
|
8331
|
+
peers.delete(peerChatPK);
|
|
8332
|
+
peers.set(peerChatPK, commitments);
|
|
8333
|
+
return commitments.get(recipientChatPK);
|
|
8334
|
+
});
|
|
8335
|
+
};
|
|
8336
|
+
return {
|
|
8337
|
+
directAdmissionForPeer: directAdmissionForPeer2,
|
|
8338
|
+
canStartDirectChat: (profile) => directAdmissionForPeer2(profile) !== CHAT_ADMISSION_DECISIONS.REJECT,
|
|
8339
|
+
reset
|
|
8340
|
+
};
|
|
8294
8341
|
}
|
|
8295
8342
|
function canInviteToGroup(profile, ownAdmission = null) {
|
|
8296
8343
|
return (ownAdmission == null || normalizeChatAdmission(ownAdmission).groups === CHAT_GROUP_ADMISSION_MODES.OPEN) && normalizeChatAdmission(profile?.chatAdmission).groups === CHAT_GROUP_ADMISSION_MODES.OPEN;
|
|
@@ -10394,6 +10441,26 @@ function transitionCore(certificate) {
|
|
|
10394
10441
|
return normalizeTransitionCore(certificate);
|
|
10395
10442
|
}
|
|
10396
10443
|
|
|
10444
|
+
// ../../core/chat/capabilities.js
|
|
10445
|
+
function memberCount(value) {
|
|
10446
|
+
const count = Number(value);
|
|
10447
|
+
if (!Number.isSafeInteger(count) || count < 1 || count > CHAT_MAX_MEMBERS) {
|
|
10448
|
+
throw new Error("invalid chat member count");
|
|
10449
|
+
}
|
|
10450
|
+
return count;
|
|
10451
|
+
}
|
|
10452
|
+
function chatCapabilities(value) {
|
|
10453
|
+
const members = memberCount(value);
|
|
10454
|
+
return Object.freeze({
|
|
10455
|
+
members,
|
|
10456
|
+
sharedReceipts: members <= CHAT_RECEIPT_MAX_MEMBERS,
|
|
10457
|
+
ordinaryDelivery: members <= CHAT_ORDINARY_DELIVERY_MAX_MEMBERS
|
|
10458
|
+
});
|
|
10459
|
+
}
|
|
10460
|
+
function chatEpochCapabilities(epochState) {
|
|
10461
|
+
return chatCapabilities(epochState?.manifest?.members?.length);
|
|
10462
|
+
}
|
|
10463
|
+
|
|
10397
10464
|
// ../../core/chat/ttl.js
|
|
10398
10465
|
"use client";
|
|
10399
10466
|
var CHAT_RETENTION_SEEN = "seen";
|
|
@@ -10406,6 +10473,10 @@ var CHAT_RETENTION_LABELS = Object.freeze({
|
|
|
10406
10473
|
[CHAT_RETENTION_SEEN]: "delete after seen",
|
|
10407
10474
|
[CHAT_RETENTION_24H]: "keep for 24h"
|
|
10408
10475
|
});
|
|
10476
|
+
var CHAT_RETENTION_DESCRIPTIONS = Object.freeze({
|
|
10477
|
+
[CHAT_RETENTION_SEEN]: "messages now delete after being seen",
|
|
10478
|
+
[CHAT_RETENTION_24H]: "messages now delete 24h after being seen"
|
|
10479
|
+
});
|
|
10409
10480
|
var MESSAGE_STORAGE_TTL_MS = CHAT_UNSAVED_TTL_MS;
|
|
10410
10481
|
var AFTER_SEEN_MS = CHAT_AFTER_SEEN_MS;
|
|
10411
10482
|
function hasChatRetention(value) {
|
|
@@ -11068,6 +11139,8 @@ async function prepareOwnChatMutation(identity, entryId, current, update) {
|
|
|
11068
11139
|
open: (record) => openOwnChatMutationEntry(identity, entryId, record),
|
|
11069
11140
|
prepare: async (record) => {
|
|
11070
11141
|
if (!record) {
|
|
11142
|
+
if (current != null)
|
|
11143
|
+
throw makeChatUnavailableError();
|
|
11071
11144
|
return prepareMutation(identity, entryId, null, update);
|
|
11072
11145
|
}
|
|
11073
11146
|
if (!record?.body || !Number.isSafeInteger(record.revision) || record.revision < 1) {
|
|
@@ -11827,26 +11900,6 @@ async function openChatMemberState(epoch, record) {
|
|
|
11827
11900
|
return evidence.state;
|
|
11828
11901
|
}
|
|
11829
11902
|
|
|
11830
|
-
// ../../core/chat/capabilities.js
|
|
11831
|
-
function memberCount(value) {
|
|
11832
|
-
const count = Number(value);
|
|
11833
|
-
if (!Number.isSafeInteger(count) || count < 1 || count > CHAT_MAX_MEMBERS) {
|
|
11834
|
-
throw new Error("invalid chat member count");
|
|
11835
|
-
}
|
|
11836
|
-
return count;
|
|
11837
|
-
}
|
|
11838
|
-
function chatCapabilities(value) {
|
|
11839
|
-
const members = memberCount(value);
|
|
11840
|
-
return Object.freeze({
|
|
11841
|
-
members,
|
|
11842
|
-
sharedReceipts: members <= CHAT_RECEIPT_MAX_MEMBERS,
|
|
11843
|
-
ordinaryDelivery: members <= CHAT_ORDINARY_DELIVERY_MAX_MEMBERS
|
|
11844
|
-
});
|
|
11845
|
-
}
|
|
11846
|
-
function chatEpochCapabilities(epochState) {
|
|
11847
|
-
return chatCapabilities(epochState?.manifest?.members?.length);
|
|
11848
|
-
}
|
|
11849
|
-
|
|
11850
11903
|
// ../../core/chat/memberstate.js
|
|
11851
11904
|
"use client";
|
|
11852
11905
|
function openStateEpoch(epochState, actor = null) {
|
|
@@ -12987,7 +13040,8 @@ function getMsgReactions(msg) {
|
|
|
12987
13040
|
continue;
|
|
12988
13041
|
}
|
|
12989
13042
|
seen.add(user);
|
|
12990
|
-
|
|
13043
|
+
const ts = timestampMs(item.ts, null);
|
|
13044
|
+
reactions.push({ emoji, user, ...ts == null ? {} : { ts } });
|
|
12991
13045
|
if (reactions.length >= MAX_REACTIONS) {
|
|
12992
13046
|
break;
|
|
12993
13047
|
}
|
|
@@ -13864,7 +13918,7 @@ function senderIdentity(senderChatPK, senderPrivateKey, options) {
|
|
|
13864
13918
|
notificationPrivateKey: options.notificationPrivateKey
|
|
13865
13919
|
};
|
|
13866
13920
|
}
|
|
13867
|
-
function ownerPreview(senderChatPK, message, messageId, head, tsMs, ttlMs) {
|
|
13921
|
+
function ownerPreview(epoch, senderChatPK, message, messageId, head, tsMs, ttlMs) {
|
|
13868
13922
|
return {
|
|
13869
13923
|
...message || {},
|
|
13870
13924
|
s: senderChatPK,
|
|
@@ -13873,6 +13927,8 @@ function ownerPreview(senderChatPK, message, messageId, head, tsMs, ttlMs) {
|
|
|
13873
13927
|
id: messageId,
|
|
13874
13928
|
ts: tsMs,
|
|
13875
13929
|
ttl: Number.isFinite(ttlMs) ? ttlMs : null,
|
|
13930
|
+
epochId: epoch.epochId,
|
|
13931
|
+
epochMemberChatPKs: epoch.manifest.members.map((member) => member.chatPK),
|
|
13876
13932
|
pending: false,
|
|
13877
13933
|
failed: false
|
|
13878
13934
|
};
|
|
@@ -14089,7 +14145,7 @@ async function prepareMsgRecord(identity, epochState, message, options = {}) {
|
|
|
14089
14145
|
msgId: messageId,
|
|
14090
14146
|
cid: head.cid,
|
|
14091
14147
|
record: { lane: epoch.messageLane, head, body, ttlMs },
|
|
14092
|
-
message: ownerPreview(identity.chatPK, messagePayload, messageId, head, tsMs, ttlMs),
|
|
14148
|
+
message: ownerPreview(epoch, identity.chatPK, messagePayload, messageId, head, tsMs, ttlMs),
|
|
14093
14149
|
mentionRecipientChatPKs: mentionTargets.recipientChatPKs,
|
|
14094
14150
|
tsMs
|
|
14095
14151
|
};
|
|
@@ -18368,12 +18424,6 @@ function dropCachedChats(cache, chatIds, currentPayload = null) {
|
|
|
18368
18424
|
...ids.map((chatId) => dropCachedMessageState(cache, chatId))
|
|
18369
18425
|
]));
|
|
18370
18426
|
}
|
|
18371
|
-
function dropCachedChat(cache, chatId) {
|
|
18372
|
-
if (!chatId) {
|
|
18373
|
-
return;
|
|
18374
|
-
}
|
|
18375
|
-
dropCachedChats(cache, [chatId]);
|
|
18376
|
-
}
|
|
18377
18427
|
// ../../core/cache/localdata/balance.js
|
|
18378
18428
|
"use client";
|
|
18379
18429
|
function normalizedBalance(value) {
|
|
@@ -22547,6 +22597,17 @@ function createRouteMemory(limit = CHAT_MESSAGE_VIEW_CACHE_SIZE) {
|
|
|
22547
22597
|
}
|
|
22548
22598
|
return {
|
|
22549
22599
|
clear,
|
|
22600
|
+
dropChat(chatId) {
|
|
22601
|
+
const id = String(chatId || "").trim();
|
|
22602
|
+
if (!id)
|
|
22603
|
+
return;
|
|
22604
|
+
const keys = chatRouteMemoryKeys(id, [...states.keys(), ...retained.keys()]);
|
|
22605
|
+
for (const key of keys) {
|
|
22606
|
+
states.delete(key);
|
|
22607
|
+
retained.delete(key);
|
|
22608
|
+
}
|
|
22609
|
+
activeChats.delete(id);
|
|
22610
|
+
},
|
|
22550
22611
|
resetAccount(nextAccountKey) {
|
|
22551
22612
|
const next = nextAccountKey || "";
|
|
22552
22613
|
if (accountKey === next) {
|
|
@@ -24099,6 +24160,9 @@ async function verifyOpenedPingEpoch(context) {
|
|
|
24099
24160
|
const decision = await decideIncomingChat(context, epochState);
|
|
24100
24161
|
if (decision === CHAT_ADMISSION_DECISIONS.REJECT)
|
|
24101
24162
|
return "rejected";
|
|
24163
|
+
const unavailable = await checkOpenedWelcomeAvailability(context);
|
|
24164
|
+
if (unavailable)
|
|
24165
|
+
return unavailable;
|
|
24102
24166
|
if (decision === CHAT_ADMISSION_DECISIONS.REQUEST)
|
|
24103
24167
|
return "request";
|
|
24104
24168
|
} else if (context.entry && (epochState.manifest.lineage === "direct" || epochState.manifest.lineage === "group")) {
|
|
@@ -24108,6 +24172,27 @@ async function verifyOpenedPingEpoch(context) {
|
|
|
24108
24172
|
}
|
|
24109
24173
|
return null;
|
|
24110
24174
|
}
|
|
24175
|
+
async function checkOpenedWelcomeAvailability(context) {
|
|
24176
|
+
const { cloud, identity, options, uid, epochState } = context;
|
|
24177
|
+
assertInboxLease(options);
|
|
24178
|
+
try {
|
|
24179
|
+
await cloud.chat.state.transition(epochState.manifest.chatId, epochState.manifest.epochId, epochState.stateCapability);
|
|
24180
|
+
assertInboxLease(options);
|
|
24181
|
+
return null;
|
|
24182
|
+
} catch (error) {
|
|
24183
|
+
const code = cleanText(error?.code).toLowerCase();
|
|
24184
|
+
if (code !== "permission-denied" && !code.endsWith("/permission-denied"))
|
|
24185
|
+
throw error;
|
|
24186
|
+
try {
|
|
24187
|
+
await retireConfirmedDeletedChat(cloud, uid, identity, context.committedEntry || context.entry || makeOwnChatEntry(epochState), options);
|
|
24188
|
+
return "deleted";
|
|
24189
|
+
} catch (confirmationError) {
|
|
24190
|
+
if (isRetryableInboxError(confirmationError))
|
|
24191
|
+
throw confirmationError;
|
|
24192
|
+
throw error;
|
|
24193
|
+
}
|
|
24194
|
+
}
|
|
24195
|
+
}
|
|
24111
24196
|
function startOpenedWelcomeHydration(context) {
|
|
24112
24197
|
const membershipWelcome = (!context.entry || context.restoringMembership) && context.payload.kind === "welcome";
|
|
24113
24198
|
if (!membershipWelcome)
|
|
@@ -24344,17 +24429,30 @@ async function settleOpenedPing(prepared, onAuthoritative) {
|
|
|
24344
24429
|
const createsMembership = !context.entry && context.payload.kind === "welcome";
|
|
24345
24430
|
const ownerPersistence = createsMembership ? captureAsync(persistOpenedPingOwner(context)) : null;
|
|
24346
24431
|
const messageStartedAt = Date.now();
|
|
24347
|
-
const
|
|
24432
|
+
const messageOutcome = await captureAsync(applyOpenedPingMessage(context));
|
|
24348
24433
|
markDiag(context.options.diag, "chat.inbox.settle.message.done", {
|
|
24349
24434
|
elapsedMs: Date.now() - messageStartedAt
|
|
24350
24435
|
});
|
|
24351
|
-
if (result)
|
|
24352
|
-
return result;
|
|
24353
|
-
prepareOpenedPingActivity(context);
|
|
24354
24436
|
if (ownerPersistence)
|
|
24355
24437
|
await unwrapAsync(ownerPersistence);
|
|
24356
|
-
else
|
|
24357
|
-
|
|
24438
|
+
else if (messageOutcome.error)
|
|
24439
|
+
throw messageOutcome.error;
|
|
24440
|
+
if (messageOutcome.value)
|
|
24441
|
+
return messageOutcome.value;
|
|
24442
|
+
if (!messageOutcome.error) {
|
|
24443
|
+
prepareOpenedPingActivity(context);
|
|
24444
|
+
if (!ownerPersistence)
|
|
24445
|
+
await persistOpenedPingOwner(context);
|
|
24446
|
+
}
|
|
24447
|
+
if (context.membershipWelcome) {
|
|
24448
|
+
const unavailable = await checkOpenedWelcomeAvailability(context);
|
|
24449
|
+
if (unavailable) {
|
|
24450
|
+
await cleanupOpenedPing(context);
|
|
24451
|
+
return unavailable;
|
|
24452
|
+
}
|
|
24453
|
+
}
|
|
24454
|
+
if (messageOutcome.error)
|
|
24455
|
+
throw messageOutcome.error;
|
|
24358
24456
|
publishOpenedPing(context);
|
|
24359
24457
|
onAuthoritative?.();
|
|
24360
24458
|
const phaseStartedAt = Date.now();
|
|
@@ -24481,6 +24579,7 @@ async function preparePreparedPing(cloud, uid, identity, prepared, options) {
|
|
|
24481
24579
|
ping,
|
|
24482
24580
|
uid
|
|
24483
24581
|
});
|
|
24582
|
+
assertInboxLease(options);
|
|
24484
24583
|
return { opened, releaseMutation };
|
|
24485
24584
|
} catch (error) {
|
|
24486
24585
|
releaseMutation();
|
|
@@ -24935,10 +25034,11 @@ async function prepareInboxWork(context, prepared, processingOptions = {}) {
|
|
|
24935
25034
|
const { blockedUids, cloud, identity, options, state, uid } = context;
|
|
24936
25035
|
const { document, opened } = prepared;
|
|
24937
25036
|
let announcement = null;
|
|
25037
|
+
let ping = null;
|
|
24938
25038
|
try {
|
|
24939
25039
|
const authenticated = await authenticateInboxPing(cloud, identity, document, opened, state.currentChats, context.profileCache);
|
|
24940
25040
|
assertInboxLease(options);
|
|
24941
|
-
|
|
25041
|
+
ping = await preparePreparedPing(cloud, uid, identity, authenticated, {
|
|
24942
25042
|
...options,
|
|
24943
25043
|
blockedUids,
|
|
24944
25044
|
currentChats: state.currentChats,
|
|
@@ -24955,9 +25055,11 @@ async function prepareInboxWork(context, prepared, processingOptions = {}) {
|
|
|
24955
25055
|
if (ping.opened?.result === "request") {
|
|
24956
25056
|
await hydrateOpenedChatRequest(ping.opened.context);
|
|
24957
25057
|
}
|
|
25058
|
+
assertInboxLease(options);
|
|
24958
25059
|
announcement = announcePreparedWelcome(context, authenticated, ping, document, prepared.startedAt);
|
|
24959
25060
|
return { announcement, document, ping, prepared, processingOptions, status: null };
|
|
24960
25061
|
} catch (error) {
|
|
25062
|
+
ping?.releaseMutation();
|
|
24961
25063
|
state.revokeAnnouncement(announcement);
|
|
24962
25064
|
if (processingOptions.deferUnknownWelcome === true && error?.message === "chat mls welcome required") {
|
|
24963
25065
|
markDiag(options.diag, "chat.inbox.document.deferred", { reason: "welcome_required" });
|
|
@@ -25085,6 +25187,8 @@ function createInboxSettlementController(options = {}) {
|
|
|
25085
25187
|
for (const job of lane.jobs) {
|
|
25086
25188
|
if (job.settled)
|
|
25087
25189
|
continue;
|
|
25190
|
+
if (job.state === "ready")
|
|
25191
|
+
job.discard?.(job.prepared);
|
|
25088
25192
|
job.settled = true;
|
|
25089
25193
|
job.state = "settled";
|
|
25090
25194
|
job.deferred.reject(error);
|
|
@@ -25129,9 +25233,12 @@ function createInboxSettlementController(options = {}) {
|
|
|
25129
25233
|
}
|
|
25130
25234
|
job.state = "preparing";
|
|
25131
25235
|
Promise.resolve().then(job.prepare).then((prepared) => {
|
|
25132
|
-
if (job.settled)
|
|
25236
|
+
if (job.settled) {
|
|
25237
|
+
job.discard?.(prepared);
|
|
25133
25238
|
return;
|
|
25239
|
+
}
|
|
25134
25240
|
if (!hasLease()) {
|
|
25241
|
+
job.discard?.(prepared);
|
|
25135
25242
|
finishJob(job, inboxLeaseError());
|
|
25136
25243
|
return;
|
|
25137
25244
|
}
|
|
@@ -25152,6 +25259,7 @@ function createInboxSettlementController(options = {}) {
|
|
|
25152
25259
|
if (!job || job.settled)
|
|
25153
25260
|
continue;
|
|
25154
25261
|
if (!hasLease()) {
|
|
25262
|
+
job.discard?.(job.prepared);
|
|
25155
25263
|
finishJob(job, inboxLeaseError());
|
|
25156
25264
|
continue;
|
|
25157
25265
|
}
|
|
@@ -25187,6 +25295,7 @@ function createInboxSettlementController(options = {}) {
|
|
|
25187
25295
|
const job = {
|
|
25188
25296
|
chatId,
|
|
25189
25297
|
deferred: inboxDeferred(),
|
|
25298
|
+
discard: task.discard,
|
|
25190
25299
|
lane,
|
|
25191
25300
|
prepare: task.prepare,
|
|
25192
25301
|
prepared: null,
|
|
@@ -25244,6 +25353,10 @@ async function processInboxDocuments(context, documents, processingOptions = {})
|
|
|
25244
25353
|
const deferred = [];
|
|
25245
25354
|
await Promise.all(groups.map((group) => context.settlementController.schedule(cleanText(group[0]?.opened?.payload?.chatId), {
|
|
25246
25355
|
prepare: () => context.discoveryPool.run(() => prepareInboxWork(context, group[0], processingOptions)),
|
|
25356
|
+
discard: (work) => {
|
|
25357
|
+
work?.ping?.releaseMutation();
|
|
25358
|
+
context.state.revokeAnnouncement(work?.announcement);
|
|
25359
|
+
},
|
|
25247
25360
|
settle: async (first, markAuthoritative) => {
|
|
25248
25361
|
if (await settleInboxWork(context, first, markAuthoritative) === "deferred")
|
|
25249
25362
|
deferred.push(first.document);
|
|
@@ -25531,6 +25644,7 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
25531
25644
|
nextAfterChat: snapshot.nextAfterChat,
|
|
25532
25645
|
hasMore: snapshot.hasMore,
|
|
25533
25646
|
ownerListToken: snapshot.ownerListToken,
|
|
25647
|
+
sourceRevision: snapshot.sourceRevision,
|
|
25534
25648
|
source: "server"
|
|
25535
25649
|
});
|
|
25536
25650
|
deleteConfirmedUserChats(cloud, uid, page.deletedEntryIds);
|
|
@@ -25755,7 +25869,8 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
25755
25869
|
latestOwnerSnapshot = {
|
|
25756
25870
|
docs: entries,
|
|
25757
25871
|
nextAfterChat: meta.nextAfterChat ?? null,
|
|
25758
|
-
hasMore: !!meta.hasMore
|
|
25872
|
+
hasMore: !!meta.hasMore,
|
|
25873
|
+
sourceRevision: options.getSourceRevision?.()
|
|
25759
25874
|
};
|
|
25760
25875
|
queueOwnerSnapshot();
|
|
25761
25876
|
}, onError, { limitCount: pageSize });
|
|
@@ -26246,15 +26361,15 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
26246
26361
|
const ownerFor = (entry) => composeActivity(structuralOwnerFor(entry), entry);
|
|
26247
26362
|
const cacheFor = (entry) => composeActivity(entry?.cache, entry);
|
|
26248
26363
|
const sourceCanPublish = (chat, sourceRevision) => {
|
|
26364
|
+
const boundary = authorityBoundaries.get(chat?.id);
|
|
26365
|
+
if (boundary?.terminal)
|
|
26366
|
+
return false;
|
|
26249
26367
|
if (!chat?.id || !Number.isSafeInteger(sourceRevision))
|
|
26250
26368
|
return true;
|
|
26251
26369
|
if (sourceRevision < sourceRevisionFloor)
|
|
26252
26370
|
return false;
|
|
26253
|
-
const boundary = authorityBoundaries.get(chat.id);
|
|
26254
26371
|
if (!boundary)
|
|
26255
26372
|
return true;
|
|
26256
|
-
if (boundary.terminal)
|
|
26257
|
-
return false;
|
|
26258
26373
|
return sourceRevision >= boundary.revision || compareOwners(chat, boundary.owner) > 0;
|
|
26259
26374
|
};
|
|
26260
26375
|
const sourceCanRemove = (chatId, sourceRevision) => {
|
|
@@ -26394,21 +26509,33 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
26394
26509
|
updatePeers(shown);
|
|
26395
26510
|
return shown;
|
|
26396
26511
|
};
|
|
26512
|
+
const clearRemovedOwners = (chatIds) => {
|
|
26513
|
+
if (!chatIds.length)
|
|
26514
|
+
return;
|
|
26515
|
+
const sources = getSources();
|
|
26516
|
+
for (const chatId of chatIds)
|
|
26517
|
+
sources.pendingDeleteIdsRef.current.delete(chatId);
|
|
26518
|
+
sources.onOwnerRemoved?.(chatIds);
|
|
26519
|
+
cache.removeMany(chatIds);
|
|
26520
|
+
const removedIds = new Set(chatIds);
|
|
26521
|
+
sources.setSelectedChat((current) => removedIds.has(current) ? null : current);
|
|
26522
|
+
};
|
|
26397
26523
|
const convergeCompleteOwnerSet = (incomingIds = null, sourceRevision = null) => {
|
|
26398
|
-
const
|
|
26524
|
+
const removedIds = [];
|
|
26399
26525
|
for (const [chatId, entry] of entries) {
|
|
26400
|
-
if (
|
|
26401
|
-
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
evictedCacheIds.push(chatId);
|
|
26406
|
-
entries.delete(chatId);
|
|
26407
|
-
} else if (!incomingIds || !incomingIds.has(chatId)) {
|
|
26526
|
+
if (incomingIds ? incomingIds.has(chatId) : entry.observedOwner)
|
|
26527
|
+
continue;
|
|
26528
|
+
if (!sourceCanRemove(chatId, sourceRevision))
|
|
26529
|
+
continue;
|
|
26530
|
+
if (entry.announcement && !authorityBoundaries.get(chatId)?.owner) {
|
|
26408
26531
|
entry.cache = null;
|
|
26532
|
+
continue;
|
|
26409
26533
|
}
|
|
26534
|
+
recordTerminal(chatId, ownerFor(entry) || cacheFor(entry));
|
|
26535
|
+
entries.delete(chatId);
|
|
26536
|
+
removedIds.push(chatId);
|
|
26410
26537
|
}
|
|
26411
|
-
|
|
26538
|
+
clearRemovedOwners(removedIds);
|
|
26412
26539
|
};
|
|
26413
26540
|
const observeOwnerChats = (rows, options = {}) => {
|
|
26414
26541
|
const incoming = (rows || []).filter((chat) => chat?.id && sourceCanPublish(chat, options.sourceRevision));
|
|
@@ -26484,6 +26611,8 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
26484
26611
|
const announceWelcome = (announcement) => {
|
|
26485
26612
|
if (!announcement?.chatId || !announcement?.token)
|
|
26486
26613
|
return false;
|
|
26614
|
+
if (authorityBoundaries.get(announcement.chatId)?.terminal)
|
|
26615
|
+
return false;
|
|
26487
26616
|
const sources = getSources();
|
|
26488
26617
|
if (sources.pendingDeleteIdsRef.current.has(announcement.chatId))
|
|
26489
26618
|
return false;
|
|
@@ -26520,7 +26649,6 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
26520
26649
|
return false;
|
|
26521
26650
|
if (!sourceCanRemove(chatId, options.sourceRevision))
|
|
26522
26651
|
return false;
|
|
26523
|
-
recordTerminal(chatId, cacheFor(entry));
|
|
26524
26652
|
entry.cache = null;
|
|
26525
26653
|
entry.presentation = null;
|
|
26526
26654
|
cache.remove(chatId);
|
|
@@ -26530,15 +26658,12 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
26530
26658
|
return true;
|
|
26531
26659
|
};
|
|
26532
26660
|
const removeOwner = (chatId, options = {}) => {
|
|
26533
|
-
const sources = getSources();
|
|
26534
26661
|
if (!chatId)
|
|
26535
26662
|
return false;
|
|
26536
26663
|
recordTerminal(chatId, ownerFor(entries.get(chatId)) || cacheFor(entries.get(chatId)));
|
|
26537
|
-
sources.pendingDeleteIdsRef.current.delete(chatId);
|
|
26538
26664
|
const removed = entries.delete(chatId);
|
|
26539
|
-
|
|
26665
|
+
clearRemovedOwners([chatId]);
|
|
26540
26666
|
publishOwners({ ...options, warm: false });
|
|
26541
|
-
sources.setSelectedChat((current) => current === chatId ? null : current);
|
|
26542
26667
|
return removed;
|
|
26543
26668
|
};
|
|
26544
26669
|
const removeInboxOwner = (chatId, options = {}) => {
|
|
@@ -26886,7 +27011,7 @@ function createChatListListener({
|
|
|
26886
27011
|
sync();
|
|
26887
27012
|
}, CHAT_LIST_LISTENER_RETRY_MS);
|
|
26888
27013
|
};
|
|
26889
|
-
const subscription = subscribe(sources.cloud, sources.uid, sources.chatPK, sources.chatPrivateKey, (nextChats, _nextPeers, meta = {}) => handleUpdate(nextChats, meta, listenStartedAt,
|
|
27014
|
+
const subscription = subscribe(sources.cloud, sources.uid, sources.chatPK, sources.chatPrivateKey, (nextChats, _nextPeers, meta = {}) => handleUpdate(nextChats, meta, listenStartedAt, meta.sourceRevision), (error) => {
|
|
26890
27015
|
const current = getSources();
|
|
26891
27016
|
state.setIsChatListLive(false);
|
|
26892
27017
|
markError(current.diag, "chat.provider.listen", listenStartedAt, error);
|
|
@@ -26897,6 +27022,7 @@ function createChatListListener({
|
|
|
26897
27022
|
retry(error);
|
|
26898
27023
|
}, {
|
|
26899
27024
|
limitCount: CHAT_LIST_LIVE_COUNT,
|
|
27025
|
+
getSourceRevision: index.getAuthorityRevision,
|
|
26900
27026
|
onInboxAnnouncement: index.announceWelcome,
|
|
26901
27027
|
onInboxRevoke: index.revokeAnnouncement,
|
|
26902
27028
|
onInboxCommit: index.commitOwner,
|
|
@@ -28343,6 +28469,7 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
28343
28469
|
if (!identity.chatSigningSecret)
|
|
28344
28470
|
throw new Error("unlocked chat signing identity required");
|
|
28345
28471
|
const mls = await lifecycle.mls?.open?.();
|
|
28472
|
+
lifecycle.assertCurrent?.();
|
|
28346
28473
|
if (!mls)
|
|
28347
28474
|
throw new Error("mls engine required");
|
|
28348
28475
|
const selfLeafId = lifecycle.selfLeafId || randomBytes3(32);
|
|
@@ -28363,7 +28490,7 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
28363
28490
|
if (additions.length)
|
|
28364
28491
|
mlsGroup = await mls.stageChange(mlsGroup.snapshot, { adds: additions });
|
|
28365
28492
|
const material = deriveChatMlsEpochMaterial(mlsGroup.epochKey, { chatId, epochVersion: 1 });
|
|
28366
|
-
const lineage = members.length === 1 ? CHAT_LINEAGES.SELF : members.length === 2 ? CHAT_LINEAGES.DIRECT : CHAT_LINEAGES.GROUP;
|
|
28493
|
+
const lineage = lifecycle.lineage || (members.length === 1 ? CHAT_LINEAGES.SELF : members.length === 2 ? CHAT_LINEAGES.DIRECT : CHAT_LINEAGES.GROUP);
|
|
28367
28494
|
const manifest = makeInitialEpochManifest({
|
|
28368
28495
|
chatId,
|
|
28369
28496
|
epochId: material.epochId,
|
|
@@ -28426,6 +28553,7 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
28426
28553
|
});
|
|
28427
28554
|
initial.backend.membershipOutbox = queuedWelcomes.map(({ outboxId: outboxId2, sealed }) => ({ outboxId: outboxId2, sealed }));
|
|
28428
28555
|
stage = "owner";
|
|
28556
|
+
lifecycle.assertCurrent?.();
|
|
28429
28557
|
const registration = registerInitialDelivery(cloud, identity, initialState);
|
|
28430
28558
|
const stateId = chatMlsStateId(identity.chatPrivateKey, chatId, initial.transitionCommitment);
|
|
28431
28559
|
const ownerMlsState = await sealChatMlsState(identity.chatPrivateKey, stateId, { v: 1, chatId, transitionCommitment: initial.transitionCommitment }, mlsGroup.snapshot);
|
|
@@ -28437,6 +28565,8 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
28437
28565
|
let stateError = null;
|
|
28438
28566
|
try {
|
|
28439
28567
|
stage = "state";
|
|
28568
|
+
lifecycle.assertCurrent?.();
|
|
28569
|
+
lifecycle.onCreateDispatched?.(initial.manifest.chatId);
|
|
28440
28570
|
await cloud.chat.state.create({
|
|
28441
28571
|
...initial.backend,
|
|
28442
28572
|
ownerEntry: owner.write,
|
|
@@ -28771,21 +28901,23 @@ function createChatSessionLaunches({
|
|
|
28771
28901
|
if (!opened?.chatId || !opened?.entry)
|
|
28772
28902
|
return "";
|
|
28773
28903
|
const chat = projectOwnChatEntry(opened.entry, opened.entryId || "", operation.identity.chatPK, opened.tsMs || Date.now());
|
|
28774
|
-
operation.listOwner.commitOwner(chat)
|
|
28904
|
+
if (!operation.listOwner.commitOwner(chat))
|
|
28905
|
+
throw makeChatUnavailableError();
|
|
28775
28906
|
return opened.chatId;
|
|
28776
28907
|
};
|
|
28777
28908
|
const presentOpenedChat = (opened, operation) => {
|
|
28778
28909
|
assertCurrent(operation);
|
|
28779
28910
|
if (!opened?.chatId || !opened?.entry)
|
|
28780
28911
|
return "";
|
|
28781
|
-
operation.pendingOwner.adoptOpenedChatId(opened.chatId);
|
|
28782
28912
|
const chatId = commitOpenedChat(opened, operation);
|
|
28913
|
+
operation.pendingOwner.adoptOpenedChatId(chatId);
|
|
28783
28914
|
pendingPeerSelect = null;
|
|
28784
28915
|
setSelectedChat(chatId);
|
|
28785
28916
|
return chatId;
|
|
28786
28917
|
};
|
|
28787
28918
|
const createChat = async (profiles, settings, lifecycle, adoptOpenedChat, operation, { profilesCurrent = false } = {}) => {
|
|
28788
28919
|
assertCurrent(operation);
|
|
28920
|
+
lifecycle.assertCurrent?.();
|
|
28789
28921
|
const { blocked, chatPK, chatPrivateKey } = operation.identity;
|
|
28790
28922
|
let profileList = Array.isArray(profiles) ? profiles : [profiles];
|
|
28791
28923
|
if (!profilesCurrent && profileList.length) {
|
|
@@ -28793,9 +28925,10 @@ function createChatSessionLaunches({
|
|
|
28793
28925
|
}
|
|
28794
28926
|
const blockedSet = new Set(blocked);
|
|
28795
28927
|
const requestedMembers = profileList.map(profileMember);
|
|
28796
|
-
|
|
28928
|
+
const expectedLineage = lifecycle.lineage || (requestedMembers.length === 0 ? "self" : requestedMembers.length === 1 ? "direct" : "group");
|
|
28929
|
+
if (expectedLineage === "direct") {
|
|
28797
28930
|
assertDirectAdmission(profileList[0], operation);
|
|
28798
|
-
} else if (
|
|
28931
|
+
} else if (expectedLineage === "group") {
|
|
28799
28932
|
if (normalizeChatAdmission(operation.identity.chatAdmission).groups === CHAT_GROUP_ADMISSION_MODES.CLOSED) {
|
|
28800
28933
|
throw new Error("group chats are closed");
|
|
28801
28934
|
}
|
|
@@ -28809,8 +28942,7 @@ function createChatSessionLaunches({
|
|
|
28809
28942
|
if (blockedSet.has(member.uid))
|
|
28810
28943
|
throw new Error("blocked peer cannot be added");
|
|
28811
28944
|
}
|
|
28812
|
-
const canonicalChatId = lifecycle.chatId || (
|
|
28813
|
-
const expectedLineage = requestedMembers.length === 0 ? "self" : requestedMembers.length === 1 ? "direct" : "group";
|
|
28945
|
+
const canonicalChatId = lifecycle.chatId || (expectedLineage === "self" ? deriveSelfChatId(chatPrivateKey, chatPK) : "");
|
|
28814
28946
|
const expectedMemberKeys = [chatPK, ...requestedMembers.map((member) => member.chatPK)].sort();
|
|
28815
28947
|
const matchingExistingChat = () => {
|
|
28816
28948
|
assertCurrent(operation);
|
|
@@ -28849,6 +28981,7 @@ function createChatSessionLaunches({
|
|
|
28849
28981
|
markDiag(diag, "chat.group.create.prekeys.start", { members: requestedMembers.length });
|
|
28850
28982
|
const additions = await prepareMlsAdditions(profileList);
|
|
28851
28983
|
assertCurrent(operation);
|
|
28984
|
+
lifecycle.assertCurrent?.();
|
|
28852
28985
|
markDiag(diag, "chat.group.create.prekeys.done", { members: requestedMembers.length, elapsedMs: Date.now() - prekeyStartedAt });
|
|
28853
28986
|
if (!additions.length && requestedMembers.length)
|
|
28854
28987
|
throw new Error("peer mls key package required");
|
|
@@ -28858,15 +28991,23 @@ function createChatSessionLaunches({
|
|
|
28858
28991
|
const created = await createGroupChat(cloud, operation.transitionIdentity, [...byKey.values()], settings, {
|
|
28859
28992
|
mls,
|
|
28860
28993
|
chatId: canonicalChatId || undefined,
|
|
28994
|
+
lineage: expectedLineage,
|
|
28861
28995
|
wakeMessageId: lifecycle.wakeMessageId,
|
|
28996
|
+
assertCurrent: () => {
|
|
28997
|
+
assertCurrent(operation);
|
|
28998
|
+
lifecycle.assertCurrent?.();
|
|
28999
|
+
},
|
|
29000
|
+
onCreateDispatched: lifecycle.onCreateDispatched,
|
|
28862
29001
|
onStateReady: (opened) => {
|
|
28863
29002
|
assertCurrent(operation);
|
|
29003
|
+
lifecycle.assertCurrent?.();
|
|
28864
29004
|
stateReady = true;
|
|
28865
29005
|
const chatId = adoptOpenedChat(opened);
|
|
28866
29006
|
lifecycle.onStateReady?.(chatId, opened, { created: true });
|
|
28867
29007
|
},
|
|
28868
29008
|
onOwnerReady: async (opened) => {
|
|
28869
29009
|
assertCurrent(operation);
|
|
29010
|
+
lifecycle.assertCurrent?.();
|
|
28870
29011
|
const chatId = adoptOpenedChat(opened);
|
|
28871
29012
|
const result = await lifecycle.onOwnerReady?.(chatId, opened, { created: true });
|
|
28872
29013
|
assertCurrent(operation);
|
|
@@ -28877,7 +29018,7 @@ function createChatSessionLaunches({
|
|
|
28877
29018
|
return adoptOpenedChat(created);
|
|
28878
29019
|
} catch (error) {
|
|
28879
29020
|
assertCurrent(operation);
|
|
28880
|
-
if (stateReady || !canonicalChatId
|
|
29021
|
+
if (stateReady || !canonicalChatId)
|
|
28881
29022
|
throw error;
|
|
28882
29023
|
await ensureChat(canonicalChatId);
|
|
28883
29024
|
assertCurrent(operation);
|
|
@@ -28891,6 +29032,10 @@ function createChatSessionLaunches({
|
|
|
28891
29032
|
const operation = operationContext();
|
|
28892
29033
|
return createChat(profiles, settings, lifecycle, (opened) => presentOpenedChat(opened, operation), operation);
|
|
28893
29034
|
};
|
|
29035
|
+
const materializeGroupChat = (profiles, settings = {}, lifecycle = {}) => {
|
|
29036
|
+
const operation = operationContext();
|
|
29037
|
+
return createChat(profiles, settings, lifecycle, (opened) => commitOpenedChat(opened, operation), operation);
|
|
29038
|
+
};
|
|
28894
29039
|
const runExistingDirectLifecycle = async (chatId, lifecycle, operation) => {
|
|
28895
29040
|
assertCurrent(operation);
|
|
28896
29041
|
const existing = getOwnerChat(chatId);
|
|
@@ -28962,6 +29107,7 @@ function createChatSessionLaunches({
|
|
|
28962
29107
|
getOpenOperation: (chatId) => directOpens.get(chatId)?.promise || null,
|
|
28963
29108
|
getPeerChatId,
|
|
28964
29109
|
materializeDirectChat,
|
|
29110
|
+
materializeGroupChat,
|
|
28965
29111
|
openDirectChat,
|
|
28966
29112
|
openNotesChat,
|
|
28967
29113
|
releaseOpenOperation: (chatId) => {
|
|
@@ -29026,16 +29172,21 @@ function createChatSessionPending({
|
|
|
29026
29172
|
getPeerProfile,
|
|
29027
29173
|
resolvePeerProfile,
|
|
29028
29174
|
setLocalByChat,
|
|
29029
|
-
getLocalByChat = () => new Map
|
|
29175
|
+
getLocalByChat = () => new Map,
|
|
29176
|
+
retireMaterializedChat = () => {}
|
|
29030
29177
|
}) {
|
|
29178
|
+
const admission = createChatAdmission({ getIdentity });
|
|
29031
29179
|
let pendingLaunch = null;
|
|
29032
29180
|
const pendingOperations = new Map;
|
|
29033
29181
|
let generation = 0;
|
|
29182
|
+
const operationForChat = (chatId) => pendingOperations.get(chatId) || [...pendingOperations.values()].find((state) => state.canonicalChatId === chatId) || null;
|
|
29183
|
+
const launchForChat = (chatId) => pendingLaunch && [pendingLaunch.id, pendingLaunch.canonicalChatId].includes(chatId) ? pendingLaunch : operationForChat(chatId)?.launch || storedLaunch(chatId);
|
|
29034
29184
|
const storedLaunch = (chatId) => getLocalByChat().get(chatId)?.find((message) => message.chatDraft)?.chatDraft || null;
|
|
29035
29185
|
const getPendingLaunch = () => {
|
|
29036
29186
|
if (!pendingLaunch)
|
|
29037
29187
|
return null;
|
|
29038
|
-
const
|
|
29188
|
+
const localByChat = getLocalByChat();
|
|
29189
|
+
const messages = localByChat.get(pendingLaunch.id) || localByChat.get(pendingLaunch.canonicalChatId) || [];
|
|
29039
29190
|
const message = messages[messages.length - 1] || null;
|
|
29040
29191
|
return { ...pendingLaunch, messages, message, chat: {
|
|
29041
29192
|
...pendingLaunch.chat,
|
|
@@ -29043,23 +29194,33 @@ function createChatSessionPending({
|
|
|
29043
29194
|
ts: message ? timestampMs(message.ts, null) : null
|
|
29044
29195
|
} };
|
|
29045
29196
|
};
|
|
29197
|
+
const resolvePendingChatId = (chatId) => {
|
|
29198
|
+
const canonicalId = operationForChat(chatId)?.canonicalChatId || launchForChat(chatId)?.canonicalChatId;
|
|
29199
|
+
return canonicalId && getOwnerChat(canonicalId) ? canonicalId : chatId;
|
|
29200
|
+
};
|
|
29046
29201
|
const finish = (launchId) => {
|
|
29047
29202
|
if (pendingLaunch?.id !== launchId)
|
|
29048
29203
|
return;
|
|
29204
|
+
if (pendingLaunch.canonicalChatId !== launchId) {
|
|
29205
|
+
publish();
|
|
29206
|
+
return;
|
|
29207
|
+
}
|
|
29049
29208
|
pendingLaunch = null;
|
|
29050
29209
|
pendingOperations.delete(launchId);
|
|
29051
29210
|
publish();
|
|
29052
29211
|
};
|
|
29053
29212
|
const track = (launchId, operation) => {
|
|
29054
29213
|
const tracked = Promise.resolve(operation);
|
|
29055
|
-
pendingOperations.
|
|
29214
|
+
const state = pendingOperations.get(launchId) || { active: true, promise: null };
|
|
29215
|
+
state.promise = tracked;
|
|
29216
|
+
pendingOperations.set(launchId, state);
|
|
29056
29217
|
tracked.then(() => {
|
|
29057
|
-
if (pendingOperations.get(launchId) !== tracked)
|
|
29218
|
+
if (pendingOperations.get(launchId) !== state || state.promise !== tracked)
|
|
29058
29219
|
return;
|
|
29059
29220
|
pendingOperations.delete(launchId);
|
|
29060
29221
|
finish(launchId);
|
|
29061
29222
|
}, () => {
|
|
29062
|
-
if (pendingOperations.get(launchId) !== tracked)
|
|
29223
|
+
if (pendingOperations.get(launchId) !== state || state.promise !== tracked)
|
|
29063
29224
|
return;
|
|
29064
29225
|
pendingOperations.delete(launchId);
|
|
29065
29226
|
if (pendingLaunch?.id !== launchId)
|
|
@@ -29071,6 +29232,12 @@ function createChatSessionPending({
|
|
|
29071
29232
|
});
|
|
29072
29233
|
return tracked;
|
|
29073
29234
|
};
|
|
29235
|
+
const cancelOperation = (launchId) => {
|
|
29236
|
+
const operation = pendingOperations.get(launchId);
|
|
29237
|
+
if (operation)
|
|
29238
|
+
operation.active = false;
|
|
29239
|
+
pendingOperations.delete(launchId);
|
|
29240
|
+
};
|
|
29074
29241
|
const show = (launch) => {
|
|
29075
29242
|
if (pendingLaunch?.id && pendingLaunch.id !== launch.id && !pendingOperations.has(pendingLaunch.id)) {
|
|
29076
29243
|
getRouteOwner().releaseOpenOperation?.(pendingLaunch.id);
|
|
@@ -29166,7 +29333,7 @@ function createChatSessionPending({
|
|
|
29166
29333
|
return cached;
|
|
29167
29334
|
return (fallbackProfiles || []).find((profile) => profileMatchesPeer(profile, peer)) || null;
|
|
29168
29335
|
};
|
|
29169
|
-
const profileAcceptsDirect = (identity, profile) => !!identity?.chatPK && !!identity?.chatPrivateKey && directAdmissionForPeer(
|
|
29336
|
+
const profileAcceptsDirect = (identity, profile) => !!identity?.chatPK && !!identity?.chatPrivateKey && admission.directAdmissionForPeer(profile) !== CHAT_ADMISSION_DECISIONS.REJECT;
|
|
29170
29337
|
const isChatAdmitted = (chat, fallbackProfiles = []) => {
|
|
29171
29338
|
const identity = getIdentity();
|
|
29172
29339
|
if (!chat)
|
|
@@ -29299,18 +29466,29 @@ function createChatSessionPending({
|
|
|
29299
29466
|
throw error;
|
|
29300
29467
|
}
|
|
29301
29468
|
};
|
|
29302
|
-
const
|
|
29303
|
-
const
|
|
29304
|
-
|
|
29305
|
-
|
|
29306
|
-
|
|
29469
|
+
const withReadyAttachmentTarget = async (chatId, options, send) => {
|
|
29470
|
+
const attemptGeneration = generation;
|
|
29471
|
+
await waitForSend(chatId, { expiresAt: options.expiresAt, signal: options.signal });
|
|
29472
|
+
if (attemptGeneration !== generation)
|
|
29473
|
+
throw makeChatUnavailableError();
|
|
29307
29474
|
const target = await admittedSendTarget(chatId, options);
|
|
29308
|
-
|
|
29475
|
+
if (attemptGeneration !== generation)
|
|
29476
|
+
throw makeChatUnavailableError();
|
|
29477
|
+
return send(target);
|
|
29309
29478
|
};
|
|
29479
|
+
const sendDurableAttachment = (chatId, attachment, options = {}) => withReadyAttachmentTarget(chatId, options, (target) => getSendActions().sendAttachment(target.routeAnchor, attachment, target.options));
|
|
29480
|
+
const sendDurableSharedAttachment = (chatId, attachment, options = {}) => withReadyAttachmentTarget(chatId, options, (target) => getSendActions().sendSharedAttachment(target.routeAnchor, attachment, target.options));
|
|
29310
29481
|
const stagePendingMessage = (launchId, local) => {
|
|
29311
|
-
|
|
29482
|
+
const launch = launchForChat(launchId);
|
|
29483
|
+
if (!launch || launch.id !== launchId)
|
|
29312
29484
|
throw makeChatUnavailableError();
|
|
29313
|
-
const { id, profiles, settings, chat } =
|
|
29485
|
+
const { id, profiles, settings, chat } = launch;
|
|
29486
|
+
const canonicalId = resolvePendingChatId(launchId);
|
|
29487
|
+
if (getOwnerChat(canonicalId)) {
|
|
29488
|
+
const { chatDraft: _draft, ...content } = local;
|
|
29489
|
+
setLocalByChat((current) => addLocalMessage(current, canonicalId, content));
|
|
29490
|
+
return;
|
|
29491
|
+
}
|
|
29314
29492
|
const chatDraft = { id, canonicalChatId: id, profiles, settings, chat };
|
|
29315
29493
|
setLocalByChat((current) => addLocalMessage(current, launchId, { ...local, chatDraft }));
|
|
29316
29494
|
};
|
|
@@ -29322,6 +29500,8 @@ function createChatSessionPending({
|
|
|
29322
29500
|
const canonical = targetId !== launchId ? current.get(targetId) || [] : [];
|
|
29323
29501
|
const writerIds = new Set(canonical.map((message) => message.cid));
|
|
29324
29502
|
const staged = (current.get(launchId) || []).filter((message) => !writerIds.has(message.cid));
|
|
29503
|
+
if (!canonical.length && !staged.length)
|
|
29504
|
+
return current;
|
|
29325
29505
|
next.set(targetId, [...canonical, ...staged].map((message) => {
|
|
29326
29506
|
const { chatDraft, ...content } = message;
|
|
29327
29507
|
return {
|
|
@@ -29337,14 +29517,22 @@ function createChatSessionPending({
|
|
|
29337
29517
|
if (getOwnerChat(canonicalId) && pendingLaunch?.id === launchId)
|
|
29338
29518
|
setSelectedChat(canonicalId);
|
|
29339
29519
|
};
|
|
29340
|
-
const
|
|
29341
|
-
const launch =
|
|
29342
|
-
if (!launch
|
|
29520
|
+
const dropChat = (chatId) => {
|
|
29521
|
+
const launch = launchForChat(chatId);
|
|
29522
|
+
if (!launch || launch.id !== chatId && launch.canonicalChatId !== chatId)
|
|
29343
29523
|
return false;
|
|
29344
29524
|
beginSelection();
|
|
29345
29525
|
getRouteOwner().releaseOpenOperation?.(launch.id);
|
|
29346
|
-
pendingLaunch
|
|
29347
|
-
|
|
29526
|
+
if (pendingLaunch?.id === launch.id)
|
|
29527
|
+
pendingLaunch = null;
|
|
29528
|
+
cancelOperation(launch.id);
|
|
29529
|
+
setLocalByChat((current) => {
|
|
29530
|
+
if (!current.has(launch.id))
|
|
29531
|
+
return current;
|
|
29532
|
+
const next = new Map(current);
|
|
29533
|
+
next.delete(launch.id);
|
|
29534
|
+
return next;
|
|
29535
|
+
});
|
|
29348
29536
|
if (getSelectedChatId() === launch.id || getSelectedChatId() === launch.canonicalChatId) {
|
|
29349
29537
|
setSelectedChat(null);
|
|
29350
29538
|
} else {
|
|
@@ -29352,25 +29540,74 @@ function createChatSessionPending({
|
|
|
29352
29540
|
}
|
|
29353
29541
|
return true;
|
|
29354
29542
|
};
|
|
29543
|
+
const retirePeer = (peerChatPK) => pendingLaunch?.chat?.members?.some((member) => member?.chatPK === peerChatPK) ? dropChat(pendingLaunch.id) : false;
|
|
29544
|
+
const discardChat = async (chatId) => {
|
|
29545
|
+
const state = operationForChat(chatId);
|
|
29546
|
+
if (!state?.dispatched)
|
|
29547
|
+
return { discarded: dropChat(chatId), chat: null };
|
|
29548
|
+
state.discarding = true;
|
|
29549
|
+
if (pendingLaunch?.id === state.launch.id)
|
|
29550
|
+
pendingLaunch = null;
|
|
29551
|
+
if ([state.launch.id, state.canonicalChatId].includes(getSelectedChatId()))
|
|
29552
|
+
setSelectedChat(null);
|
|
29553
|
+
else
|
|
29554
|
+
publish();
|
|
29555
|
+
state.retirementTarget ||= getOwnerChat(state.canonicalChatId) || null;
|
|
29556
|
+
if (state.retirementTarget)
|
|
29557
|
+
retireMaterializedChat(state.canonicalChatId);
|
|
29558
|
+
const attemptGeneration = generation;
|
|
29559
|
+
await state.promise.catch(() => null);
|
|
29560
|
+
if (attemptGeneration !== generation)
|
|
29561
|
+
throw makeChatUnavailableError();
|
|
29562
|
+
if (!state.retirementTarget) {
|
|
29563
|
+
if (!getOwnerChat(state.canonicalChatId))
|
|
29564
|
+
await ensureChat(state.canonicalChatId);
|
|
29565
|
+
state.retirementTarget = getOwnerChat(state.canonicalChatId) || null;
|
|
29566
|
+
}
|
|
29567
|
+
if (attemptGeneration !== generation || !state.retirementTarget)
|
|
29568
|
+
throw makeChatUnavailableError();
|
|
29569
|
+
retireMaterializedChat(state.canonicalChatId);
|
|
29570
|
+
return { discarded: true, chat: state.retirementTarget };
|
|
29571
|
+
};
|
|
29355
29572
|
const materialize = (launchId, wakeMessageId, sendInitial) => {
|
|
29356
29573
|
const launch = pendingLaunch;
|
|
29357
29574
|
if (!launch || launch.id !== launchId || pendingOperations.has(launchId))
|
|
29358
29575
|
throw makeChatUnavailableError();
|
|
29359
29576
|
const attemptGeneration = generation;
|
|
29577
|
+
const state = { active: true, promise: null, launch, canonicalChatId: launchId };
|
|
29578
|
+
pendingOperations.set(launchId, state);
|
|
29579
|
+
const assertCurrent = () => {
|
|
29580
|
+
if (attemptGeneration !== generation || !state.active)
|
|
29581
|
+
throw makeChatUnavailableError();
|
|
29582
|
+
};
|
|
29360
29583
|
const expiresAt = Date.now() + CHAT_SEND_CONNECTION_WAIT_MS;
|
|
29361
29584
|
let canonicalId = launchId;
|
|
29362
29585
|
const operation = (async () => {
|
|
29363
29586
|
await waitForSend(launchId, { expiresAt });
|
|
29364
|
-
|
|
29365
|
-
throw makeChatUnavailableError();
|
|
29587
|
+
assertCurrent();
|
|
29366
29588
|
let initialResult = null;
|
|
29367
29589
|
const lifecycle = {
|
|
29368
29590
|
wakeMessageId,
|
|
29591
|
+
assertCurrent,
|
|
29592
|
+
onCreateDispatched: (chatId) => {
|
|
29593
|
+
assertCurrent();
|
|
29594
|
+
state.dispatched = true;
|
|
29595
|
+
state.canonicalChatId = chatId;
|
|
29596
|
+
state.launch = { ...state.launch, canonicalChatId: chatId };
|
|
29597
|
+
},
|
|
29369
29598
|
onStateReady: (chatId) => {
|
|
29599
|
+
assertCurrent();
|
|
29370
29600
|
canonicalId = chatId;
|
|
29601
|
+
state.canonicalChatId = chatId;
|
|
29602
|
+
state.launch = { ...state.launch, canonicalChatId: chatId };
|
|
29371
29603
|
adoptOpenedChatId(chatId, launchId);
|
|
29604
|
+
if (state.discarding) {
|
|
29605
|
+
state.retirementTarget = getOwnerChat(chatId);
|
|
29606
|
+
retireMaterializedChat(chatId);
|
|
29607
|
+
}
|
|
29372
29608
|
},
|
|
29373
29609
|
onOwnerReady: async (chatId, _opened, context) => {
|
|
29610
|
+
assertCurrent();
|
|
29374
29611
|
canonicalId = chatId;
|
|
29375
29612
|
initialResult = await sendInitial(chatId, { ...context, expiresAt });
|
|
29376
29613
|
return initialResult;
|
|
@@ -29385,18 +29622,20 @@ function createChatSessionPending({
|
|
|
29385
29622
|
canonicalId = resolvedChatId;
|
|
29386
29623
|
initialResult = await sendInitial(resolvedChatId, { created: false, expiresAt });
|
|
29387
29624
|
} else {
|
|
29388
|
-
canonicalId = await getRouteOwner().
|
|
29625
|
+
canonicalId = await getRouteOwner().materializeGroupChat(launch.profiles, launch.settings, {
|
|
29389
29626
|
...lifecycle,
|
|
29390
|
-
chatId:
|
|
29627
|
+
chatId: launch.canonicalChatId,
|
|
29628
|
+
lineage: launch.chat.lineage
|
|
29391
29629
|
});
|
|
29392
29630
|
}
|
|
29393
29631
|
}
|
|
29394
29632
|
if (attemptGeneration !== generation)
|
|
29395
29633
|
throw makeChatUnavailableError();
|
|
29396
|
-
|
|
29634
|
+
if (state.active)
|
|
29635
|
+
settleMaterialization(launchId, canonicalId);
|
|
29397
29636
|
return { ...initialResult || {}, chatId: canonicalId };
|
|
29398
29637
|
})().catch((error) => {
|
|
29399
|
-
if (attemptGeneration === generation)
|
|
29638
|
+
if (attemptGeneration === generation && state.active)
|
|
29400
29639
|
settleMaterialization(launchId, canonicalId, true);
|
|
29401
29640
|
throw error;
|
|
29402
29641
|
});
|
|
@@ -29407,10 +29646,74 @@ function createChatSessionPending({
|
|
|
29407
29646
|
...context?.expiresAt ? { expiresAt: context.expiresAt } : {},
|
|
29408
29647
|
...context?.created === true ? { deliverRecipients: false, ownerImmediate: true } : {}
|
|
29409
29648
|
});
|
|
29649
|
+
const runChatMutation = (chatId, mutate, { initialProfiles } = {}) => {
|
|
29650
|
+
const launch = launchForChat(chatId);
|
|
29651
|
+
const canonicalId = resolvePendingChatId(chatId);
|
|
29652
|
+
const active = launch && pendingOperations.get(launch.id);
|
|
29653
|
+
if (active?.discarding)
|
|
29654
|
+
throw makeChatUnavailableError();
|
|
29655
|
+
if (!active && getOwnerChat(canonicalId))
|
|
29656
|
+
return mutate(canonicalId, { created: false });
|
|
29657
|
+
if (!launch || getIdentity().chatBanned)
|
|
29658
|
+
throw makeChatUnavailableError();
|
|
29659
|
+
const attemptGeneration = generation;
|
|
29660
|
+
const perform = async (id, context) => {
|
|
29661
|
+
if (attemptGeneration !== generation)
|
|
29662
|
+
throw makeChatUnavailableError();
|
|
29663
|
+
const value = await mutate(id, context);
|
|
29664
|
+
if (attemptGeneration !== generation)
|
|
29665
|
+
throw makeChatUnavailableError();
|
|
29666
|
+
const ownerEntry = getOwnerChat(id)?.ownEntry;
|
|
29667
|
+
if (!ownerEntry)
|
|
29668
|
+
throw makeChatUnavailableError();
|
|
29669
|
+
return { value, ownerEntry };
|
|
29670
|
+
};
|
|
29671
|
+
if (active) {
|
|
29672
|
+
return track(launch.id, active.promise.then(async (result) => {
|
|
29673
|
+
if (!active.active)
|
|
29674
|
+
throw makeChatUnavailableError();
|
|
29675
|
+
const id = result?.chatId || launch.canonicalChatId;
|
|
29676
|
+
return { ...await perform(id, { created: false }), chatId: id };
|
|
29677
|
+
})).then((result) => result.value);
|
|
29678
|
+
}
|
|
29679
|
+
pendingLaunch = launch;
|
|
29680
|
+
if (initialProfiles) {
|
|
29681
|
+
if (launch.chat.lineage === "self")
|
|
29682
|
+
throw new Error("self chat membership is fixed");
|
|
29683
|
+
const profiles = initialProfiles(launch.profiles);
|
|
29684
|
+
if (profiles === launch.profiles)
|
|
29685
|
+
return Promise.resolve({ unchanged: true });
|
|
29686
|
+
const members = profiles.map(profileMember);
|
|
29687
|
+
chatCapabilities(members.length + 1);
|
|
29688
|
+
const self = memberFromChatIdentity(transitionIdentity());
|
|
29689
|
+
if (members.some((member) => member.chatPK === self.chatPK))
|
|
29690
|
+
throw new Error("current user is already a chat member");
|
|
29691
|
+
const lineage = launch.chat.lineage === "group" || members.length !== 1 ? "group" : launch.chat.lineage;
|
|
29692
|
+
pendingLaunch = {
|
|
29693
|
+
...launch,
|
|
29694
|
+
canonicalChatId: lineage === "group" && launch.chat.lineage !== "group" ? randomChatId() : launch.canonicalChatId,
|
|
29695
|
+
profiles: profiles.map((profile, index) => pendingProfile(profile, members[index])),
|
|
29696
|
+
chat: {
|
|
29697
|
+
...launch.chat,
|
|
29698
|
+
lineage,
|
|
29699
|
+
peerChatPK: lineage === "direct" ? members[0]?.chatPK || null : null,
|
|
29700
|
+
members: [self, ...members],
|
|
29701
|
+
memberCount: members.length + 1,
|
|
29702
|
+
memberChatPKs: [self.chatPK, ...members.map((member) => member.chatPK)],
|
|
29703
|
+
memberUids: [self.uid, ...members.map((member) => member.uid)]
|
|
29704
|
+
}
|
|
29705
|
+
};
|
|
29706
|
+
publish();
|
|
29707
|
+
}
|
|
29708
|
+
return materialize(launch.id, "", (id, context) => perform(id, {
|
|
29709
|
+
...context,
|
|
29710
|
+
...initialSendOptions({}, context)
|
|
29711
|
+
})).then((result) => result.value);
|
|
29712
|
+
};
|
|
29410
29713
|
const sendPendingMessage = (launchId, message, options = {}) => {
|
|
29411
29714
|
const { chatPK } = getIdentity();
|
|
29412
|
-
const launch =
|
|
29413
|
-
const previousOperation = pendingOperations.get(launchId);
|
|
29715
|
+
const launch = launchForChat(launchId);
|
|
29716
|
+
const previousOperation = pendingOperations.get(launchId)?.promise;
|
|
29414
29717
|
if (!launch || launch.id !== launchId)
|
|
29415
29718
|
throw makeChatUnavailableError();
|
|
29416
29719
|
const routeAnchor = launch.chat?.members?.find((member) => member.chatPK !== chatPK)?.chatPK || chatPK;
|
|
@@ -29429,8 +29732,8 @@ function createChatSessionPending({
|
|
|
29429
29732
|
};
|
|
29430
29733
|
const sendPendingAttachment = (launchId, attachment, options = {}) => {
|
|
29431
29734
|
const { chatPK } = getIdentity();
|
|
29432
|
-
const launch =
|
|
29433
|
-
const previousOperation = pendingOperations.get(launchId);
|
|
29735
|
+
const launch = launchForChat(launchId);
|
|
29736
|
+
const previousOperation = pendingOperations.get(launchId)?.promise;
|
|
29434
29737
|
if (!launch || launch.id !== launchId)
|
|
29435
29738
|
throw makeChatUnavailableError();
|
|
29436
29739
|
const routeAnchor = launch.chat?.members?.find((member) => member.chatPK !== chatPK)?.chatPK || chatPK;
|
|
@@ -29445,8 +29748,8 @@ function createChatSessionPending({
|
|
|
29445
29748
|
};
|
|
29446
29749
|
const sendPendingSharedAttachment = (launchId, attachment, options = {}) => {
|
|
29447
29750
|
const { chatPK } = getIdentity();
|
|
29448
|
-
const launch =
|
|
29449
|
-
const previousOperation = pendingOperations.get(launchId);
|
|
29751
|
+
const launch = launchForChat(launchId);
|
|
29752
|
+
const previousOperation = pendingOperations.get(launchId)?.promise;
|
|
29450
29753
|
if (!launch || launch.id !== launchId)
|
|
29451
29754
|
throw makeChatUnavailableError();
|
|
29452
29755
|
const routeAnchor = launch.chat?.members?.find((member) => member.chatPK !== chatPK)?.chatPK || chatPK;
|
|
@@ -29459,6 +29762,12 @@ function createChatSessionPending({
|
|
|
29459
29762
|
return track(launchId, previousOperation.then((result) => sendDurableSharedAttachment(result?.chatId || launchId, queuedAttachment, options)));
|
|
29460
29763
|
};
|
|
29461
29764
|
const sendChatMessage = (chatId, message, options = {}) => {
|
|
29765
|
+
const operation = operationForChat(chatId);
|
|
29766
|
+
if (operation?.discarding)
|
|
29767
|
+
throw makeChatUnavailableError();
|
|
29768
|
+
if (operation)
|
|
29769
|
+
return sendPendingMessage(operation.launch.id, message, options);
|
|
29770
|
+
chatId = resolvePendingChatId(chatId);
|
|
29462
29771
|
if (!getOwnerChat(chatId)) {
|
|
29463
29772
|
if (!pendingLaunch)
|
|
29464
29773
|
pendingLaunch = storedLaunch(chatId);
|
|
@@ -29469,9 +29778,10 @@ function createChatSessionPending({
|
|
|
29469
29778
|
return sendDurableMessage(chatId, message, options);
|
|
29470
29779
|
};
|
|
29471
29780
|
const retryMessage = (chatId, cid) => {
|
|
29781
|
+
chatId = resolvePendingChatId(chatId);
|
|
29472
29782
|
const attemptGeneration = generation;
|
|
29473
29783
|
const launch = pendingLaunch && (pendingLaunch.id === chatId || pendingLaunch.canonicalChatId === chatId) ? pendingLaunch : storedLaunch(chatId);
|
|
29474
|
-
const failed = getLocalByChat().get(
|
|
29784
|
+
const failed = getLocalByChat().get(chatId)?.find((message) => message.cid === cid && message.failed);
|
|
29475
29785
|
if (!failed)
|
|
29476
29786
|
return false;
|
|
29477
29787
|
if (launch && !getOwnerChat(chatId)) {
|
|
@@ -29508,8 +29818,24 @@ function createChatSessionPending({
|
|
|
29508
29818
|
getSendActions().discardLocalMessage(chatId, cid);
|
|
29509
29819
|
return true;
|
|
29510
29820
|
};
|
|
29511
|
-
const sendChatAttachment = (chatId, attachment, options = {}) =>
|
|
29512
|
-
|
|
29821
|
+
const sendChatAttachment = (chatId, attachment, options = {}) => {
|
|
29822
|
+
const operation = operationForChat(chatId);
|
|
29823
|
+
if (operation?.discarding)
|
|
29824
|
+
throw makeChatUnavailableError();
|
|
29825
|
+
if (operation)
|
|
29826
|
+
return sendPendingAttachment(operation.launch.id, attachment, options);
|
|
29827
|
+
chatId = resolvePendingChatId(chatId);
|
|
29828
|
+
return pendingLaunch?.id === chatId && !getOwnerChat(chatId) ? sendPendingAttachment(chatId, attachment, options) : sendDurableAttachment(chatId, attachment, options);
|
|
29829
|
+
};
|
|
29830
|
+
const sendChatSharedAttachment = (chatId, attachment, options = {}) => {
|
|
29831
|
+
const operation = operationForChat(chatId);
|
|
29832
|
+
if (operation?.discarding)
|
|
29833
|
+
throw makeChatUnavailableError();
|
|
29834
|
+
if (operation)
|
|
29835
|
+
return sendPendingSharedAttachment(operation.launch.id, attachment, options);
|
|
29836
|
+
chatId = resolvePendingChatId(chatId);
|
|
29837
|
+
return pendingLaunch?.id === chatId && !getOwnerChat(chatId) ? sendPendingSharedAttachment(chatId, attachment, options) : sendDurableSharedAttachment(chatId, attachment, options);
|
|
29838
|
+
};
|
|
29513
29839
|
const sendDirectAttachment = async (profile, attachment, options = {}) => sendChatAttachment(await getRouteOwner().openDirectChat(profile), attachment, options);
|
|
29514
29840
|
const sendMaterializedDirect = async (profile, payload, options, send) => {
|
|
29515
29841
|
const queuedPayload = { ...payload, cid: makeSendCid(payload) };
|
|
@@ -29556,6 +29882,10 @@ function createChatSessionPending({
|
|
|
29556
29882
|
return settleNormalizedTargets(normalizedTargets, (target) => sendPickerTarget(target, (chatId) => sendChatSharedAttachment(chatId, attachment), (profile) => sendPickerDirectSharedAttachment(profile, attachment)));
|
|
29557
29883
|
};
|
|
29558
29884
|
return {
|
|
29885
|
+
directAdmissionForPeer: admission.directAdmissionForPeer,
|
|
29886
|
+
dropChat,
|
|
29887
|
+
discardChat,
|
|
29888
|
+
canStartDirectChat: admission.canStartDirectChat,
|
|
29559
29889
|
adoptOpenedChatId,
|
|
29560
29890
|
adoptResolved,
|
|
29561
29891
|
beginPendingChat,
|
|
@@ -29563,8 +29893,11 @@ function createChatSessionPending({
|
|
|
29563
29893
|
canSendToChat,
|
|
29564
29894
|
isChatAdmitted,
|
|
29565
29895
|
isPendingLaunchAdmitted,
|
|
29566
|
-
isPendingChat: (chatId) =>
|
|
29896
|
+
isPendingChat: (chatId) => !!launchForChat(chatId),
|
|
29567
29897
|
getPendingLaunch,
|
|
29898
|
+
getActionChat: (chatId) => getOwnerChat(resolvePendingChatId(chatId)) || launchForChat(chatId)?.chat || null,
|
|
29899
|
+
hasPendingChatOperation: (chatId) => !!operationForChat(chatId),
|
|
29900
|
+
runChatMutation,
|
|
29568
29901
|
openNewChat,
|
|
29569
29902
|
retirePeer,
|
|
29570
29903
|
reconcileAdmission: () => {
|
|
@@ -29574,7 +29907,7 @@ function createChatSessionPending({
|
|
|
29574
29907
|
beginSelection();
|
|
29575
29908
|
getRouteOwner().clearOpenOperations?.();
|
|
29576
29909
|
pendingLaunch = null;
|
|
29577
|
-
|
|
29910
|
+
cancelOperation(launch.id);
|
|
29578
29911
|
if (getSelectedChatId() === launch.id || getSelectedChatId() === launch.canonicalChatId) {
|
|
29579
29912
|
setSelectedChat(null);
|
|
29580
29913
|
} else {
|
|
@@ -29584,10 +29917,13 @@ function createChatSessionPending({
|
|
|
29584
29917
|
},
|
|
29585
29918
|
reset: () => {
|
|
29586
29919
|
generation += 1;
|
|
29920
|
+
admission.reset();
|
|
29587
29921
|
beginSelection();
|
|
29588
29922
|
if (pendingLaunch?.id)
|
|
29589
29923
|
getRouteOwner().releaseOpenOperation?.(pendingLaunch.id);
|
|
29590
29924
|
pendingLaunch = null;
|
|
29925
|
+
for (const operation of pendingOperations.values())
|
|
29926
|
+
operation.active = false;
|
|
29591
29927
|
pendingOperations.clear();
|
|
29592
29928
|
},
|
|
29593
29929
|
selectChat: (chatId, { flushPrevious }) => {
|
|
@@ -29600,9 +29936,14 @@ function createChatSessionPending({
|
|
|
29600
29936
|
const selectResolved = () => {
|
|
29601
29937
|
if (!isSelectionCurrent(selection) || getIdentity().chatBanned)
|
|
29602
29938
|
return false;
|
|
29603
|
-
if (pendingLaunch && chatId !== pendingLaunch.id
|
|
29939
|
+
if (pendingLaunch && chatId !== pendingLaunch.id) {
|
|
29940
|
+
if (chatId === pendingLaunch.canonicalChatId) {
|
|
29941
|
+
settleMaterialization(pendingLaunch.id, chatId);
|
|
29942
|
+
}
|
|
29604
29943
|
getRouteOwner().releaseOpenOperation?.(pendingLaunch.id);
|
|
29605
29944
|
pendingLaunch = null;
|
|
29945
|
+
if (getSelectedChatId() === chatId)
|
|
29946
|
+
publish();
|
|
29606
29947
|
}
|
|
29607
29948
|
const previousChatId = getSelectedChatId();
|
|
29608
29949
|
if (previousChatId && previousChatId !== chatId)
|
|
@@ -30119,7 +30460,6 @@ function createChatDelete({
|
|
|
30119
30460
|
for (const chatId of chatIds || []) {
|
|
30120
30461
|
if (!chatId)
|
|
30121
30462
|
continue;
|
|
30122
|
-
dropCachedChat(localCache, chatId);
|
|
30123
30463
|
closeMessageBatchRef.current?.(chatId);
|
|
30124
30464
|
if (localForRender.has(chatId)) {
|
|
30125
30465
|
if (!localChanged)
|
|
@@ -30151,6 +30491,7 @@ function createChatDelete({
|
|
|
30151
30491
|
keepSelectedDeletedChatIdsRef.current.delete(chatId);
|
|
30152
30492
|
}
|
|
30153
30493
|
}
|
|
30494
|
+
dropCachedChats(localCache, ids);
|
|
30154
30495
|
clearDeletedChatState(ids);
|
|
30155
30496
|
renderVisibleChats();
|
|
30156
30497
|
setSelectedChat((current) => current && ids.includes(current) && !options?.keepSelected ? null : current);
|
|
@@ -30208,9 +30549,7 @@ function createChatDelete({
|
|
|
30208
30549
|
const dropChat = (chatId) => {
|
|
30209
30550
|
if (!chatId)
|
|
30210
30551
|
return;
|
|
30211
|
-
clearDeletedChatState([chatId]);
|
|
30212
30552
|
listActionsRef.current?.removeOwner?.(chatId, { warm: false });
|
|
30213
|
-
setSelectedChat((current) => current === chatId ? null : current);
|
|
30214
30553
|
};
|
|
30215
30554
|
const dropLeftChat = (chatId) => {
|
|
30216
30555
|
if (!chatId)
|
|
@@ -31576,8 +31915,6 @@ async function setOwnChatNotificationMode(cloud, identity, entryId, entry, value
|
|
|
31576
31915
|
if (!current?.current || current.retirement)
|
|
31577
31916
|
throw makeChatUnavailableError();
|
|
31578
31917
|
const manifest = current.current.manifest;
|
|
31579
|
-
if (manifest.lineage !== "group")
|
|
31580
|
-
throw new Error("group chat required");
|
|
31581
31918
|
const requestedMode = cleanText(value);
|
|
31582
31919
|
const notificationMode = normalizeChatNotificationMode(requestedMode, manifest);
|
|
31583
31920
|
if (notificationMode !== requestedMode) {
|
|
@@ -31637,20 +31974,20 @@ function createChatSessionNotifications({
|
|
|
31637
31974
|
throw makeChatUnavailableError();
|
|
31638
31975
|
}
|
|
31639
31976
|
};
|
|
31640
|
-
const requireOwnerChat = (chatId, operation
|
|
31977
|
+
const requireOwnerChat = (chatId, operation) => {
|
|
31641
31978
|
assertCurrent(operation);
|
|
31642
31979
|
const chat = getOwnerChat(chatId);
|
|
31643
|
-
if (operation.identity.chatBanned || canWriteChat(chatId) !== true ||
|
|
31980
|
+
if (operation.identity.chatBanned || canWriteChat(chatId) !== true || !chat?.ownEntry || chat.ownEntry.retirement || !chat.entryId) {
|
|
31644
31981
|
throw makeChatUnavailableError();
|
|
31645
31982
|
}
|
|
31646
31983
|
return chat;
|
|
31647
31984
|
};
|
|
31648
31985
|
const setChatNotificationMode = async (chatId, mode) => {
|
|
31649
31986
|
const operation = operationContext();
|
|
31650
|
-
requireOwnerChat(chatId, operation
|
|
31987
|
+
requireOwnerChat(chatId, operation);
|
|
31651
31988
|
const releaseMutation = await operation.mutationGate.acquireExclusive(chatId);
|
|
31652
31989
|
try {
|
|
31653
|
-
const chat = requireOwnerChat(chatId, operation
|
|
31990
|
+
const chat = requireOwnerChat(chatId, operation);
|
|
31654
31991
|
const committed = await writeMode(cloud, operation.identity, chat.entryId, chat.ownEntry, mode);
|
|
31655
31992
|
assertCurrent(operation);
|
|
31656
31993
|
const projected = projectOwnChatEntry(committed, chat.entryId, operation.identity.chatPK, chat.ts);
|
|
@@ -32036,7 +32373,7 @@ function createChatSession({
|
|
|
32036
32373
|
const owner = getOwnerChat(chatId);
|
|
32037
32374
|
if (owner)
|
|
32038
32375
|
return !isChatMembershipRemoved(owner);
|
|
32039
|
-
return pendingOwner?.
|
|
32376
|
+
return pendingOwner?.isPendingChat(chatId) === true;
|
|
32040
32377
|
};
|
|
32041
32378
|
const requireOnline = () => {
|
|
32042
32379
|
if (!online)
|
|
@@ -32051,10 +32388,6 @@ function createChatSession({
|
|
|
32051
32388
|
if (chatId && !isMutableChat(chatId))
|
|
32052
32389
|
throw makeChatUnavailableError();
|
|
32053
32390
|
};
|
|
32054
|
-
const gateChatMutation = (fn) => (chatId, ...rest) => {
|
|
32055
|
-
requireMutableChat(chatId);
|
|
32056
|
-
return fn(chatId, ...rest);
|
|
32057
|
-
};
|
|
32058
32391
|
const gateAdmittedChatMutation = (fn) => (chatId, ...rest) => {
|
|
32059
32392
|
requireMutableChat(chatId);
|
|
32060
32393
|
return Promise.resolve(pendingOwner.assertChatAdmission(chatId)).then(() => fn(chatId, ...rest));
|
|
@@ -32256,7 +32589,8 @@ function createChatSession({
|
|
|
32256
32589
|
getPeerProfile,
|
|
32257
32590
|
resolvePeerProfile,
|
|
32258
32591
|
setLocalByChat,
|
|
32259
|
-
getLocalByChat: () => localByChatRef.current
|
|
32592
|
+
getLocalByChat: () => localByChatRef.current,
|
|
32593
|
+
retireMaterializedChat: (chatId) => actionOwner.deleteActions.beginRetirement(chatId)
|
|
32260
32594
|
});
|
|
32261
32595
|
launchOwner = createChatSessionLaunches({
|
|
32262
32596
|
cloud,
|
|
@@ -32296,29 +32630,79 @@ function createChatSession({
|
|
|
32296
32630
|
const openDirectChat = (...args) => launchOwner.openDirectChat(...args);
|
|
32297
32631
|
const openNotesChat = (...args) => launchOwner.openNotesChat(...args);
|
|
32298
32632
|
const openNewChat = (...args) => pendingOwner.openNewChat(...args);
|
|
32299
|
-
const
|
|
32300
|
-
const canStartDirectChat2 = (profile) => canStartDirectChat(chatPrivateKey, chatPK, profile, chatAdmission);
|
|
32633
|
+
const { directAdmissionForPeer: directAdmissionForPeer2, canStartDirectChat } = pendingOwner;
|
|
32301
32634
|
const canInviteToGroup2 = (profile) => canInviteToGroup(profile, chatAdmission);
|
|
32302
32635
|
const createGroupChat2 = (...args) => launchOwner.createGroupChat(...args);
|
|
32303
32636
|
const selectLocalChat = (...args) => launchOwner.selectLocalChat(...args);
|
|
32304
|
-
const
|
|
32637
|
+
const mutateMaterializedChat = (chatId, mutate, options) => {
|
|
32305
32638
|
requireMutableChat(chatId);
|
|
32306
|
-
|
|
32307
|
-
|
|
32639
|
+
return pendingOwner.runChatMutation(chatId, async (id, context) => {
|
|
32640
|
+
await pendingOwner.assertChatAdmission(id);
|
|
32641
|
+
return mutate(id, context);
|
|
32642
|
+
}, options);
|
|
32643
|
+
};
|
|
32644
|
+
const addChatMembers = (chatId, profiles) => {
|
|
32645
|
+
const additions = Array.isArray(profiles) ? profiles : [profiles];
|
|
32646
|
+
additions.forEach(membershipOwner.profileMember);
|
|
32647
|
+
return mutateMaterializedChat(chatId, (id, context) => context.created ? { entry: getOwnerChat(id).ownEntry } : membershipOwner.addChatMembers(id, additions), {
|
|
32648
|
+
initialProfiles: (current) => {
|
|
32649
|
+
const next = new Map(current.map((profile) => [profile.chatPK, profile]));
|
|
32650
|
+
for (const profile of additions)
|
|
32651
|
+
next.set(profile.chatPK, profile);
|
|
32652
|
+
return next.size === current.length ? current : [...next.values()];
|
|
32653
|
+
}
|
|
32654
|
+
});
|
|
32308
32655
|
};
|
|
32309
|
-
const kickChatMember =
|
|
32310
|
-
|
|
32311
|
-
|
|
32312
|
-
|
|
32656
|
+
const kickChatMember = (chatId, memberChatPK) => {
|
|
32657
|
+
const target = cleanText(memberChatPK);
|
|
32658
|
+
if (!target)
|
|
32659
|
+
throw new Error("chat member required");
|
|
32660
|
+
if (target === chatPK && pendingOwner.isPendingChat(chatId)) {
|
|
32661
|
+
return deleteChat(chatId);
|
|
32662
|
+
}
|
|
32663
|
+
return mutateMaterializedChat(chatId, (id, context) => context.created ? { entry: getOwnerChat(id).ownEntry } : membershipOwner.kickChatMember(id, target), {
|
|
32664
|
+
initialProfiles: (current) => current.some((profile) => profile.chatPK === target) ? current.filter((profile) => profile.chatPK !== target) : current
|
|
32665
|
+
});
|
|
32313
32666
|
};
|
|
32314
32667
|
const leaveChat = (chatId) => {
|
|
32668
|
+
if (pendingOwner.isPendingChat(chatId)) {
|
|
32669
|
+
return deleteChat(chatId);
|
|
32670
|
+
}
|
|
32315
32671
|
requireMutableChat(chatId);
|
|
32316
32672
|
return membershipOwner.leaveChat(chatId);
|
|
32317
32673
|
};
|
|
32318
32674
|
const leaveOwnedChat = (...args) => membershipOwner.leaveOwnedChat(...args);
|
|
32319
|
-
const
|
|
32320
|
-
|
|
32321
|
-
|
|
32675
|
+
const sharedSettingsChat = (chatId) => {
|
|
32676
|
+
const chat = pendingOwner.getActionChat(chatId);
|
|
32677
|
+
if (chat?.lineage === "self")
|
|
32678
|
+
throw new Error("self chat identity follows your profile");
|
|
32679
|
+
return chat;
|
|
32680
|
+
};
|
|
32681
|
+
const updateChatSettings2 = async (chatId, settings) => {
|
|
32682
|
+
const chat = sharedSettingsChat(chatId);
|
|
32683
|
+
const requested = normalizeChatSettingsInput({ ...chat?.settings, ...settings });
|
|
32684
|
+
const changes = ["title", "avatarRef", "retention"].filter((key) => Object.prototype.hasOwnProperty.call(settings, key));
|
|
32685
|
+
const patch = Object.fromEntries(changes.map((key) => [key, requested[key]]));
|
|
32686
|
+
if (chat && !pendingOwner.hasPendingChatOperation(chatId) && changes.every((key) => chat.settings?.[key] === requested[key])) {
|
|
32687
|
+
return Promise.resolve({ unchanged: true });
|
|
32688
|
+
}
|
|
32689
|
+
return mutateMaterializedChat(chatId, (id) => settingsOwner.updateChatSettings(id, patch));
|
|
32690
|
+
};
|
|
32691
|
+
const updateChatAvatar = async (chatId, data) => {
|
|
32692
|
+
assertChatAvatarBytes(data);
|
|
32693
|
+
const chat = sharedSettingsChat(chatId);
|
|
32694
|
+
if (chat?.lineage !== "group")
|
|
32695
|
+
throw new Error("group chat required");
|
|
32696
|
+
return mutateMaterializedChat(chatId, (id) => settingsOwner.updateChatAvatar(id, data));
|
|
32697
|
+
};
|
|
32698
|
+
const setChatNotificationMode = async (chatId, mode) => {
|
|
32699
|
+
const chat = pendingOwner.getActionChat(chatId);
|
|
32700
|
+
if (chat && normalizeChatNotificationMode(mode, chat) !== mode)
|
|
32701
|
+
throw new Error("all notifications are unavailable in large groups");
|
|
32702
|
+
if (chat && !pendingOwner.hasPendingChatOperation(chatId) && (chat.notificationMode || defaultChatNotificationMode(chat)) === mode)
|
|
32703
|
+
return mode;
|
|
32704
|
+
return mutateMaterializedChat(chatId, (id) => notificationsOwner.setChatNotificationMode(id, mode));
|
|
32705
|
+
};
|
|
32322
32706
|
const updateMessage = async (chatId, original, changes) => {
|
|
32323
32707
|
if (chatBanned)
|
|
32324
32708
|
throw makeChatUnavailableError();
|
|
@@ -32472,7 +32856,27 @@ function createChatSession({
|
|
|
32472
32856
|
};
|
|
32473
32857
|
const dropChat = (...args) => actionOwner.deleteActions.dropChat(...args);
|
|
32474
32858
|
const dropUnavailableChat = (...args) => actionOwner.deleteActions.dropUnavailableChat(...args);
|
|
32475
|
-
const deleteChat =
|
|
32859
|
+
const deleteChat = async (value, options) => {
|
|
32860
|
+
const inputs = Array.isArray(value) ? value : [value];
|
|
32861
|
+
const durable = [];
|
|
32862
|
+
let discarded = 0;
|
|
32863
|
+
for (const input of inputs) {
|
|
32864
|
+
const id = cleanText(typeof input === "string" ? input : input?.chatId || input?.id);
|
|
32865
|
+
if (pendingOwner.hasPendingChatOperation(id) || !getOwnerChat(id) && pendingOwner.isPendingChat(id)) {
|
|
32866
|
+
const result = await pendingOwner.discardChat(id);
|
|
32867
|
+
if (result.chat)
|
|
32868
|
+
durable.push(result.chat);
|
|
32869
|
+
else if (result.discarded)
|
|
32870
|
+
discarded += 1;
|
|
32871
|
+
} else
|
|
32872
|
+
durable.push(input);
|
|
32873
|
+
}
|
|
32874
|
+
if (!durable.length)
|
|
32875
|
+
return Array.isArray(value) ? discarded : discarded > 0;
|
|
32876
|
+
requireOnline();
|
|
32877
|
+
const deleted = await actionOwner.deleteActions.deleteChat(Array.isArray(value) ? durable : durable[0], options);
|
|
32878
|
+
return Array.isArray(value) ? discarded + Number(deleted) : deleted;
|
|
32879
|
+
};
|
|
32476
32880
|
const purgePeerChats = async (affected, operation) => {
|
|
32477
32881
|
const identity = operation.identity;
|
|
32478
32882
|
for (const chat of affected) {
|
|
@@ -32577,14 +32981,14 @@ function createChatSession({
|
|
|
32577
32981
|
const sendPendingChatMessage = (...args) => pendingOwner.sendPendingChatMessage(...args);
|
|
32578
32982
|
const sendChatMessage = sendMessage;
|
|
32579
32983
|
const sendDirectAttachment = gateOnlineMutation((...args) => pendingOwner.sendDirectAttachment(...args));
|
|
32580
|
-
const sendChatAttachment =
|
|
32984
|
+
const sendChatAttachment = gateOnlineMutation((...args) => pendingOwner.sendChatAttachment(...args));
|
|
32581
32985
|
const retryMessage = async (...args) => {
|
|
32582
32986
|
const result = await pendingOwner.retryMessage(...args);
|
|
32583
32987
|
if (online)
|
|
32584
32988
|
drainMembershipOutbox();
|
|
32585
32989
|
return result;
|
|
32586
32990
|
};
|
|
32587
|
-
const sendAttachment =
|
|
32991
|
+
const sendAttachment = gateOnlineMutation((...args) => pendingOwner.sendChatAttachment(...args));
|
|
32588
32992
|
const sendChatImage = (chatId, image, options = {}) => sendChatAttachment(chatId, { ...image, type: image?.type || "img" }, options);
|
|
32589
32993
|
const sendAttachmentMany = gateOnlineMutation((...args) => pendingOwner.sendAttachmentMany(...args));
|
|
32590
32994
|
const sendImageMany = (targets, image, options = {}) => sendAttachmentMany(targets, { ...image, type: image?.type || "img" }, options);
|
|
@@ -32642,7 +33046,11 @@ function createChatSession({
|
|
|
32642
33046
|
});
|
|
32643
33047
|
return state;
|
|
32644
33048
|
};
|
|
32645
|
-
const setChatTtl =
|
|
33049
|
+
const setChatTtl = async (chatId, retention) => {
|
|
33050
|
+
const next = cleanChatRetention(retention);
|
|
33051
|
+
await updateChatSettings2(chatId, { retention: next });
|
|
33052
|
+
return next;
|
|
33053
|
+
};
|
|
32646
33054
|
const makeMessagePermanent = gateAdmittedChatMutation((...args) => actionOwner.saveActions.makeMessagePermanent(...args));
|
|
32647
33055
|
const makeMessageTemporary = gateAdmittedChatMutation((...args) => actionOwner.saveActions.makeMessageTemporary(...args));
|
|
32648
33056
|
const readMessageFile = (...args) => actionOwner.saveActions.readMessageFile(...args);
|
|
@@ -32745,6 +33153,17 @@ function createChatSession({
|
|
|
32745
33153
|
chatsRef,
|
|
32746
33154
|
reconcileMessageBatches: (...args) => messageBatchOwner.reconcileChats(...args),
|
|
32747
33155
|
warmChats: (...args) => messageBatchOwner.warm(...args),
|
|
33156
|
+
onOwnerRemoved: (chatIds) => {
|
|
33157
|
+
for (const chatId of chatIds)
|
|
33158
|
+
pendingOwner.dropChat(chatId);
|
|
33159
|
+
actionOwner.deleteActions.clearDeletedChatState(chatIds);
|
|
33160
|
+
for (const chatId of chatIds) {
|
|
33161
|
+
actionOwner.liveActions.closeChatActivity(chatId);
|
|
33162
|
+
historyOwner.clear(chatId);
|
|
33163
|
+
messageBatchOwner.routeMemory.dropChat(chatId);
|
|
33164
|
+
routeMemoryScopes.delete(chatId);
|
|
33165
|
+
}
|
|
33166
|
+
},
|
|
32748
33167
|
onTransition: stageCommittedMembershipMessage,
|
|
32749
33168
|
onRemoved: (removedChatId, _epochVersion, retiredEntry) => {
|
|
32750
33169
|
const applied = chatListOwner.applyMembershipRemoval(removedChatId, retiredEntry);
|
|
@@ -32801,7 +33220,7 @@ function createChatSession({
|
|
|
32801
33220
|
openNotesChat,
|
|
32802
33221
|
openNewChat,
|
|
32803
33222
|
directAdmissionForPeer: directAdmissionForPeer2,
|
|
32804
|
-
canStartDirectChat
|
|
33223
|
+
canStartDirectChat,
|
|
32805
33224
|
canInviteToGroup: canInviteToGroup2,
|
|
32806
33225
|
createGroupChat: createGroupChat2,
|
|
32807
33226
|
addChatMembers,
|
|
@@ -33247,7 +33666,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
33247
33666
|
let expiryTimer = null;
|
|
33248
33667
|
let command = null;
|
|
33249
33668
|
let authenticating = null;
|
|
33250
|
-
let snapshot = Object.freeze({ visibility: null, ready: false, pending: false, error: null, ...UNKNOWN_PRESENCE });
|
|
33669
|
+
let snapshot = Object.freeze({ visibility: null, ready: false, pending: false, pendingVisibility: null, error: null, ...UNKNOWN_PRESENCE });
|
|
33251
33670
|
function topic() {
|
|
33252
33671
|
return sources.uid ? derivePresenceTopic(realm2, sources.uid) : null;
|
|
33253
33672
|
}
|
|
@@ -33255,7 +33674,13 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
33255
33674
|
return Boolean(channel && challenge && policy && sources.enabled && !closed);
|
|
33256
33675
|
}
|
|
33257
33676
|
function publish(patch = {}) {
|
|
33258
|
-
const next = {
|
|
33677
|
+
const next = {
|
|
33678
|
+
...snapshot,
|
|
33679
|
+
...patch,
|
|
33680
|
+
ready: isReady(),
|
|
33681
|
+
pending: Boolean(command),
|
|
33682
|
+
pendingVisibility: command?.policy.visibility ?? null
|
|
33683
|
+
};
|
|
33259
33684
|
if (Object.keys(next).every((key) => Object.is(next[key], snapshot[key])))
|
|
33260
33685
|
return;
|
|
33261
33686
|
snapshot = Object.freeze(next);
|
|
@@ -33311,7 +33736,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
33311
33736
|
const pending = command;
|
|
33312
33737
|
command = null;
|
|
33313
33738
|
clearTimeout(pending.timer);
|
|
33314
|
-
publish({
|
|
33739
|
+
publish({ error });
|
|
33315
33740
|
if (error)
|
|
33316
33741
|
pending.reject(error);
|
|
33317
33742
|
else
|
|
@@ -33551,12 +33976,14 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
33551
33976
|
listener();
|
|
33552
33977
|
}
|
|
33553
33978
|
function update(next) {
|
|
33554
|
-
const
|
|
33979
|
+
const accountChanged = sources.uid !== next.uid;
|
|
33980
|
+
const identityChanged = accountChanged || sources.identity?.publicKey !== next.identity?.publicKey;
|
|
33555
33981
|
if (identityChanged) {
|
|
33556
33982
|
stop();
|
|
33557
33983
|
policy = null;
|
|
33558
33984
|
records.clear();
|
|
33559
|
-
|
|
33985
|
+
if (accountChanged)
|
|
33986
|
+
peers.clear();
|
|
33560
33987
|
publish({ visibility: null, error: null, ...UNKNOWN_PRESENCE });
|
|
33561
33988
|
}
|
|
33562
33989
|
sources = next;
|
|
@@ -33586,7 +34013,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
33586
34013
|
reject,
|
|
33587
34014
|
timer: setTimeout(() => finishCommand(new Error("could not confirm activity setting; reconnect to check")), COMMAND_TIMEOUT_MS)
|
|
33588
34015
|
};
|
|
33589
|
-
publish({
|
|
34016
|
+
publish({ error: null });
|
|
33590
34017
|
if (!channel.send(presenceShard(topic()), { type: "policy", expectedRevision: policy.revision, policy: nextPolicy })) {
|
|
33591
34018
|
finishCommand(new Error("could not update activity while offline"));
|
|
33592
34019
|
}
|
|
@@ -36938,6 +37365,7 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
36938
37365
|
let profilesLoading = false;
|
|
36939
37366
|
let hydratedCacheKey = "";
|
|
36940
37367
|
let avatarHydrateKey = "";
|
|
37368
|
+
let avatarPublishSession = null;
|
|
36941
37369
|
let profilesReady = false;
|
|
36942
37370
|
let blockedPeersReady = false;
|
|
36943
37371
|
let discoveredPeerUids = new Set;
|
|
@@ -37348,12 +37776,21 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
37348
37776
|
const session = lifecycle;
|
|
37349
37777
|
const startedAt = Date.now();
|
|
37350
37778
|
const requested = walletPKs.length + chatPeerPKs.length;
|
|
37351
|
-
hydrateCachedAvatars(walletPKs, chatPeerPKs
|
|
37779
|
+
hydrateCachedAvatars(walletPKs, chatPeerPKs, () => {
|
|
37780
|
+
if (!isCurrent(session) || avatarPublishSession === session)
|
|
37781
|
+
return;
|
|
37782
|
+
avatarPublishSession = session;
|
|
37783
|
+
queueMicrotask(() => {
|
|
37784
|
+
if (avatarPublishSession !== session)
|
|
37785
|
+
return;
|
|
37786
|
+
avatarPublishSession = null;
|
|
37787
|
+
if (isCurrent(session))
|
|
37788
|
+
rebuild({ base: true, persist: false });
|
|
37789
|
+
});
|
|
37790
|
+
}).then((changed) => {
|
|
37352
37791
|
if (!isCurrent(session))
|
|
37353
37792
|
return;
|
|
37354
37793
|
markDiag(diag, "peers.avatars.hydrate", { requested, changed, elapsedMs: Date.now() - startedAt });
|
|
37355
|
-
if (changed)
|
|
37356
|
-
rebuild({ base: true });
|
|
37357
37794
|
}).catch((error) => {
|
|
37358
37795
|
if (!isCurrent(session))
|
|
37359
37796
|
return;
|
|
@@ -37933,7 +38370,7 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
|
|
|
37933
38370
|
function uniqueLookupKeys(keys) {
|
|
37934
38371
|
return uniqueValues(keys);
|
|
37935
38372
|
}
|
|
37936
|
-
async function hydrateCachedAvatars(walletPKs, chatPKs) {
|
|
38373
|
+
async function hydrateCachedAvatars(walletPKs, chatPKs, onHydrated) {
|
|
37937
38374
|
if (typeof avatarCache?.read !== "function")
|
|
37938
38375
|
return false;
|
|
37939
38376
|
const profiles = new Map;
|
|
@@ -37952,21 +38389,18 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
|
|
|
37952
38389
|
queue(key, "chat");
|
|
37953
38390
|
if (!profiles.size)
|
|
37954
38391
|
return false;
|
|
37955
|
-
const cached = await Promise.all(Array.from(profiles.values()).map(async ({ uid, version }) => ({
|
|
37956
|
-
avatar: await readCachedAvatar(uid, version),
|
|
37957
|
-
uid,
|
|
37958
|
-
version
|
|
37959
|
-
})));
|
|
37960
38392
|
let changed = false;
|
|
37961
|
-
|
|
37962
|
-
|
|
37963
|
-
|
|
37964
|
-
|
|
37965
|
-
|
|
37966
|
-
|
|
37967
|
-
|
|
38393
|
+
await Promise.all(Array.from(profiles.values()).map(async ({ uid, version }) => {
|
|
38394
|
+
const avatar = await readCachedAvatar(uid, version);
|
|
38395
|
+
if (!avatar)
|
|
38396
|
+
return;
|
|
38397
|
+
const current = profileCache.get(uid);
|
|
38398
|
+
if (!current || current.avatarVersion !== version || current.avatar === avatar)
|
|
38399
|
+
return;
|
|
38400
|
+
storeProfile({ ...current, avatar });
|
|
37968
38401
|
changed = true;
|
|
37969
|
-
|
|
38402
|
+
onHydrated?.();
|
|
38403
|
+
}));
|
|
37970
38404
|
return changed;
|
|
37971
38405
|
}
|
|
37972
38406
|
function needsAvatarResolve(profile) {
|
|
@@ -40556,6 +40990,7 @@ function createPayment({
|
|
|
40556
40990
|
throw new Error("bitcoin payment must be reviewed before sending");
|
|
40557
40991
|
}
|
|
40558
40992
|
const payment = paymentInput?.state === PAYMENT_STATE_PREPARED ? normalizePreparedPayment(paymentInput, wallet, network) : await preparePayment(paymentInput);
|
|
40993
|
+
assertReady();
|
|
40559
40994
|
const startedAt = Date.now();
|
|
40560
40995
|
const diagKind = payment.kind;
|
|
40561
40996
|
markDiag(diag, "wallet.payment.start", { kind: diagKind });
|