@glyphteck/veyl 0.59.0 → 0.60.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 +2175 -1764
- package/dist/auth.js +13 -5
- package/dist/cli.js +1226 -654
- package/dist/index.js +1209 -653
- package/docs/api.md +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6450,7 +6450,7 @@ var init_notifications = __esm(() => {
|
|
|
6450
6450
|
});
|
|
6451
6451
|
|
|
6452
6452
|
// ../../core/config.js
|
|
6453
|
-
var MS_PER_SECOND = 1000, MINUTE_MS, HOUR_MS, DAY_MS, KIB_BYTES = 1024, MIB_BYTES, SATS_PER_BITCOIN = 100000000n, USERNAME_MAX_CHARS = 12, PASSWORD_MIN_CHARS = 15, PASSWORD_MAX_CHARS = 64, PASSWORD_MAX_BYTES, AUTOLOCK_MIN_MINUTES = 1, AUTOLOCK_MAX_MINUTES = 60, LOCAL_CACHE_WRITE_DELAY_MS = 350, LOCAL_MEDIA_CACHE_MAX_BYTES, LOCAL_MEDIA_CACHE_MAX_ITEMS = 1000, LOCAL_MEDIA_ACCESS_TOUCH_MIN_MS, LOCAL_PROFILE_CACHE_MAX_ITEMS = 500, LOCAL_PROFILE_CACHE_MAX_AGE_MS, LOCAL_CHAT_CACHE_MAX_ITEMS = 1000, LOCAL_CHAT_MESSAGE_CACHE_MAX_VISIBLE = 1000, LOCAL_CHAT_MESSAGE_CACHE_WRITE_DELAY_MS = 2500, LOCAL_AVATAR_CACHE_MAX_BYTES, LOCAL_AVATAR_CACHE_MAX_AGE_MS, AVATAR_IMAGE_MAX_BYTES, CHAT_AVATAR_IMAGE_MAX_BYTES, CHAT_AVATAR_REF_MAX_CHARS, CHAT_MESSAGE_FILE_CACHE_MAX_BYTES, IDLE_CALLBACK_MIN_TIMEOUT_MS = 50, ATTACHMENT_CACHE_IDLE_TIMEOUT_MS = 2500, ATTACHMENT_CACHE_FALLBACK_DELAY_MS = 250, CHAT_UNSAVED_TTL_DAYS = 21, CHAT_UNSAVED_TTL_MS, CHAT_AFTER_SEEN_MS, CHAT_MEDIA_TTL_DAYS, CHAT_MEDIA_TTL_MS, CHAT_UPLOAD_MAX_BYTES, CHAT_AUDIO_TRANSCODE_BITRATE_BPS = 128000, CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS = 3000000, CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS, CHAT_MESSAGE_BATCH_SIZE = 40, CHAT_MESSAGE_QUERY_MAX_DOCS = 120, CHAT_TTL_CLIENT_DELETE_GRACE_MS, CHAT_TTL_DELETE_CHUNK_SIZE = 40, CHAT_DELETE_SCAN_BATCH_SIZE = 200, CHAT_DELETE_WRITE_BATCH_SIZE = 400, CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500, CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250, CHAT_LIST_PAGE_SIZE = 20, CHAT_LIST_LIVE_COUNT, CHAT_INBOX_PING_PAGE_SIZE = 25, CHAT_INBOX_CURSOR_OVERLAP_MS, CHAT_LIST_CACHE_WRITE_DELAY_MS = 1500, CHAT_LIST_SNAPSHOT_COALESCE_MS = 80, CHAT_LIST_LISTENER_RETRY_MS = 1000, CHAT_TOP_WARM_COUNT = 0, CHAT_EAGER_WARM_COUNT = 0, CHAT_WARM_DELAY_MS = 3000, CHAT_WARM_BATCH_SIZE, CHAT_MESSAGE_VIEW_CACHE_SIZE = 30, CHAT_MEDIA_WARM_MESSAGES_PER_CHAT, CHAT_MEDIA_WARM_START_DELAY_MS = 600, CHAT_MEDIA_WARM_STEP_DELAY_MS = 120, CHAT_MEDIA_WARM_TYPES, CHAT_MEDIA_WARM_MAX_BYTES = 0, CHAT_READ_WRITE_INTERVAL_MS, CHAT_READ_WRITE_MAX_WAIT_MS, CHAT_LIVE_PING_INTERVAL_MS, CHAT_LIVE_SWEEP_INTERVAL_MS, CHAT_LIVE_STALE_AFTER_MS, CHAT_LIVE_RECONNECT_MAX_MS, CHAT_LIVE_TYPING_IDLE_MS, CHAT_LIVE_TYPING_RENEW_MS, CHAT_LIVE_COMPOSITION_HANDOFF_MS, CHAT_LIVE_READ_SEND_INTERVAL_MS = 120, CHAT_LIVE_READ_SEND_MAX_WAIT_MS = 500, CHAT_LIVE_READ_WRITE_INTERVAL_MS, CHAT_LIVE_READ_WRITE_MAX_WAIT_MS, CHAT_SEND_QUEUE_RATE_LIMIT_COUNT = 12, CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS, CHAT_SETTINGS_BODY_MAX_BYTES, CHAT_MANIFEST_MAX_BYTES, CHAT_RECEIPT_MAX_MEMBERS = 32, CHAT_ORDINARY_DELIVERY_MAX_MEMBERS = 128, CHAT_MAX_MEMBERS = 256, CHAT_MAX_TEXT_CHARS = 2048, CHAT_MAX_REACTIONS, SEARCH_DEBOUNCE_MS = 300, SEARCH_USERNAME_LIMIT = 15, SEARCH_ROLE_LIMIT = 15, RECENT_PEER_REFRESH_LIMIT = 50, RECENT_PEER_REFRESH_DELAY_MS = 250, RECENT_PEER_REFRESH_INTERVAL_MS, RECENT_PEER_REFRESH_THROTTLE_MS = 120, BAN_REFRESH_GRACE_MS = 50, REQUEST_MONEY_MAX_SATS, WALLET_TRANSFER_POLL_MS, WALLET_ACTIVE_TRANSFER_REFRESH_MS, WALLET_IDLE_ACTIVE_TRANSFER_REFRESH_MS, WALLET_VISIBLE_TRANSFER_DISCOVERY_WINDOW_MS, WALLET_ACTIVE_TRANSFER_EMPTY_SCAN_LIMIT = 8, WALLET_CACHE_HYDRATE_DELAY_MS = 0, WALLET_BOOT_REFRESH_DELAY_MS = 500, WALLET_BOOT_CACHED_REFRESH_DELAY_MS = 2500, WALLET_REGTEST_DEPOSIT_CLAIM_POLL_MS, WALLET_MAINNET_DEPOSIT_CLAIM_POLL_MS, WALLET_BALANCE_EVENT_COALESCE_MS, WALLET_INCOMING_UPDATE_COALESCE_MS = 250, WALLET_AUTO_CLAIM_MAX_FEE_SATS = 5000, WALLET_CLAIM_PAGE_SIZE = 100, WALLET_PENDING_TRANSFER_CLAIM_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_STATUS_BATCH_SIZE = 2, WALLET_PENDING_TRANSFER_COLD_REFRESH_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_COLD_REFRESH_INTERVAL_MS, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_QUEUE = 3, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_QUEUE = 10, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_SIZE = 2, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_SIZE = 3, WALLET_PENDING_TRANSFER_SLOW_CLAIM_MS, WALLET_PENDING_TRANSFER_CLAIM_COOLDOWN_MS, WALLET_SDK_BACKGROUND_QUIET_MS, WALLET_RECENT_TRANSFER_LIMIT = 100, WALLET_TRANSFER_PAGE_LIMIT = 100, WALLET_TRANSFER_FETCH_THROTTLE_MS = 150, WALLET_PENDING_TRANSFER_CLAIM_RETRY_MS, WALLET_SENT_TRANSFER_REFRESH_DELAY_MS, WALLET_PENDING_TRANSFER_BOOT_GRACE_MS, WALLET_PENDING_TRANSFER_HOT_AGE_MS, WALLET_PENDING_TRANSFER_WARM_AGE_MS, WALLET_PENDING_TRANSFER_WARM_RETRY_MS, WALLET_PENDING_TRANSFER_STALE_RETRY_MS, WALLET_PENDING_TRANSFER_STUCK_RETRY_MS, WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS, WALLET_TRANSFER_CACHE_WRITE_DELAY_MS;
|
|
6453
|
+
var MS_PER_SECOND = 1000, MINUTE_MS, HOUR_MS, DAY_MS, KIB_BYTES = 1024, MIB_BYTES, SATS_PER_BITCOIN = 100000000n, USERNAME_MAX_CHARS = 12, PASSWORD_MIN_CHARS = 15, PASSWORD_MAX_CHARS = 64, PASSWORD_MAX_BYTES, AUTOLOCK_MIN_MINUTES = 1, AUTOLOCK_MAX_MINUTES = 60, LOCAL_CACHE_WRITE_DELAY_MS = 350, LOCAL_MEDIA_CACHE_MAX_BYTES, LOCAL_MEDIA_CACHE_MAX_ITEMS = 1000, LOCAL_MEDIA_ACCESS_TOUCH_MIN_MS, LOCAL_PROFILE_CACHE_MAX_ITEMS = 500, LOCAL_PROFILE_CACHE_MAX_AGE_MS, LOCAL_CHAT_CACHE_MAX_ITEMS = 1000, LOCAL_CHAT_MESSAGE_CACHE_MAX_VISIBLE = 1000, LOCAL_CHAT_MESSAGE_CACHE_WRITE_DELAY_MS = 2500, LOCAL_AVATAR_CACHE_MAX_BYTES, LOCAL_AVATAR_CACHE_MAX_AGE_MS, AVATAR_IMAGE_MAX_BYTES, AVATAR_IMAGE_QUALITY_ATTEMPTS, CHAT_AVATAR_IMAGE_MAX_BYTES, CHAT_AVATAR_REF_MAX_CHARS = 256, CHAT_MESSAGE_FILE_CACHE_MAX_BYTES, IDLE_CALLBACK_MIN_TIMEOUT_MS = 50, ATTACHMENT_CACHE_IDLE_TIMEOUT_MS = 2500, ATTACHMENT_CACHE_FALLBACK_DELAY_MS = 250, CHAT_UNSAVED_TTL_DAYS = 21, CHAT_UNSAVED_TTL_MS, CHAT_AFTER_SEEN_MS, CHAT_MEDIA_TTL_DAYS, CHAT_MEDIA_TTL_MS, CHAT_UPLOAD_MAX_BYTES, CHAT_AUDIO_TRANSCODE_BITRATE_BPS = 128000, CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS = 3000000, CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS, CHAT_MESSAGE_BATCH_SIZE = 40, CHAT_MESSAGE_QUERY_MAX_DOCS = 120, CHAT_TTL_CLIENT_DELETE_GRACE_MS, CHAT_TTL_DELETE_CHUNK_SIZE = 40, CHAT_DELETE_SCAN_BATCH_SIZE = 200, CHAT_DELETE_WRITE_BATCH_SIZE = 400, CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500, CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250, CHAT_LIST_PAGE_SIZE = 20, CHAT_LIST_LIVE_COUNT, CHAT_INBOX_PING_PAGE_SIZE = 25, CHAT_INBOX_CURSOR_OVERLAP_MS, CHAT_LIST_CACHE_WRITE_DELAY_MS = 1500, CHAT_LIST_SNAPSHOT_COALESCE_MS = 80, CHAT_LIST_LISTENER_RETRY_MS = 1000, CHAT_TOP_WARM_COUNT = 0, CHAT_EAGER_WARM_COUNT = 0, CHAT_WARM_DELAY_MS = 3000, CHAT_WARM_BATCH_SIZE, CHAT_MESSAGE_VIEW_CACHE_SIZE = 30, CHAT_MEDIA_WARM_MESSAGES_PER_CHAT, CHAT_MEDIA_WARM_START_DELAY_MS = 600, CHAT_MEDIA_WARM_STEP_DELAY_MS = 120, CHAT_MEDIA_WARM_TYPES, CHAT_MEDIA_WARM_MAX_BYTES = 0, CHAT_READ_WRITE_INTERVAL_MS, CHAT_READ_WRITE_MAX_WAIT_MS, CHAT_LIVE_PING_INTERVAL_MS, CHAT_LIVE_SWEEP_INTERVAL_MS, CHAT_LIVE_STALE_AFTER_MS, CHAT_LIVE_RECONNECT_MAX_MS, CHAT_LIVE_TYPING_IDLE_MS, CHAT_LIVE_TYPING_RENEW_MS, CHAT_LIVE_COMPOSITION_HANDOFF_MS, CHAT_LIVE_READ_SEND_INTERVAL_MS = 120, CHAT_LIVE_READ_SEND_MAX_WAIT_MS = 500, CHAT_LIVE_READ_WRITE_INTERVAL_MS, CHAT_LIVE_READ_WRITE_MAX_WAIT_MS, CHAT_SEND_QUEUE_RATE_LIMIT_COUNT = 12, CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS, CHAT_SETTINGS_BODY_MAX_BYTES, CHAT_MANIFEST_MAX_BYTES, CHAT_RECEIPT_MAX_MEMBERS = 32, CHAT_ORDINARY_DELIVERY_MAX_MEMBERS = 128, CHAT_MAX_MEMBERS = 256, CHAT_MAX_TEXT_CHARS = 2048, CHAT_TITLE_MAX_CHARS = 32, CHAT_MAX_REACTIONS, SEARCH_DEBOUNCE_MS = 300, SEARCH_USERNAME_LIMIT = 15, SEARCH_ROLE_LIMIT = 15, RECENT_PEER_REFRESH_LIMIT = 50, RECENT_PEER_REFRESH_DELAY_MS = 250, RECENT_PEER_REFRESH_INTERVAL_MS, RECENT_PEER_REFRESH_THROTTLE_MS = 120, BAN_REFRESH_GRACE_MS = 50, REQUEST_MONEY_MAX_SATS, WALLET_TRANSFER_POLL_MS, WALLET_ACTIVE_TRANSFER_REFRESH_MS, WALLET_IDLE_ACTIVE_TRANSFER_REFRESH_MS, WALLET_VISIBLE_TRANSFER_DISCOVERY_WINDOW_MS, WALLET_ACTIVE_TRANSFER_EMPTY_SCAN_LIMIT = 8, WALLET_CACHE_HYDRATE_DELAY_MS = 0, WALLET_BOOT_REFRESH_DELAY_MS = 500, WALLET_BOOT_CACHED_REFRESH_DELAY_MS = 2500, WALLET_REGTEST_DEPOSIT_CLAIM_POLL_MS, WALLET_MAINNET_DEPOSIT_CLAIM_POLL_MS, WALLET_BALANCE_EVENT_COALESCE_MS, WALLET_INCOMING_UPDATE_COALESCE_MS = 250, WALLET_AUTO_CLAIM_MAX_FEE_SATS = 5000, WALLET_CLAIM_PAGE_SIZE = 100, WALLET_PENDING_TRANSFER_CLAIM_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_STATUS_BATCH_SIZE = 2, WALLET_PENDING_TRANSFER_COLD_REFRESH_BATCH_SIZE = 50, WALLET_PENDING_TRANSFER_COLD_REFRESH_INTERVAL_MS, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_QUEUE = 3, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_QUEUE = 10, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_SIZE = 2, WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_SIZE = 3, WALLET_PENDING_TRANSFER_SLOW_CLAIM_MS, WALLET_PENDING_TRANSFER_CLAIM_COOLDOWN_MS, WALLET_SDK_BACKGROUND_QUIET_MS, WALLET_RECENT_TRANSFER_LIMIT = 100, WALLET_TRANSFER_PAGE_LIMIT = 100, WALLET_TRANSFER_FETCH_THROTTLE_MS = 150, WALLET_PENDING_TRANSFER_CLAIM_RETRY_MS, WALLET_SENT_TRANSFER_REFRESH_DELAY_MS, WALLET_PENDING_TRANSFER_BOOT_GRACE_MS, WALLET_PENDING_TRANSFER_HOT_AGE_MS, WALLET_PENDING_TRANSFER_WARM_AGE_MS, WALLET_PENDING_TRANSFER_WARM_RETRY_MS, WALLET_PENDING_TRANSFER_STALE_RETRY_MS, WALLET_PENDING_TRANSFER_STUCK_RETRY_MS, WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS, WALLET_TRANSFER_CACHE_WRITE_DELAY_MS;
|
|
6454
6454
|
var init_config = __esm(() => {
|
|
6455
6455
|
MINUTE_MS = 60 * MS_PER_SECOND;
|
|
6456
6456
|
HOUR_MS = 60 * MINUTE_MS;
|
|
@@ -6463,8 +6463,8 @@ var init_config = __esm(() => {
|
|
|
6463
6463
|
LOCAL_AVATAR_CACHE_MAX_BYTES = 128 * MIB_BYTES;
|
|
6464
6464
|
LOCAL_AVATAR_CACHE_MAX_AGE_MS = 30 * DAY_MS;
|
|
6465
6465
|
AVATAR_IMAGE_MAX_BYTES = 256 * KIB_BYTES;
|
|
6466
|
-
|
|
6467
|
-
|
|
6466
|
+
AVATAR_IMAGE_QUALITY_ATTEMPTS = Object.freeze([0.92, 0.84, 0.76, 0.68]);
|
|
6467
|
+
CHAT_AVATAR_IMAGE_MAX_BYTES = AVATAR_IMAGE_MAX_BYTES;
|
|
6468
6468
|
CHAT_MESSAGE_FILE_CACHE_MAX_BYTES = 64 * MIB_BYTES;
|
|
6469
6469
|
CHAT_UNSAVED_TTL_MS = CHAT_UNSAVED_TTL_DAYS * DAY_MS;
|
|
6470
6470
|
CHAT_AFTER_SEEN_MS = DAY_MS;
|
|
@@ -9424,6 +9424,430 @@ var init_user = __esm(() => {
|
|
|
9424
9424
|
};
|
|
9425
9425
|
});
|
|
9426
9426
|
|
|
9427
|
+
// ../../core/crypto/file.js
|
|
9428
|
+
function createFileKey() {
|
|
9429
|
+
return randomBytes3(32);
|
|
9430
|
+
}
|
|
9431
|
+
function encodeFileKey(key) {
|
|
9432
|
+
return toHex(toBytes32(key, "file key"));
|
|
9433
|
+
}
|
|
9434
|
+
function decodeFileKey(key) {
|
|
9435
|
+
return toBytes32(key, "file key");
|
|
9436
|
+
}
|
|
9437
|
+
function getFileAadForPath(scope) {
|
|
9438
|
+
if (!scope) {
|
|
9439
|
+
throw new Error("file scope required");
|
|
9440
|
+
}
|
|
9441
|
+
return encodeScope(FILE_SCOPE, [scope]);
|
|
9442
|
+
}
|
|
9443
|
+
function getSubtleCrypto() {
|
|
9444
|
+
const subtle = globalThis.crypto?.subtle;
|
|
9445
|
+
if (!subtle) {
|
|
9446
|
+
throw new Error("WebCrypto AES-GCM unavailable");
|
|
9447
|
+
}
|
|
9448
|
+
return subtle;
|
|
9449
|
+
}
|
|
9450
|
+
async function importAesKey(key, usages) {
|
|
9451
|
+
return getSubtleCrypto().importKey("raw", toBytes32(key, "file key"), { name: "AES-GCM" }, false, usages);
|
|
9452
|
+
}
|
|
9453
|
+
async function sealFile(key, bytes, scope) {
|
|
9454
|
+
const fileKey = new Uint8Array(toBytes32(key, "file key"));
|
|
9455
|
+
try {
|
|
9456
|
+
const cryptoKey = await importAesKey(fileKey, ["encrypt"]);
|
|
9457
|
+
const iv = randomBytes3(FILE_IV_BYTES);
|
|
9458
|
+
const ct = await getSubtleCrypto().encrypt({
|
|
9459
|
+
name: "AES-GCM",
|
|
9460
|
+
iv,
|
|
9461
|
+
tagLength: FILE_TAG_BYTES * 8,
|
|
9462
|
+
additionalData: getFileAadForPath(scope)
|
|
9463
|
+
}, cryptoKey, toBytes(bytes, "plaintext"));
|
|
9464
|
+
return packBodyData(iv, new Uint8Array(ct));
|
|
9465
|
+
} finally {
|
|
9466
|
+
cleanBytes(fileKey);
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
9469
|
+
async function openFileForPath(key, body, scope) {
|
|
9470
|
+
const fileKey = new Uint8Array(toBytes32(key, "file key"));
|
|
9471
|
+
try {
|
|
9472
|
+
const cryptoKey = await importAesKey(fileKey, ["decrypt"]);
|
|
9473
|
+
const { nonce, ct } = unpackBodyData(body, FILE_IV_BYTES);
|
|
9474
|
+
const pt = await getSubtleCrypto().decrypt({
|
|
9475
|
+
name: "AES-GCM",
|
|
9476
|
+
iv: nonce,
|
|
9477
|
+
tagLength: FILE_TAG_BYTES * 8,
|
|
9478
|
+
additionalData: getFileAadForPath(scope)
|
|
9479
|
+
}, cryptoKey, ct);
|
|
9480
|
+
return new Uint8Array(pt);
|
|
9481
|
+
} finally {
|
|
9482
|
+
cleanBytes(fileKey);
|
|
9483
|
+
}
|
|
9484
|
+
}
|
|
9485
|
+
var FILE_SCOPE = "file-body", FILE_IV_BYTES = 12, FILE_TAG_BYTES = 16;
|
|
9486
|
+
var init_file = __esm(() => {
|
|
9487
|
+
init_core();
|
|
9488
|
+
init_kdf();
|
|
9489
|
+
init_pack();
|
|
9490
|
+
"use client";
|
|
9491
|
+
});
|
|
9492
|
+
|
|
9493
|
+
// ../../core/chat/filepayload.js
|
|
9494
|
+
function cleanMediaEpochId(value) {
|
|
9495
|
+
const epochId = String(value || "").trim();
|
|
9496
|
+
if (!new RegExp(`^${CHAT_ID_PATTERN}$`).test(epochId)) {
|
|
9497
|
+
throw new Error("invalid media epoch id");
|
|
9498
|
+
}
|
|
9499
|
+
return epochId.toLowerCase();
|
|
9500
|
+
}
|
|
9501
|
+
function cleanMediaId(value) {
|
|
9502
|
+
const mediaId = String(value || "").trim();
|
|
9503
|
+
if (!new RegExp(`^${MEDIA_ID_PATTERN}$`).test(mediaId)) {
|
|
9504
|
+
throw new Error("invalid media id");
|
|
9505
|
+
}
|
|
9506
|
+
return mediaId.toLowerCase();
|
|
9507
|
+
}
|
|
9508
|
+
function cleanSharedMediaId(value) {
|
|
9509
|
+
const sharedId = String(value || "").trim();
|
|
9510
|
+
if (!new RegExp(`^${SHARED_MEDIA_ID_PATTERN}$`).test(sharedId)) {
|
|
9511
|
+
throw new Error("invalid shared media id");
|
|
9512
|
+
}
|
|
9513
|
+
return sharedId.toLowerCase();
|
|
9514
|
+
}
|
|
9515
|
+
function makeChatMediaId() {
|
|
9516
|
+
return toHex(randomBytes3(16));
|
|
9517
|
+
}
|
|
9518
|
+
function mediaFilePath(epochId, mediaId) {
|
|
9519
|
+
const nextEpochId = cleanMediaEpochId(epochId);
|
|
9520
|
+
const nextMediaId = cleanMediaId(mediaId);
|
|
9521
|
+
return `${CHAT_MEDIA_ROOT}/${nextEpochId}/${nextMediaId}`;
|
|
9522
|
+
}
|
|
9523
|
+
function sharedMediaFilePath(sharedId) {
|
|
9524
|
+
const nextSharedId = cleanSharedMediaId(sharedId);
|
|
9525
|
+
return `${SHARED_MEDIA_ROOT}/${nextSharedId}`;
|
|
9526
|
+
}
|
|
9527
|
+
function makeSharedMediaId() {
|
|
9528
|
+
return toHex(randomBytes3(16));
|
|
9529
|
+
}
|
|
9530
|
+
function getMediaFileRef(path) {
|
|
9531
|
+
const value = String(path || "").trim();
|
|
9532
|
+
const chatMatch = value.match(CHAT_MEDIA_FILE_PATTERN);
|
|
9533
|
+
if (chatMatch?.[1] && chatMatch?.[2]) {
|
|
9534
|
+
return {
|
|
9535
|
+
type: "chat",
|
|
9536
|
+
epochId: chatMatch[1].toLowerCase(),
|
|
9537
|
+
mediaId: chatMatch[2].toLowerCase()
|
|
9538
|
+
};
|
|
9539
|
+
}
|
|
9540
|
+
const sharedMatch = value.match(SHARED_MEDIA_FILE_PATTERN);
|
|
9541
|
+
if (sharedMatch?.[1]) {
|
|
9542
|
+
return {
|
|
9543
|
+
type: "shared",
|
|
9544
|
+
sharedId: sharedMatch[1].toLowerCase()
|
|
9545
|
+
};
|
|
9546
|
+
}
|
|
9547
|
+
throw new Error("invalid media file path");
|
|
9548
|
+
}
|
|
9549
|
+
function getChatMediaFileRef(path) {
|
|
9550
|
+
const ref = getMediaFileRef(path);
|
|
9551
|
+
if (ref?.type !== "chat") {
|
|
9552
|
+
throw new Error("invalid media file path");
|
|
9553
|
+
}
|
|
9554
|
+
return ref;
|
|
9555
|
+
}
|
|
9556
|
+
function getSharedMediaFileRef(path) {
|
|
9557
|
+
const ref = getMediaFileRef(path);
|
|
9558
|
+
if (ref?.type !== "shared") {
|
|
9559
|
+
throw new Error("invalid shared media file path");
|
|
9560
|
+
}
|
|
9561
|
+
return ref;
|
|
9562
|
+
}
|
|
9563
|
+
function uploadByteLength(value) {
|
|
9564
|
+
if (Number.isFinite(value?.byteLength)) {
|
|
9565
|
+
return value.byteLength;
|
|
9566
|
+
}
|
|
9567
|
+
if (Number.isFinite(value?.size)) {
|
|
9568
|
+
return value.size;
|
|
9569
|
+
}
|
|
9570
|
+
if (Number.isFinite(value)) {
|
|
9571
|
+
return value;
|
|
9572
|
+
}
|
|
9573
|
+
return null;
|
|
9574
|
+
}
|
|
9575
|
+
function makeChatUploadTooLargeError(bytes, maxBytes = MAX_CHAT_UPLOAD_BYTES) {
|
|
9576
|
+
const error = new Error("upload too large");
|
|
9577
|
+
error.code = "upload-too-large";
|
|
9578
|
+
error.maxBytes = maxBytes;
|
|
9579
|
+
if (Number.isFinite(bytes)) {
|
|
9580
|
+
error.bytes = bytes;
|
|
9581
|
+
}
|
|
9582
|
+
return error;
|
|
9583
|
+
}
|
|
9584
|
+
function assertChatUploadByteSize(bytes, maxBytes = MAX_CHAT_UPLOAD_BYTES) {
|
|
9585
|
+
const length = uploadByteLength(bytes);
|
|
9586
|
+
if (!Number.isFinite(length)) {
|
|
9587
|
+
throw new Error("upload bytes required");
|
|
9588
|
+
}
|
|
9589
|
+
if (length <= 0 || length > maxBytes) {
|
|
9590
|
+
throw makeChatUploadTooLargeError(length, maxBytes);
|
|
9591
|
+
}
|
|
9592
|
+
return length;
|
|
9593
|
+
}
|
|
9594
|
+
async function toUploadBytes(data) {
|
|
9595
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
9596
|
+
if (typeof data.arrayBuffer === "function") {
|
|
9597
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
9598
|
+
}
|
|
9599
|
+
if (typeof FileReader !== "undefined") {
|
|
9600
|
+
return new Promise((resolve, reject) => {
|
|
9601
|
+
const reader = new FileReader;
|
|
9602
|
+
reader.onload = () => resolve(new Uint8Array(reader.result));
|
|
9603
|
+
reader.onerror = () => reject(reader.error || new Error("blob read failed"));
|
|
9604
|
+
reader.readAsArrayBuffer(data);
|
|
9605
|
+
});
|
|
9606
|
+
}
|
|
9607
|
+
if (typeof Response !== "undefined") {
|
|
9608
|
+
return new Uint8Array(await new Response(data).arrayBuffer());
|
|
9609
|
+
}
|
|
9610
|
+
}
|
|
9611
|
+
if (typeof data?.arrayBuffer === "function") {
|
|
9612
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
9613
|
+
}
|
|
9614
|
+
return toBytes(data, "upload bytes");
|
|
9615
|
+
}
|
|
9616
|
+
async function makeChatFileUploadPayload(epochId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
9617
|
+
const nextEpochId = cleanMediaEpochId(epochId);
|
|
9618
|
+
const mediaId = makeChatMediaId();
|
|
9619
|
+
const path = mediaFilePath(nextEpochId, mediaId);
|
|
9620
|
+
const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
|
|
9621
|
+
const key = createFileKey();
|
|
9622
|
+
try {
|
|
9623
|
+
const uploadBytes = await toUploadBytes(data);
|
|
9624
|
+
assertChatUploadByteSize(uploadBytes);
|
|
9625
|
+
const body = await sealFile(key, uploadBytes, path);
|
|
9626
|
+
assertChatUploadByteSize(body);
|
|
9627
|
+
return {
|
|
9628
|
+
epochId: nextEpochId,
|
|
9629
|
+
mediaId,
|
|
9630
|
+
path,
|
|
9631
|
+
body,
|
|
9632
|
+
metadata: {
|
|
9633
|
+
contentType: "application/octet-stream",
|
|
9634
|
+
cacheControl
|
|
9635
|
+
},
|
|
9636
|
+
file: {
|
|
9637
|
+
p: path,
|
|
9638
|
+
k: encodeFileKey(key),
|
|
9639
|
+
x: expiresAt
|
|
9640
|
+
}
|
|
9641
|
+
};
|
|
9642
|
+
} catch (error) {
|
|
9643
|
+
if (error && typeof error === "object") {
|
|
9644
|
+
error.path = error?.path || path;
|
|
9645
|
+
error.cid = error?.cid || cid;
|
|
9646
|
+
}
|
|
9647
|
+
throw error;
|
|
9648
|
+
} finally {
|
|
9649
|
+
cleanBytes(key);
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9652
|
+
async function makeSharedFileUploadPayload(data, { contentType = "application/octet-stream", cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
9653
|
+
const sharedId = makeSharedMediaId();
|
|
9654
|
+
const path = sharedMediaFilePath(sharedId);
|
|
9655
|
+
const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
|
|
9656
|
+
const key = createFileKey();
|
|
9657
|
+
try {
|
|
9658
|
+
const uploadBytes = await toUploadBytes(data);
|
|
9659
|
+
assertChatUploadByteSize(uploadBytes);
|
|
9660
|
+
const body = await sealFile(key, uploadBytes, path);
|
|
9661
|
+
assertChatUploadByteSize(body);
|
|
9662
|
+
return {
|
|
9663
|
+
sharedId,
|
|
9664
|
+
path,
|
|
9665
|
+
body,
|
|
9666
|
+
metadata: {
|
|
9667
|
+
contentType,
|
|
9668
|
+
cacheControl
|
|
9669
|
+
},
|
|
9670
|
+
file: {
|
|
9671
|
+
p: path,
|
|
9672
|
+
k: encodeFileKey(key),
|
|
9673
|
+
x: expiresAt
|
|
9674
|
+
}
|
|
9675
|
+
};
|
|
9676
|
+
} catch (error) {
|
|
9677
|
+
if (error && typeof error === "object") {
|
|
9678
|
+
error.path = error?.path || path;
|
|
9679
|
+
error.sharedId = error?.sharedId || sharedId;
|
|
9680
|
+
}
|
|
9681
|
+
throw error;
|
|
9682
|
+
} finally {
|
|
9683
|
+
cleanBytes(key);
|
|
9684
|
+
}
|
|
9685
|
+
}
|
|
9686
|
+
var CHAT_MEDIA_ROOT = "chatEpochs", SHARED_MEDIA_ROOT = "shared", CHAT_MEDIA_TTL_MS2, MAX_CHAT_UPLOAD_BYTES, CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS2, CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS2, CHAT_VIDEO_TRANSCODE_TOTAL_BITRATE_BPS, CHAT_ID_PATTERN = "[0-9a-fA-F]{64}", SHARED_MEDIA_ID_PATTERN = "[0-9a-fA-F]{32}", MEDIA_ID_PATTERN = "[0-9a-fA-F]{32}", CHAT_MEDIA_FILE_PATTERN, SHARED_MEDIA_FILE_PATTERN;
|
|
9687
|
+
var init_filepayload = __esm(() => {
|
|
9688
|
+
init_file();
|
|
9689
|
+
init_config();
|
|
9690
|
+
init_core();
|
|
9691
|
+
"use client";
|
|
9692
|
+
CHAT_MEDIA_TTL_MS2 = CHAT_MEDIA_TTL_MS;
|
|
9693
|
+
MAX_CHAT_UPLOAD_BYTES = CHAT_UPLOAD_MAX_BYTES;
|
|
9694
|
+
CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS2 = CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS;
|
|
9695
|
+
CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS2 = CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS;
|
|
9696
|
+
CHAT_VIDEO_TRANSCODE_TOTAL_BITRATE_BPS = CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS2 + CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS2;
|
|
9697
|
+
CHAT_MEDIA_FILE_PATTERN = new RegExp(`^${CHAT_MEDIA_ROOT}/(${CHAT_ID_PATTERN})/(${MEDIA_ID_PATTERN})$`);
|
|
9698
|
+
SHARED_MEDIA_FILE_PATTERN = new RegExp(`^${SHARED_MEDIA_ROOT}/(${SHARED_MEDIA_ID_PATTERN})$`);
|
|
9699
|
+
});
|
|
9700
|
+
|
|
9701
|
+
// ../../core/files.js
|
|
9702
|
+
function makeFileId(size = 8) {
|
|
9703
|
+
return toHex(randomBytes3(size));
|
|
9704
|
+
}
|
|
9705
|
+
function setErrorStage(error, stage, extra = {}) {
|
|
9706
|
+
if (!error || typeof error !== "object") {
|
|
9707
|
+
return error;
|
|
9708
|
+
}
|
|
9709
|
+
error.stage = error?.stage || stage;
|
|
9710
|
+
Object.assign(error, extra);
|
|
9711
|
+
return error;
|
|
9712
|
+
}
|
|
9713
|
+
async function makeChatFileUpload(epochId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
9714
|
+
try {
|
|
9715
|
+
return await makeChatFileUploadPayload(epochId, cid, data, {
|
|
9716
|
+
cacheControl
|
|
9717
|
+
});
|
|
9718
|
+
} catch (error) {
|
|
9719
|
+
throw setErrorStage(error, "encrypt", {
|
|
9720
|
+
...error?.path ? { path: error.path } : {},
|
|
9721
|
+
cid
|
|
9722
|
+
});
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
async function putChatFile(epochId, cid, data, options) {
|
|
9726
|
+
const upload = await makeChatFileUpload(epochId, cid, data, options);
|
|
9727
|
+
try {
|
|
9728
|
+
if (typeof options?.uploadChatMedia !== "function") {
|
|
9729
|
+
throw new Error("chat media upload required");
|
|
9730
|
+
}
|
|
9731
|
+
await options.uploadChatMedia(upload);
|
|
9732
|
+
return upload.file;
|
|
9733
|
+
} catch (error) {
|
|
9734
|
+
throw setErrorStage(error, "upload", { path: upload.path, cid });
|
|
9735
|
+
}
|
|
9736
|
+
}
|
|
9737
|
+
async function makeSharedFileUpload(data, { contentType = "application/octet-stream", cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
9738
|
+
try {
|
|
9739
|
+
return await makeSharedFileUploadPayload(data, {
|
|
9740
|
+
contentType,
|
|
9741
|
+
cacheControl
|
|
9742
|
+
});
|
|
9743
|
+
} catch (error) {
|
|
9744
|
+
throw setErrorStage(error, "encrypt", {
|
|
9745
|
+
...error?.path ? { path: error.path } : {}
|
|
9746
|
+
});
|
|
9747
|
+
}
|
|
9748
|
+
}
|
|
9749
|
+
async function putSharedFile(data, options = {}) {
|
|
9750
|
+
const upload = await makeSharedFileUpload(data, options);
|
|
9751
|
+
try {
|
|
9752
|
+
if (typeof options?.uploadSharedMedia !== "function") {
|
|
9753
|
+
throw new Error("shared media upload required");
|
|
9754
|
+
}
|
|
9755
|
+
await options.uploadSharedMedia(upload);
|
|
9756
|
+
return upload.file;
|
|
9757
|
+
} catch (error) {
|
|
9758
|
+
throw setErrorStage(error, "upload", { path: upload.path });
|
|
9759
|
+
}
|
|
9760
|
+
}
|
|
9761
|
+
async function readChatFile(readChatMedia, file) {
|
|
9762
|
+
let body;
|
|
9763
|
+
try {
|
|
9764
|
+
if (typeof readChatMedia !== "function") {
|
|
9765
|
+
throw new Error("chat media read required");
|
|
9766
|
+
}
|
|
9767
|
+
body = await readChatMedia(file?.p);
|
|
9768
|
+
} catch (error) {
|
|
9769
|
+
error.path = error?.path || file?.p || null;
|
|
9770
|
+
error.stage = error?.stage || "download";
|
|
9771
|
+
throw error;
|
|
9772
|
+
}
|
|
9773
|
+
try {
|
|
9774
|
+
getMediaFileRef(file?.p);
|
|
9775
|
+
const bytes = await openFileForPath(decodeFileKey(file?.k), body, file?.p);
|
|
9776
|
+
return bytes;
|
|
9777
|
+
} catch (error) {
|
|
9778
|
+
error.path = file?.p || null;
|
|
9779
|
+
error.stage = "decrypt";
|
|
9780
|
+
throw error;
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
var init_files = __esm(() => {
|
|
9784
|
+
init_file();
|
|
9785
|
+
init_core();
|
|
9786
|
+
init_filepayload();
|
|
9787
|
+
"use client";
|
|
9788
|
+
});
|
|
9789
|
+
|
|
9790
|
+
// ../../core/chat/avatar.js
|
|
9791
|
+
function chatAvatarFile(value) {
|
|
9792
|
+
const ref = typeof value === "string" ? value.trim() : "";
|
|
9793
|
+
if (!ref.startsWith(CHAT_AVATAR_REF_PREFIX) || ref.length > CHAT_AVATAR_REF_MAX_CHARS)
|
|
9794
|
+
return null;
|
|
9795
|
+
const parts = ref.split(":");
|
|
9796
|
+
const key = parts[2]?.toLowerCase();
|
|
9797
|
+
if (parts.length !== 3 || parts[0] !== "v1" || !FILE_KEY_RE.test(key))
|
|
9798
|
+
return null;
|
|
9799
|
+
let parsed;
|
|
9800
|
+
try {
|
|
9801
|
+
parsed = getChatMediaFileRef(parts[1]);
|
|
9802
|
+
} catch {
|
|
9803
|
+
return null;
|
|
9804
|
+
}
|
|
9805
|
+
return Object.freeze({
|
|
9806
|
+
t: "img",
|
|
9807
|
+
m: "image/webp",
|
|
9808
|
+
p: mediaFilePath(parsed.epochId, parsed.mediaId),
|
|
9809
|
+
k: key
|
|
9810
|
+
});
|
|
9811
|
+
}
|
|
9812
|
+
function makeChatAvatarRef(file) {
|
|
9813
|
+
let parsed;
|
|
9814
|
+
const path = cleanText(file?.p);
|
|
9815
|
+
const key = cleanText(file?.k).toLowerCase();
|
|
9816
|
+
try {
|
|
9817
|
+
parsed = getChatMediaFileRef(path);
|
|
9818
|
+
} catch {
|
|
9819
|
+
throw new Error("invalid chat avatar");
|
|
9820
|
+
}
|
|
9821
|
+
if (!FILE_KEY_RE.test(key))
|
|
9822
|
+
throw new Error("invalid chat avatar");
|
|
9823
|
+
const ref = `${CHAT_AVATAR_REF_PREFIX}${mediaFilePath(parsed.epochId, parsed.mediaId)}:${key}`;
|
|
9824
|
+
if (ref.length > CHAT_AVATAR_REF_MAX_CHARS)
|
|
9825
|
+
throw new Error("invalid chat avatar");
|
|
9826
|
+
return ref;
|
|
9827
|
+
}
|
|
9828
|
+
function cleanChatAvatarRef(value) {
|
|
9829
|
+
if (value == null)
|
|
9830
|
+
return null;
|
|
9831
|
+
const file = chatAvatarFile(value);
|
|
9832
|
+
if (!file)
|
|
9833
|
+
throw new Error("invalid chat avatar");
|
|
9834
|
+
return makeChatAvatarRef(file);
|
|
9835
|
+
}
|
|
9836
|
+
function assertChatAvatarBytes(value) {
|
|
9837
|
+
const bytes = Number(value?.byteLength ?? value?.size);
|
|
9838
|
+
if (!Number.isSafeInteger(bytes) || bytes <= 0 || bytes > CHAT_AVATAR_IMAGE_MAX_BYTES) {
|
|
9839
|
+
throw new Error("invalid chat avatar");
|
|
9840
|
+
}
|
|
9841
|
+
return bytes;
|
|
9842
|
+
}
|
|
9843
|
+
var CHAT_AVATAR_REF_PREFIX = "v1:", FILE_KEY_RE;
|
|
9844
|
+
var init_avatar2 = __esm(() => {
|
|
9845
|
+
init_config();
|
|
9846
|
+
init_filepayload();
|
|
9847
|
+
"use client";
|
|
9848
|
+
FILE_KEY_RE = /^[0-9a-f]{64}$/u;
|
|
9849
|
+
});
|
|
9850
|
+
|
|
9427
9851
|
// ../../core/chat/state.js
|
|
9428
9852
|
function makeCid() {
|
|
9429
9853
|
return `${Date.now().toString(36)}${toHex(randomBytes3(3))}`;
|
|
@@ -9689,6 +10113,11 @@ function makeChatUnavailableError() {
|
|
|
9689
10113
|
error.code = "permission-denied";
|
|
9690
10114
|
return error;
|
|
9691
10115
|
}
|
|
10116
|
+
function isChatEpochConflict(error) {
|
|
10117
|
+
const code = String(error?.code || "").trim().toLowerCase();
|
|
10118
|
+
const message = String(error?.message || "").trim().toLowerCase();
|
|
10119
|
+
return code === "permission-denied" || code.endsWith("/permission-denied") || code === "aborted" || code.endsWith("/aborted") || code === "failed-precondition" || code.endsWith("/failed-precondition") || message.includes("insufficient permissions") || message === "chat owner epoch changed";
|
|
10120
|
+
}
|
|
9692
10121
|
function makeMessageSaveUnavailableError() {
|
|
9693
10122
|
const error = new Error("this message can't be saved anymore");
|
|
9694
10123
|
error.code = "message-unavailable";
|
|
@@ -10168,34 +10597,6 @@ var init_chat = __esm(() => {
|
|
|
10168
10597
|
MSG_BODY_HEADER_BYTES = 1 + 1 + 1 + BOX_NONCE_BYTES + ED25519_SIGNATURE_BYTES;
|
|
10169
10598
|
});
|
|
10170
10599
|
|
|
10171
|
-
// ../../core/chat/avatar.js
|
|
10172
|
-
function cleanChatAvatarRef(value) {
|
|
10173
|
-
if (value == null)
|
|
10174
|
-
return null;
|
|
10175
|
-
const ref = cleanText(value);
|
|
10176
|
-
if (!ref || ref.length > CHAT_AVATAR_REF_MAX_CHARS) {
|
|
10177
|
-
throw new Error("invalid chat avatar");
|
|
10178
|
-
}
|
|
10179
|
-
return ref;
|
|
10180
|
-
}
|
|
10181
|
-
function chatAvatarDataUrl(value) {
|
|
10182
|
-
const ref = typeof value === "string" ? value.trim() : "";
|
|
10183
|
-
if (!ref.startsWith(CHAT_AVATAR_PREFIX) || ref.length > CHAT_AVATAR_REF_MAX_CHARS)
|
|
10184
|
-
return "";
|
|
10185
|
-
const body = ref.slice(CHAT_AVATAR_PREFIX.length);
|
|
10186
|
-
if (!body || body.length % 4 !== 0 || !BASE64_RE.test(body))
|
|
10187
|
-
return "";
|
|
10188
|
-
const padding2 = body.endsWith("==") ? 2 : body.endsWith("=") ? 1 : 0;
|
|
10189
|
-
const bytes = body.length / 4 * 3 - padding2;
|
|
10190
|
-
return bytes > 0 && bytes <= CHAT_AVATAR_IMAGE_MAX_BYTES ? ref : "";
|
|
10191
|
-
}
|
|
10192
|
-
var CHAT_AVATAR_PREFIX = "data:image/webp;base64,", BASE64_RE;
|
|
10193
|
-
var init_avatar2 = __esm(() => {
|
|
10194
|
-
init_config();
|
|
10195
|
-
"use client";
|
|
10196
|
-
BASE64_RE = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
10197
|
-
});
|
|
10198
|
-
|
|
10199
10600
|
// ../../core/chat/epochs/transition.js
|
|
10200
10601
|
function cleanTime(value) {
|
|
10201
10602
|
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
@@ -10416,7 +10817,7 @@ function normalizeChatStateSettings(value = {}) {
|
|
|
10416
10817
|
throw new Error("chat settings required");
|
|
10417
10818
|
}
|
|
10418
10819
|
const title = value.title == null ? null : cleanText(value.title);
|
|
10419
|
-
if (title != null && (!title || title.length >
|
|
10820
|
+
if (title != null && (!title || title.length > CHAT_TITLE_MAX_CHARS)) {
|
|
10420
10821
|
throw new Error("invalid chat title");
|
|
10421
10822
|
}
|
|
10422
10823
|
return {
|
|
@@ -11010,369 +11411,6 @@ var init_owner = __esm(() => {
|
|
|
11010
11411
|
"use client";
|
|
11011
11412
|
});
|
|
11012
11413
|
|
|
11013
|
-
// ../../core/crypto/file.js
|
|
11014
|
-
function createFileKey() {
|
|
11015
|
-
return randomBytes3(32);
|
|
11016
|
-
}
|
|
11017
|
-
function encodeFileKey(key) {
|
|
11018
|
-
return toHex(toBytes32(key, "file key"));
|
|
11019
|
-
}
|
|
11020
|
-
function decodeFileKey(key) {
|
|
11021
|
-
return toBytes32(key, "file key");
|
|
11022
|
-
}
|
|
11023
|
-
function getFileAadForPath(scope) {
|
|
11024
|
-
if (!scope) {
|
|
11025
|
-
throw new Error("file scope required");
|
|
11026
|
-
}
|
|
11027
|
-
return encodeScope(FILE_SCOPE, [scope]);
|
|
11028
|
-
}
|
|
11029
|
-
function getSubtleCrypto() {
|
|
11030
|
-
const subtle = globalThis.crypto?.subtle;
|
|
11031
|
-
if (!subtle) {
|
|
11032
|
-
throw new Error("WebCrypto AES-GCM unavailable");
|
|
11033
|
-
}
|
|
11034
|
-
return subtle;
|
|
11035
|
-
}
|
|
11036
|
-
async function importAesKey(key, usages) {
|
|
11037
|
-
return getSubtleCrypto().importKey("raw", toBytes32(key, "file key"), { name: "AES-GCM" }, false, usages);
|
|
11038
|
-
}
|
|
11039
|
-
async function sealFile(key, bytes, scope) {
|
|
11040
|
-
const fileKey = new Uint8Array(toBytes32(key, "file key"));
|
|
11041
|
-
try {
|
|
11042
|
-
const cryptoKey = await importAesKey(fileKey, ["encrypt"]);
|
|
11043
|
-
const iv = randomBytes3(FILE_IV_BYTES);
|
|
11044
|
-
const ct = await getSubtleCrypto().encrypt({
|
|
11045
|
-
name: "AES-GCM",
|
|
11046
|
-
iv,
|
|
11047
|
-
tagLength: FILE_TAG_BYTES * 8,
|
|
11048
|
-
additionalData: getFileAadForPath(scope)
|
|
11049
|
-
}, cryptoKey, toBytes(bytes, "plaintext"));
|
|
11050
|
-
return packBodyData(iv, new Uint8Array(ct));
|
|
11051
|
-
} finally {
|
|
11052
|
-
cleanBytes(fileKey);
|
|
11053
|
-
}
|
|
11054
|
-
}
|
|
11055
|
-
async function openFileForPath(key, body, scope) {
|
|
11056
|
-
const fileKey = new Uint8Array(toBytes32(key, "file key"));
|
|
11057
|
-
try {
|
|
11058
|
-
const cryptoKey = await importAesKey(fileKey, ["decrypt"]);
|
|
11059
|
-
const { nonce, ct } = unpackBodyData(body, FILE_IV_BYTES);
|
|
11060
|
-
const pt = await getSubtleCrypto().decrypt({
|
|
11061
|
-
name: "AES-GCM",
|
|
11062
|
-
iv: nonce,
|
|
11063
|
-
tagLength: FILE_TAG_BYTES * 8,
|
|
11064
|
-
additionalData: getFileAadForPath(scope)
|
|
11065
|
-
}, cryptoKey, ct);
|
|
11066
|
-
return new Uint8Array(pt);
|
|
11067
|
-
} finally {
|
|
11068
|
-
cleanBytes(fileKey);
|
|
11069
|
-
}
|
|
11070
|
-
}
|
|
11071
|
-
var FILE_SCOPE = "file-body", FILE_IV_BYTES = 12, FILE_TAG_BYTES = 16;
|
|
11072
|
-
var init_file = __esm(() => {
|
|
11073
|
-
init_core();
|
|
11074
|
-
init_kdf();
|
|
11075
|
-
init_pack();
|
|
11076
|
-
"use client";
|
|
11077
|
-
});
|
|
11078
|
-
|
|
11079
|
-
// ../../core/chat/filepayload.js
|
|
11080
|
-
function cleanMediaEpochId(value) {
|
|
11081
|
-
const epochId = String(value || "").trim();
|
|
11082
|
-
if (!new RegExp(`^${CHAT_ID_PATTERN}$`).test(epochId)) {
|
|
11083
|
-
throw new Error("invalid media epoch id");
|
|
11084
|
-
}
|
|
11085
|
-
return epochId.toLowerCase();
|
|
11086
|
-
}
|
|
11087
|
-
function cleanMediaId(value) {
|
|
11088
|
-
const mediaId = String(value || "").trim();
|
|
11089
|
-
if (!new RegExp(`^${MEDIA_ID_PATTERN}$`).test(mediaId)) {
|
|
11090
|
-
throw new Error("invalid media id");
|
|
11091
|
-
}
|
|
11092
|
-
return mediaId.toLowerCase();
|
|
11093
|
-
}
|
|
11094
|
-
function cleanSharedMediaId(value) {
|
|
11095
|
-
const sharedId = String(value || "").trim();
|
|
11096
|
-
if (!new RegExp(`^${SHARED_MEDIA_ID_PATTERN}$`).test(sharedId)) {
|
|
11097
|
-
throw new Error("invalid shared media id");
|
|
11098
|
-
}
|
|
11099
|
-
return sharedId.toLowerCase();
|
|
11100
|
-
}
|
|
11101
|
-
function makeChatMediaId() {
|
|
11102
|
-
return toHex(randomBytes3(16));
|
|
11103
|
-
}
|
|
11104
|
-
function mediaFilePath(epochId, mediaId) {
|
|
11105
|
-
const nextEpochId = cleanMediaEpochId(epochId);
|
|
11106
|
-
const nextMediaId = cleanMediaId(mediaId);
|
|
11107
|
-
return `${CHAT_MEDIA_ROOT}/${nextEpochId}/${nextMediaId}`;
|
|
11108
|
-
}
|
|
11109
|
-
function sharedMediaFilePath(sharedId) {
|
|
11110
|
-
const nextSharedId = cleanSharedMediaId(sharedId);
|
|
11111
|
-
return `${SHARED_MEDIA_ROOT}/${nextSharedId}`;
|
|
11112
|
-
}
|
|
11113
|
-
function makeSharedMediaId() {
|
|
11114
|
-
return toHex(randomBytes3(16));
|
|
11115
|
-
}
|
|
11116
|
-
function getMediaFileRef(path) {
|
|
11117
|
-
const value = String(path || "").trim();
|
|
11118
|
-
const chatMatch = value.match(CHAT_MEDIA_FILE_PATTERN);
|
|
11119
|
-
if (chatMatch?.[1] && chatMatch?.[2]) {
|
|
11120
|
-
return {
|
|
11121
|
-
type: "chat",
|
|
11122
|
-
epochId: chatMatch[1].toLowerCase(),
|
|
11123
|
-
mediaId: chatMatch[2].toLowerCase()
|
|
11124
|
-
};
|
|
11125
|
-
}
|
|
11126
|
-
const sharedMatch = value.match(SHARED_MEDIA_FILE_PATTERN);
|
|
11127
|
-
if (sharedMatch?.[1]) {
|
|
11128
|
-
return {
|
|
11129
|
-
type: "shared",
|
|
11130
|
-
sharedId: sharedMatch[1].toLowerCase()
|
|
11131
|
-
};
|
|
11132
|
-
}
|
|
11133
|
-
throw new Error("invalid media file path");
|
|
11134
|
-
}
|
|
11135
|
-
function getChatMediaFileRef(path) {
|
|
11136
|
-
const ref = getMediaFileRef(path);
|
|
11137
|
-
if (ref?.type !== "chat") {
|
|
11138
|
-
throw new Error("invalid media file path");
|
|
11139
|
-
}
|
|
11140
|
-
return ref;
|
|
11141
|
-
}
|
|
11142
|
-
function getSharedMediaFileRef(path) {
|
|
11143
|
-
const ref = getMediaFileRef(path);
|
|
11144
|
-
if (ref?.type !== "shared") {
|
|
11145
|
-
throw new Error("invalid shared media file path");
|
|
11146
|
-
}
|
|
11147
|
-
return ref;
|
|
11148
|
-
}
|
|
11149
|
-
function uploadByteLength(value) {
|
|
11150
|
-
if (Number.isFinite(value?.byteLength)) {
|
|
11151
|
-
return value.byteLength;
|
|
11152
|
-
}
|
|
11153
|
-
if (Number.isFinite(value?.size)) {
|
|
11154
|
-
return value.size;
|
|
11155
|
-
}
|
|
11156
|
-
if (Number.isFinite(value)) {
|
|
11157
|
-
return value;
|
|
11158
|
-
}
|
|
11159
|
-
return null;
|
|
11160
|
-
}
|
|
11161
|
-
function makeChatUploadTooLargeError(bytes, maxBytes = MAX_CHAT_UPLOAD_BYTES) {
|
|
11162
|
-
const error = new Error("upload too large");
|
|
11163
|
-
error.code = "upload-too-large";
|
|
11164
|
-
error.maxBytes = maxBytes;
|
|
11165
|
-
if (Number.isFinite(bytes)) {
|
|
11166
|
-
error.bytes = bytes;
|
|
11167
|
-
}
|
|
11168
|
-
return error;
|
|
11169
|
-
}
|
|
11170
|
-
function assertChatUploadByteSize(bytes, maxBytes = MAX_CHAT_UPLOAD_BYTES) {
|
|
11171
|
-
const length = uploadByteLength(bytes);
|
|
11172
|
-
if (!Number.isFinite(length)) {
|
|
11173
|
-
throw new Error("upload bytes required");
|
|
11174
|
-
}
|
|
11175
|
-
if (length <= 0 || length > maxBytes) {
|
|
11176
|
-
throw makeChatUploadTooLargeError(length, maxBytes);
|
|
11177
|
-
}
|
|
11178
|
-
return length;
|
|
11179
|
-
}
|
|
11180
|
-
async function toUploadBytes(data) {
|
|
11181
|
-
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
11182
|
-
if (typeof data.arrayBuffer === "function") {
|
|
11183
|
-
return new Uint8Array(await data.arrayBuffer());
|
|
11184
|
-
}
|
|
11185
|
-
if (typeof FileReader !== "undefined") {
|
|
11186
|
-
return new Promise((resolve, reject) => {
|
|
11187
|
-
const reader = new FileReader;
|
|
11188
|
-
reader.onload = () => resolve(new Uint8Array(reader.result));
|
|
11189
|
-
reader.onerror = () => reject(reader.error || new Error("blob read failed"));
|
|
11190
|
-
reader.readAsArrayBuffer(data);
|
|
11191
|
-
});
|
|
11192
|
-
}
|
|
11193
|
-
if (typeof Response !== "undefined") {
|
|
11194
|
-
return new Uint8Array(await new Response(data).arrayBuffer());
|
|
11195
|
-
}
|
|
11196
|
-
}
|
|
11197
|
-
if (typeof data?.arrayBuffer === "function") {
|
|
11198
|
-
return new Uint8Array(await data.arrayBuffer());
|
|
11199
|
-
}
|
|
11200
|
-
return toBytes(data, "upload bytes");
|
|
11201
|
-
}
|
|
11202
|
-
async function makeChatFileUploadPayload(epochId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
11203
|
-
const nextEpochId = cleanMediaEpochId(epochId);
|
|
11204
|
-
const mediaId = makeChatMediaId();
|
|
11205
|
-
const path = mediaFilePath(nextEpochId, mediaId);
|
|
11206
|
-
const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
|
|
11207
|
-
const key = createFileKey();
|
|
11208
|
-
try {
|
|
11209
|
-
const uploadBytes = await toUploadBytes(data);
|
|
11210
|
-
assertChatUploadByteSize(uploadBytes);
|
|
11211
|
-
const body = await sealFile(key, uploadBytes, path);
|
|
11212
|
-
assertChatUploadByteSize(body);
|
|
11213
|
-
return {
|
|
11214
|
-
epochId: nextEpochId,
|
|
11215
|
-
mediaId,
|
|
11216
|
-
path,
|
|
11217
|
-
body,
|
|
11218
|
-
metadata: {
|
|
11219
|
-
contentType: "application/octet-stream",
|
|
11220
|
-
cacheControl
|
|
11221
|
-
},
|
|
11222
|
-
file: {
|
|
11223
|
-
p: path,
|
|
11224
|
-
k: encodeFileKey(key),
|
|
11225
|
-
x: expiresAt
|
|
11226
|
-
}
|
|
11227
|
-
};
|
|
11228
|
-
} catch (error) {
|
|
11229
|
-
if (error && typeof error === "object") {
|
|
11230
|
-
error.path = error?.path || path;
|
|
11231
|
-
error.cid = error?.cid || cid;
|
|
11232
|
-
}
|
|
11233
|
-
throw error;
|
|
11234
|
-
} finally {
|
|
11235
|
-
cleanBytes(key);
|
|
11236
|
-
}
|
|
11237
|
-
}
|
|
11238
|
-
async function makeSharedFileUploadPayload(data, { contentType = "application/octet-stream", cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
11239
|
-
const sharedId = makeSharedMediaId();
|
|
11240
|
-
const path = sharedMediaFilePath(sharedId);
|
|
11241
|
-
const expiresAt = Date.now() + CHAT_MEDIA_TTL_MS2;
|
|
11242
|
-
const key = createFileKey();
|
|
11243
|
-
try {
|
|
11244
|
-
const uploadBytes = await toUploadBytes(data);
|
|
11245
|
-
assertChatUploadByteSize(uploadBytes);
|
|
11246
|
-
const body = await sealFile(key, uploadBytes, path);
|
|
11247
|
-
assertChatUploadByteSize(body);
|
|
11248
|
-
return {
|
|
11249
|
-
sharedId,
|
|
11250
|
-
path,
|
|
11251
|
-
body,
|
|
11252
|
-
metadata: {
|
|
11253
|
-
contentType,
|
|
11254
|
-
cacheControl
|
|
11255
|
-
},
|
|
11256
|
-
file: {
|
|
11257
|
-
p: path,
|
|
11258
|
-
k: encodeFileKey(key),
|
|
11259
|
-
x: expiresAt
|
|
11260
|
-
}
|
|
11261
|
-
};
|
|
11262
|
-
} catch (error) {
|
|
11263
|
-
if (error && typeof error === "object") {
|
|
11264
|
-
error.path = error?.path || path;
|
|
11265
|
-
error.sharedId = error?.sharedId || sharedId;
|
|
11266
|
-
}
|
|
11267
|
-
throw error;
|
|
11268
|
-
} finally {
|
|
11269
|
-
cleanBytes(key);
|
|
11270
|
-
}
|
|
11271
|
-
}
|
|
11272
|
-
var CHAT_MEDIA_ROOT = "chatEpochs", SHARED_MEDIA_ROOT = "shared", CHAT_MEDIA_TTL_MS2, MAX_CHAT_UPLOAD_BYTES, CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS2, CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS2, CHAT_VIDEO_TRANSCODE_TOTAL_BITRATE_BPS, CHAT_ID_PATTERN = "[0-9a-fA-F]{64}", SHARED_MEDIA_ID_PATTERN = "[0-9a-fA-F]{32}", MEDIA_ID_PATTERN = "[0-9a-fA-F]{32}", CHAT_MEDIA_FILE_PATTERN, SHARED_MEDIA_FILE_PATTERN;
|
|
11273
|
-
var init_filepayload = __esm(() => {
|
|
11274
|
-
init_file();
|
|
11275
|
-
init_config();
|
|
11276
|
-
init_core();
|
|
11277
|
-
"use client";
|
|
11278
|
-
CHAT_MEDIA_TTL_MS2 = CHAT_MEDIA_TTL_MS;
|
|
11279
|
-
MAX_CHAT_UPLOAD_BYTES = CHAT_UPLOAD_MAX_BYTES;
|
|
11280
|
-
CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS2 = CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS;
|
|
11281
|
-
CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS2 = CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS;
|
|
11282
|
-
CHAT_VIDEO_TRANSCODE_TOTAL_BITRATE_BPS = CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS2 + CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS2;
|
|
11283
|
-
CHAT_MEDIA_FILE_PATTERN = new RegExp(`^${CHAT_MEDIA_ROOT}/(${CHAT_ID_PATTERN})/(${MEDIA_ID_PATTERN})$`);
|
|
11284
|
-
SHARED_MEDIA_FILE_PATTERN = new RegExp(`^${SHARED_MEDIA_ROOT}/(${SHARED_MEDIA_ID_PATTERN})$`);
|
|
11285
|
-
});
|
|
11286
|
-
|
|
11287
|
-
// ../../core/files.js
|
|
11288
|
-
function makeFileId(size = 8) {
|
|
11289
|
-
return toHex(randomBytes3(size));
|
|
11290
|
-
}
|
|
11291
|
-
function setErrorStage(error, stage, extra = {}) {
|
|
11292
|
-
if (!error || typeof error !== "object") {
|
|
11293
|
-
return error;
|
|
11294
|
-
}
|
|
11295
|
-
error.stage = error?.stage || stage;
|
|
11296
|
-
Object.assign(error, extra);
|
|
11297
|
-
return error;
|
|
11298
|
-
}
|
|
11299
|
-
async function makeChatFileUpload(epochId, cid, data, { cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
11300
|
-
try {
|
|
11301
|
-
return await makeChatFileUploadPayload(epochId, cid, data, {
|
|
11302
|
-
cacheControl
|
|
11303
|
-
});
|
|
11304
|
-
} catch (error) {
|
|
11305
|
-
throw setErrorStage(error, "encrypt", {
|
|
11306
|
-
...error?.path ? { path: error.path } : {},
|
|
11307
|
-
cid
|
|
11308
|
-
});
|
|
11309
|
-
}
|
|
11310
|
-
}
|
|
11311
|
-
async function putChatFile(epochId, cid, data, options) {
|
|
11312
|
-
const upload = await makeChatFileUpload(epochId, cid, data, options);
|
|
11313
|
-
try {
|
|
11314
|
-
if (typeof options?.uploadChatMedia !== "function") {
|
|
11315
|
-
throw new Error("chat media upload required");
|
|
11316
|
-
}
|
|
11317
|
-
await options.uploadChatMedia(upload);
|
|
11318
|
-
return upload.file;
|
|
11319
|
-
} catch (error) {
|
|
11320
|
-
throw setErrorStage(error, "upload", { path: upload.path, cid });
|
|
11321
|
-
}
|
|
11322
|
-
}
|
|
11323
|
-
async function makeSharedFileUpload(data, { contentType = "application/octet-stream", cacheControl = "private, max-age=0, no-transform" } = {}) {
|
|
11324
|
-
try {
|
|
11325
|
-
return await makeSharedFileUploadPayload(data, {
|
|
11326
|
-
contentType,
|
|
11327
|
-
cacheControl
|
|
11328
|
-
});
|
|
11329
|
-
} catch (error) {
|
|
11330
|
-
throw setErrorStage(error, "encrypt", {
|
|
11331
|
-
...error?.path ? { path: error.path } : {}
|
|
11332
|
-
});
|
|
11333
|
-
}
|
|
11334
|
-
}
|
|
11335
|
-
async function putSharedFile(data, options = {}) {
|
|
11336
|
-
const upload = await makeSharedFileUpload(data, options);
|
|
11337
|
-
try {
|
|
11338
|
-
if (typeof options?.uploadSharedMedia !== "function") {
|
|
11339
|
-
throw new Error("shared media upload required");
|
|
11340
|
-
}
|
|
11341
|
-
await options.uploadSharedMedia(upload);
|
|
11342
|
-
return upload.file;
|
|
11343
|
-
} catch (error) {
|
|
11344
|
-
throw setErrorStage(error, "upload", { path: upload.path });
|
|
11345
|
-
}
|
|
11346
|
-
}
|
|
11347
|
-
async function readChatFile(readChatMedia, file) {
|
|
11348
|
-
let body;
|
|
11349
|
-
try {
|
|
11350
|
-
if (typeof readChatMedia !== "function") {
|
|
11351
|
-
throw new Error("chat media read required");
|
|
11352
|
-
}
|
|
11353
|
-
body = await readChatMedia(file?.p);
|
|
11354
|
-
} catch (error) {
|
|
11355
|
-
error.path = error?.path || file?.p || null;
|
|
11356
|
-
error.stage = error?.stage || "download";
|
|
11357
|
-
throw error;
|
|
11358
|
-
}
|
|
11359
|
-
try {
|
|
11360
|
-
getMediaFileRef(file?.p);
|
|
11361
|
-
const bytes = await openFileForPath(decodeFileKey(file?.k), body, file?.p);
|
|
11362
|
-
return bytes;
|
|
11363
|
-
} catch (error) {
|
|
11364
|
-
error.path = file?.p || null;
|
|
11365
|
-
error.stage = "decrypt";
|
|
11366
|
-
throw error;
|
|
11367
|
-
}
|
|
11368
|
-
}
|
|
11369
|
-
var init_files = __esm(() => {
|
|
11370
|
-
init_file();
|
|
11371
|
-
init_core();
|
|
11372
|
-
init_filepayload();
|
|
11373
|
-
"use client";
|
|
11374
|
-
});
|
|
11375
|
-
|
|
11376
11414
|
// ../../core/chat/messages/types.js
|
|
11377
11415
|
var ATTACHMENT_MSG_TYPES, MAX_TXT_CHARS, REACTION_MSG_TYPE = "rxn", DELETE_MSG_TYPE = "del", SYSTEM_MSG_TYPE = "sys", EPOCH_TRANSITION_MSG_TYPE = "epoch", EPOCH_PROPOSAL_MSG_TYPE = "epoch_proposal", SYSTEM_SETTINGS_KIND = "settings", DEFAULT_REACTION_EMOJI = "❤️", MAX_REACTIONS, HOLD_VISIBLE_KEY = "__holdVisible", SOURCE_GONE_VISIBLE_KEY = "__sourceGoneVisible";
|
|
11378
11416
|
var init_types = __esm(() => {
|
|
@@ -12320,7 +12358,7 @@ function cleanChatSettingsEventTitle(value) {
|
|
|
12320
12358
|
if (value == null)
|
|
12321
12359
|
return null;
|
|
12322
12360
|
const title = cleanText(value);
|
|
12323
|
-
if (!title || title.length >
|
|
12361
|
+
if (!title || title.length > CHAT_TITLE_MAX_CHARS)
|
|
12324
12362
|
throw new Error("invalid chat settings event title");
|
|
12325
12363
|
return title;
|
|
12326
12364
|
}
|
|
@@ -12335,10 +12373,7 @@ function makeChatSettingsMsg(revision, digest, changes = [], values = {}) {
|
|
|
12335
12373
|
const titleChanged = cleanChanges.includes("title");
|
|
12336
12374
|
const avatarChanged = cleanChanges.includes("avatarRef");
|
|
12337
12375
|
const title = titleChanged ? cleanChatSettingsEventTitle(values?.title) : undefined;
|
|
12338
|
-
const avatarRef = avatarChanged ? values?.avatarRef == null ? null :
|
|
12339
|
-
if (avatarChanged && values?.avatarRef != null && !avatarRef) {
|
|
12340
|
-
throw new Error("invalid chat settings event avatar");
|
|
12341
|
-
}
|
|
12376
|
+
const avatarRef = avatarChanged ? values?.avatarRef == null ? null : cleanChatAvatarRef(values.avatarRef) : undefined;
|
|
12342
12377
|
return {
|
|
12343
12378
|
t: SYSTEM_MSG_TYPE,
|
|
12344
12379
|
sys: SYSTEM_SETTINGS_KIND,
|
|
@@ -12361,7 +12396,7 @@ function isChatSettingsMsg(msg) {
|
|
|
12361
12396
|
return false;
|
|
12362
12397
|
}
|
|
12363
12398
|
}
|
|
12364
|
-
return !changes.includes("avatarRef") || msg?.avatarRef == null || !!
|
|
12399
|
+
return !changes.includes("avatarRef") || msg?.avatarRef == null || !!chatAvatarFile(msg.avatarRef);
|
|
12365
12400
|
}
|
|
12366
12401
|
function isPendingChatSettingsMsg(msg) {
|
|
12367
12402
|
return msg?.t === SYSTEM_MSG_TYPE && msg?.sys === SYSTEM_SETTINGS_KIND && msg?.pending === true && String(msg?.id || "").startsWith("local:") && chatSettingsEventChanges(msg).length === 1 && chatSettingsEventChanges(msg)[0] === "retention";
|
|
@@ -12369,7 +12404,7 @@ function isPendingChatSettingsMsg(msg) {
|
|
|
12369
12404
|
function getChatSettingsEventAvatar(msg) {
|
|
12370
12405
|
if (!isChatSettingsMsg(msg) || !chatSettingsEventChanges(msg).includes("avatarRef"))
|
|
12371
12406
|
return "";
|
|
12372
|
-
return
|
|
12407
|
+
return chatAvatarFile(msg?.avatarRef) ? msg.avatarRef : "";
|
|
12373
12408
|
}
|
|
12374
12409
|
function isSystemMsg(msg) {
|
|
12375
12410
|
return !!getSystemMsgText(msg) && (msg?.t === SYSTEM_MSG_TYPE || isMembershipEventMsg(msg));
|
|
@@ -12734,6 +12769,7 @@ var init_control = __esm(() => {
|
|
|
12734
12769
|
init_messagekeys();
|
|
12735
12770
|
init_ttl();
|
|
12736
12771
|
init_actions();
|
|
12772
|
+
init_config();
|
|
12737
12773
|
init_avatar2();
|
|
12738
12774
|
init_files2();
|
|
12739
12775
|
init_text();
|
|
@@ -13240,13 +13276,12 @@ function withChatPreviewExpired(preview, expiredAt) {
|
|
|
13240
13276
|
sourceTs: getChatPreviewSourceTs(preview)
|
|
13241
13277
|
}));
|
|
13242
13278
|
}
|
|
13243
|
-
function
|
|
13279
|
+
function chatPreviewCountsAsUnseen(preview, chatPK) {
|
|
13244
13280
|
if (!preview || fromSelf(preview, chatPK)) {
|
|
13245
13281
|
return false;
|
|
13246
13282
|
}
|
|
13247
13283
|
if (isReactionMsg(preview)) {
|
|
13248
|
-
|
|
13249
|
-
return !!cleanText(preview?.emoji) && (!targetFrom || targetFrom === chatPK);
|
|
13284
|
+
return !!cleanText(preview?.emoji);
|
|
13250
13285
|
}
|
|
13251
13286
|
return canRenderPreviewContent(preview);
|
|
13252
13287
|
}
|
|
@@ -15925,7 +15960,7 @@ function getPeersFromChats(chats) {
|
|
|
15925
15960
|
}
|
|
15926
15961
|
function isChatUnseenForUser(chatData, userChatPK) {
|
|
15927
15962
|
const last = chatData?.preview;
|
|
15928
|
-
if (!last?.ts || !
|
|
15963
|
+
if (!last?.ts || !chatPreviewCountsAsUnseen(last, userChatPK))
|
|
15929
15964
|
return false;
|
|
15930
15965
|
const readMs = timestampMs(chatData?.readMs, null);
|
|
15931
15966
|
const lastMs = timestampMs(last.ts, null);
|
|
@@ -18272,11 +18307,13 @@ function createChatDelete({
|
|
|
18272
18307
|
listActionsRef,
|
|
18273
18308
|
closeMessageBatchRef,
|
|
18274
18309
|
mutationGate,
|
|
18310
|
+
reconcileEpoch,
|
|
18275
18311
|
diag
|
|
18276
18312
|
}) {
|
|
18277
18313
|
const pendingDeleteIdsRef = { current: new Set };
|
|
18278
18314
|
const locallyDeletedChatIdsRef = { current: new Set };
|
|
18279
18315
|
const deletedChatIdsRef = { current: new Set };
|
|
18316
|
+
const removedChatVersionsRef = { current: new Map };
|
|
18280
18317
|
const keepSelectedDeletedChatIdsRef = { current: new Set };
|
|
18281
18318
|
const isChatPendingDelete = (chatId) => !!chatId && pendingDeleteIdsRef.current.has(chatId);
|
|
18282
18319
|
const clearDeletedChatState = (chatIds) => {
|
|
@@ -18386,20 +18423,46 @@ function createChatDelete({
|
|
|
18386
18423
|
listActionsRef.current?.commitServerChats?.(nextServerChats, { warm: false });
|
|
18387
18424
|
setSelectedChat((current) => current === chatId ? null : current);
|
|
18388
18425
|
};
|
|
18389
|
-
const dropRemovedChat = (chatId) => {
|
|
18390
|
-
if (!chatId)
|
|
18426
|
+
const dropRemovedChat = (chatId, epochVersion) => {
|
|
18427
|
+
if (!chatId || !Number.isSafeInteger(epochVersion) || epochVersion < 1)
|
|
18391
18428
|
return false;
|
|
18392
|
-
|
|
18393
|
-
|
|
18429
|
+
const previousVersion = removedChatVersionsRef.current.get(chatId) || 0;
|
|
18430
|
+
removedChatVersionsRef.current.set(chatId, Math.max(previousVersion, epochVersion));
|
|
18394
18431
|
dropChat(chatId);
|
|
18395
18432
|
return true;
|
|
18396
18433
|
};
|
|
18434
|
+
const commitRemovedRetirement = (chatId, epochVersion) => {
|
|
18435
|
+
if (!chatId || !Number.isSafeInteger(epochVersion) || epochVersion < 1)
|
|
18436
|
+
return false;
|
|
18437
|
+
const previousVersion = removedChatVersionsRef.current.get(chatId) || 0;
|
|
18438
|
+
removedChatVersionsRef.current.set(chatId, Math.max(previousVersion, epochVersion));
|
|
18439
|
+
pendingDeleteIdsRef.current.delete(chatId);
|
|
18440
|
+
locallyDeletedChatIdsRef.current.delete(chatId);
|
|
18441
|
+
deletedChatIdsRef.current.delete(chatId);
|
|
18442
|
+
keepSelectedDeletedChatIdsRef.current.delete(chatId);
|
|
18443
|
+
confirmDeletedChats([chatId]);
|
|
18444
|
+
return true;
|
|
18445
|
+
};
|
|
18446
|
+
const restoreRemovedChat = (chat) => {
|
|
18447
|
+
const chatId = chat?.id;
|
|
18448
|
+
const removedAtVersion = removedChatVersionsRef.current.get(chatId);
|
|
18449
|
+
if (!chatId || removedAtVersion == null)
|
|
18450
|
+
return false;
|
|
18451
|
+
if (!Number.isSafeInteger(chat.epochVersion) || chat.epochVersion <= removedAtVersion || !Array.isArray(chat.memberChatPKs) || !chat.memberChatPKs.includes(chatPK))
|
|
18452
|
+
return false;
|
|
18453
|
+
removedChatVersionsRef.current.delete(chatId);
|
|
18454
|
+
return true;
|
|
18455
|
+
};
|
|
18397
18456
|
const dropUnavailableChat = (chatId) => {
|
|
18398
18457
|
if (!chatId)
|
|
18399
18458
|
return Promise.resolve(false);
|
|
18400
18459
|
return (async () => {
|
|
18401
18460
|
const releaseMutation = await mutationGate?.acquireExclusive(chatId) || (() => {});
|
|
18402
18461
|
try {
|
|
18462
|
+
if (removedChatVersionsRef.current.has(chatId)) {
|
|
18463
|
+
dropChat(chatId);
|
|
18464
|
+
return true;
|
|
18465
|
+
}
|
|
18403
18466
|
const chat = lastServerChatsRef.current.find((item) => item?.id === chatId);
|
|
18404
18467
|
if (chat?.deliveryRegistered && chat?.epochVersion && chatPK && chat?.epochState?.stateCapability) {
|
|
18405
18468
|
await cloud.delivery.revoke(deriveChatDeliveryCapability(chat.epochState.stateCapability, {
|
|
@@ -18476,39 +18539,74 @@ function createChatDelete({
|
|
|
18476
18539
|
}, target.members, target.expectedEpochId);
|
|
18477
18540
|
return result.deliveredCount;
|
|
18478
18541
|
};
|
|
18542
|
+
const deleteCurrentChat = async (input, options = {}) => {
|
|
18543
|
+
const chatId = cleanText(typeof input === "string" ? input : input?.chatId || input?.id);
|
|
18544
|
+
const attemptedEpochs = new Set;
|
|
18545
|
+
while (chatId) {
|
|
18546
|
+
const target = getDeleteTargets(input)[0];
|
|
18547
|
+
if (!target)
|
|
18548
|
+
return null;
|
|
18549
|
+
const peerChatPK = cleanText(typeof input === "object" ? input?.peerChatPK : "") || cleanText(lastServerChatsRef.current.find((item) => item?.id === chatId)?.peerChatPK);
|
|
18550
|
+
const releaseMutation = await mutationGate?.acquireExclusive([chatId, peerChatPK].filter(Boolean)) || (() => {});
|
|
18551
|
+
let released = false;
|
|
18552
|
+
const release = () => {
|
|
18553
|
+
if (released)
|
|
18554
|
+
return;
|
|
18555
|
+
released = true;
|
|
18556
|
+
releaseMutation();
|
|
18557
|
+
};
|
|
18558
|
+
try {
|
|
18559
|
+
const deletion = await cloud.chat.delete(target.chatId, {
|
|
18560
|
+
entryId: target.entryId,
|
|
18561
|
+
expectedEpochId: target.expectedEpochId,
|
|
18562
|
+
stateCapability: target.stateCapability,
|
|
18563
|
+
cleanup: options.cleanup !== false
|
|
18564
|
+
});
|
|
18565
|
+
if (deletion?.newlyDeleted !== false)
|
|
18566
|
+
await wakeDeletedChat(target);
|
|
18567
|
+
return target;
|
|
18568
|
+
} catch (error) {
|
|
18569
|
+
release();
|
|
18570
|
+
const attemptKey = `${target.epochVersion || 0}:${target.expectedEpochId || ""}`;
|
|
18571
|
+
if (!attemptKey || attemptedEpochs.has(attemptKey) || !isChatEpochConflict(error) || typeof reconcileEpoch !== "function")
|
|
18572
|
+
throw error;
|
|
18573
|
+
attemptedEpochs.add(attemptKey);
|
|
18574
|
+
const reconciled = await reconcileEpoch(chatId);
|
|
18575
|
+
const current = lastServerChatsRef.current.find((item) => item?.id === chatId) || null;
|
|
18576
|
+
if (reconciled?.removed || !current)
|
|
18577
|
+
return null;
|
|
18578
|
+
if (!Number.isSafeInteger(current.epochVersion) || current.epochVersion <= target.epochVersion) {
|
|
18579
|
+
throw error;
|
|
18580
|
+
}
|
|
18581
|
+
} finally {
|
|
18582
|
+
release();
|
|
18583
|
+
}
|
|
18584
|
+
}
|
|
18585
|
+
return null;
|
|
18586
|
+
};
|
|
18479
18587
|
const deleteChat = async (chat, options = {}) => {
|
|
18480
|
-
const
|
|
18588
|
+
const inputs = Array.isArray(chat) ? chat : [chat];
|
|
18589
|
+
const targets = getDeleteTargets(inputs);
|
|
18481
18590
|
if (!targets.length)
|
|
18482
18591
|
return false;
|
|
18483
18592
|
const chatIds = targets.map((target) => target.chatId);
|
|
18484
|
-
const
|
|
18485
|
-
|
|
18486
|
-
return cleanText(typeof input === "object" ? input?.peerChatPK : "") || cleanText(lastServerChatsRef.current.find((item) => item?.id === chatId)?.peerChatPK);
|
|
18487
|
-
}).filter(Boolean);
|
|
18488
|
-
const releaseMutation = await mutationGate?.acquireExclusive([...chatIds, ...peerChatPKs]) || (() => {});
|
|
18593
|
+
const deletedTargets = [];
|
|
18594
|
+
hideDeletingChats(chatIds, options);
|
|
18489
18595
|
try {
|
|
18490
|
-
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
|
|
18495
|
-
|
|
18496
|
-
|
|
18497
|
-
|
|
18498
|
-
|
|
18499
|
-
|
|
18500
|
-
|
|
18501
|
-
|
|
18502
|
-
await options.onDeliveryRevoked?.();
|
|
18503
|
-
confirmDeletedChats(chatIds);
|
|
18504
|
-
} catch (error) {
|
|
18505
|
-
restoreDeletedChats(chatIds);
|
|
18506
|
-
throw error;
|
|
18507
|
-
}
|
|
18508
|
-
return Array.isArray(chat) ? targets.length : true;
|
|
18509
|
-
} finally {
|
|
18510
|
-
releaseMutation();
|
|
18596
|
+
for (const target of targets) {
|
|
18597
|
+
const input = inputs.find((item) => cleanText(typeof item === "string" ? item : item?.chatId || item?.id) === target.chatId) || target.chatId;
|
|
18598
|
+
const deleted = await deleteCurrentChat(input, options);
|
|
18599
|
+
if (deleted)
|
|
18600
|
+
deletedTargets.push(deleted);
|
|
18601
|
+
}
|
|
18602
|
+
await revokeTargetDeliveries(deletedTargets);
|
|
18603
|
+
await options.onDeliveryRevoked?.();
|
|
18604
|
+
confirmDeletedChats(chatIds);
|
|
18605
|
+
} catch (error) {
|
|
18606
|
+
restoreDeletedChats(chatIds);
|
|
18607
|
+
throw error;
|
|
18511
18608
|
}
|
|
18609
|
+
return Array.isArray(chat) ? targets.length : true;
|
|
18512
18610
|
};
|
|
18513
18611
|
const wasChatDeletedLocally = (chatId) => !!chatId && locallyDeletedChatIdsRef.current.has(chatId);
|
|
18514
18612
|
const ackDeletedChat = (chatId) => {
|
|
@@ -18519,21 +18617,25 @@ function createChatDelete({
|
|
|
18519
18617
|
pendingDeleteIdsRef.current = new Set;
|
|
18520
18618
|
locallyDeletedChatIdsRef.current = new Set;
|
|
18521
18619
|
deletedChatIdsRef.current = new Set;
|
|
18620
|
+
removedChatVersionsRef.current = new Map;
|
|
18522
18621
|
keepSelectedDeletedChatIdsRef.current = new Set;
|
|
18523
18622
|
};
|
|
18524
18623
|
return {
|
|
18525
18624
|
pendingDeleteIdsRef,
|
|
18526
18625
|
deletedChatIdsRef,
|
|
18626
|
+
removedChatVersionsRef,
|
|
18527
18627
|
keepSelectedDeletedChatIdsRef,
|
|
18528
18628
|
resetDeleteState,
|
|
18529
18629
|
clearDeletedChatState,
|
|
18530
18630
|
beginRetirement,
|
|
18531
18631
|
rollbackRetirement,
|
|
18532
18632
|
commitRetirement,
|
|
18633
|
+
commitRemovedRetirement,
|
|
18533
18634
|
restoreDeletedChat,
|
|
18534
18635
|
deleteChat,
|
|
18535
18636
|
dropChat,
|
|
18536
18637
|
dropRemovedChat,
|
|
18638
|
+
restoreRemovedChat,
|
|
18537
18639
|
dropUnavailableChat,
|
|
18538
18640
|
wasChatDeletedLocally,
|
|
18539
18641
|
ackDeletedChat,
|
|
@@ -20039,7 +20141,73 @@ function withoutChatIdentity(options = {}) {
|
|
|
20039
20141
|
}
|
|
20040
20142
|
function refreshQueuedSendOptions(sendOptionsForPeer, peerChatPK, sendOptionOverrides = null) {
|
|
20041
20143
|
const chatId = cleanText(sendOptionOverrides?.chatId);
|
|
20042
|
-
|
|
20144
|
+
const current = sendOptionsForPeer(peerChatPK, chatId);
|
|
20145
|
+
if (chatId) {
|
|
20146
|
+
return {
|
|
20147
|
+
...withoutChatIdentity(sendOptionOverrides),
|
|
20148
|
+
...current
|
|
20149
|
+
};
|
|
20150
|
+
}
|
|
20151
|
+
return mergeSendOptions(withoutChatIdentity(sendOptionOverrides), current);
|
|
20152
|
+
}
|
|
20153
|
+
function currentEpoch(options) {
|
|
20154
|
+
const manifest = options?.epochState?.manifest || options?.ownEntry?.current?.manifest || null;
|
|
20155
|
+
const epochId = cleanText(manifest?.epochId);
|
|
20156
|
+
const epochVersion = Number(manifest?.epochVersion);
|
|
20157
|
+
return epochId && Number.isInteger(epochVersion) && epochVersion > 0 ? { epochId, epochVersion } : null;
|
|
20158
|
+
}
|
|
20159
|
+
async function runCurrentEpochWrite({
|
|
20160
|
+
chatId,
|
|
20161
|
+
mutationGate,
|
|
20162
|
+
readOptions,
|
|
20163
|
+
reconcileEpoch,
|
|
20164
|
+
write,
|
|
20165
|
+
onCommitted,
|
|
20166
|
+
onEpochAdvance
|
|
20167
|
+
}) {
|
|
20168
|
+
const attemptedEpochs = new Set;
|
|
20169
|
+
while (true) {
|
|
20170
|
+
const releaseMutation = await mutationGate?.acquireShared(chatId) || (() => {});
|
|
20171
|
+
let released = false;
|
|
20172
|
+
let committed = false;
|
|
20173
|
+
const release = () => {
|
|
20174
|
+
if (released)
|
|
20175
|
+
return;
|
|
20176
|
+
released = true;
|
|
20177
|
+
releaseMutation();
|
|
20178
|
+
};
|
|
20179
|
+
let options;
|
|
20180
|
+
let epoch;
|
|
20181
|
+
try {
|
|
20182
|
+
options = readOptions();
|
|
20183
|
+
epoch = currentEpoch(options);
|
|
20184
|
+
return await write(options, (...args) => {
|
|
20185
|
+
committed = true;
|
|
20186
|
+
release();
|
|
20187
|
+
onCommitted?.(...args);
|
|
20188
|
+
});
|
|
20189
|
+
} catch (error) {
|
|
20190
|
+
release();
|
|
20191
|
+
const attemptKey = epoch ? `${epoch.epochVersion}:${epoch.epochId}` : "";
|
|
20192
|
+
if (committed || options?.chatExists !== true || !attemptKey || attemptedEpochs.has(attemptKey) || !isChatEpochConflict(error) || typeof reconcileEpoch !== "function") {
|
|
20193
|
+
throw error;
|
|
20194
|
+
}
|
|
20195
|
+
attemptedEpochs.add(attemptKey);
|
|
20196
|
+
await reconcileEpoch(chatId);
|
|
20197
|
+
const nextEpoch = currentEpoch(readOptions());
|
|
20198
|
+
if (!nextEpoch || nextEpoch.epochVersion <= epoch.epochVersion || nextEpoch.epochId === epoch.epochId) {
|
|
20199
|
+
throw error;
|
|
20200
|
+
}
|
|
20201
|
+
onEpochAdvance?.({
|
|
20202
|
+
fromEpochId: epoch.epochId,
|
|
20203
|
+
fromEpochVersion: epoch.epochVersion,
|
|
20204
|
+
toEpochId: nextEpoch.epochId,
|
|
20205
|
+
toEpochVersion: nextEpoch.epochVersion
|
|
20206
|
+
});
|
|
20207
|
+
} finally {
|
|
20208
|
+
release();
|
|
20209
|
+
}
|
|
20210
|
+
}
|
|
20043
20211
|
}
|
|
20044
20212
|
function markSendPerf(diag, label, startedAt, data = {}, minMs = SEND_CHAT_REDUCER_WARN_MS) {
|
|
20045
20213
|
const elapsedMs = Date.now() - startedAt;
|
|
@@ -20304,7 +20472,7 @@ async function makeSharedMediaAttachment(cloud, media, message, data) {
|
|
|
20304
20472
|
...source?.c ? { caption: source.c } : {}
|
|
20305
20473
|
}));
|
|
20306
20474
|
}
|
|
20307
|
-
function createChatSend({ cloud, media = {}, uid, chatBanned, chatPK, chatSigningPK, chatSigningSecret, chatPrivateKey, notificationPK, notificationPrivateKey, localCache, localByChatRef, setLocalByChat, setChats, setLastChat, sendOptionsForPeer, selectLocalChat, adoptLocalMessageMedia, readMessageFile, mutationGate, diag = null }) {
|
|
20475
|
+
function createChatSend({ cloud, media = {}, uid, chatBanned, chatPK, chatSigningPK, chatSigningSecret, chatPrivateKey, notificationPK, notificationPrivateKey, localCache, localByChatRef, setLocalByChat, setChats, setLastChat, sendOptionsForPeer, selectLocalChat, adoptLocalMessageMedia, readMessageFile, mutationGate, reconcileEpoch, diag = null }) {
|
|
20308
20476
|
const adoptedLocalMediaRef = { current: new Set };
|
|
20309
20477
|
const attachmentPrepareByCidRef = { current: new Map };
|
|
20310
20478
|
const cachedLocalMediaRef = { current: new Set };
|
|
@@ -20664,78 +20832,80 @@ ${cid}` : "";
|
|
|
20664
20832
|
};
|
|
20665
20833
|
const queueSend = async (peerChatPK, message, run, { previewRequired = false, sendOptions: sendOptionOverrides = null } = {}) => {
|
|
20666
20834
|
const startedAt = Date.now();
|
|
20667
|
-
|
|
20668
|
-
|
|
20835
|
+
const sendOptions = refreshQueuedSendOptions(sendOptionsForPeer, peerChatPK, sendOptionOverrides);
|
|
20836
|
+
const local = await showLocalMessage(peerChatPK, message, sendOptions);
|
|
20669
20837
|
const mutationTarget = local.chatId || cleanText(sendOptionOverrides?.chatId) || peerChatPK;
|
|
20670
|
-
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20699
|
-
|
|
20700
|
-
|
|
20701
|
-
|
|
20702
|
-
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20714
|
-
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
|
|
20726
|
-
|
|
20727
|
-
|
|
20728
|
-
|
|
20729
|
-
|
|
20730
|
-
|
|
20731
|
-
|
|
20838
|
+
const requiredPreview = previewRequired || sendOptions.chatExists !== true;
|
|
20839
|
+
let committed = false;
|
|
20840
|
+
let sent = false;
|
|
20841
|
+
const markSent = () => {
|
|
20842
|
+
if (sent)
|
|
20843
|
+
return;
|
|
20844
|
+
sent = true;
|
|
20845
|
+
sentChatIdsRef.current.add(local.chatId);
|
|
20846
|
+
markLocalStatus(local.chatId, local.cid, LOCAL_SENT);
|
|
20847
|
+
};
|
|
20848
|
+
const markCommitted = () => {
|
|
20849
|
+
if (committed)
|
|
20850
|
+
return;
|
|
20851
|
+
committed = true;
|
|
20852
|
+
markSent();
|
|
20853
|
+
markDone(diag, "chat.send.commit", startedAt, { type: local?.t || "txt", previewRequired: !!requiredPreview });
|
|
20854
|
+
};
|
|
20855
|
+
return await new Promise((resolve, reject) => {
|
|
20856
|
+
const job = {
|
|
20857
|
+
previewKey: local.chatId,
|
|
20858
|
+
previewRequired: requiredPreview,
|
|
20859
|
+
resolve,
|
|
20860
|
+
reject,
|
|
20861
|
+
onSuccess: markSent,
|
|
20862
|
+
onError: (error) => {
|
|
20863
|
+
markError(diag, "chat.send.job", startedAt, error, { type: local?.t || "txt", previewRequired: !!requiredPreview });
|
|
20864
|
+
if (committed)
|
|
20865
|
+
markSent();
|
|
20866
|
+
else
|
|
20867
|
+
markLocalStatus(local.chatId, local.cid, LOCAL_FAILED);
|
|
20868
|
+
},
|
|
20869
|
+
run: async (context) => {
|
|
20870
|
+
const writeStartedAt = Date.now();
|
|
20871
|
+
markDiag(diag, "chat.send.write.start", { type: local?.t || "txt", previewRequired: !!requiredPreview, updatePreview: !!context?.updatePreview });
|
|
20872
|
+
try {
|
|
20873
|
+
const result = await runCurrentEpochWrite({
|
|
20874
|
+
chatId: mutationTarget,
|
|
20875
|
+
mutationGate,
|
|
20876
|
+
readOptions: () => refreshQueuedSendOptions(sendOptionsForPeer, peerChatPK, {
|
|
20877
|
+
...sendOptionOverrides,
|
|
20878
|
+
chatId: local.chatId
|
|
20879
|
+
}),
|
|
20880
|
+
reconcileEpoch,
|
|
20881
|
+
write: (currentSendOptions, onCommitted) => run({
|
|
20882
|
+
...context,
|
|
20883
|
+
local,
|
|
20884
|
+
onCommitted,
|
|
20885
|
+
sendOptions: currentSendOptions
|
|
20886
|
+
}),
|
|
20887
|
+
onCommitted: () => {
|
|
20888
|
+
markCommitted();
|
|
20889
|
+
context?.releaseTargets?.();
|
|
20890
|
+
},
|
|
20891
|
+
onEpochAdvance: ({ fromEpochVersion, toEpochVersion }) => {
|
|
20892
|
+
markDiag(diag, "chat.send.epoch.advance", { fromEpochVersion, toEpochVersion });
|
|
20893
|
+
}
|
|
20894
|
+
});
|
|
20895
|
+
markDone(diag, "chat.send.write", writeStartedAt, { type: local?.t || "txt", previewRequired: !!requiredPreview, updatePreview: !!context?.updatePreview });
|
|
20896
|
+
return result;
|
|
20897
|
+
} catch (error) {
|
|
20898
|
+
markError(diag, "chat.send.write", writeStartedAt, error, {
|
|
20899
|
+
type: local?.t || "txt",
|
|
20900
|
+
previewRequired: !!requiredPreview,
|
|
20901
|
+
updatePreview: !!context?.updatePreview
|
|
20902
|
+
});
|
|
20903
|
+
throw error;
|
|
20732
20904
|
}
|
|
20733
|
-
}
|
|
20734
|
-
|
|
20735
|
-
|
|
20736
|
-
}
|
|
20737
|
-
releaseMutation?.();
|
|
20738
|
-
}
|
|
20905
|
+
}
|
|
20906
|
+
};
|
|
20907
|
+
enqueueSendJob(peerChatPK, job, reject);
|
|
20908
|
+
});
|
|
20739
20909
|
};
|
|
20740
20910
|
const sendOptionsForQueuedWrite = (baseOptions, local, updatePreview, onCommitted) => {
|
|
20741
20911
|
const chatId = local?.chatId;
|
|
@@ -24971,7 +25141,7 @@ async function unlinkBlockedChat(cloud, uid, identity, entry, options) {
|
|
|
24971
25141
|
const entryId = ownChatEntryId(identity.chatPrivateKey, manifest.chatId);
|
|
24972
25142
|
markDiag(options.diag, "chat.owner.unlink", { reason: "blocked" });
|
|
24973
25143
|
await cloud.user.chats.unlink(uid, entryId);
|
|
24974
|
-
options.onRemoved?.(manifest.chatId);
|
|
25144
|
+
options.onRemoved?.(manifest.chatId, manifest.epochVersion);
|
|
24975
25145
|
options.onPingDelete?.(manifest.chatId);
|
|
24976
25146
|
return { removed: true, blocked: true, chatId: manifest.chatId };
|
|
24977
25147
|
}
|
|
@@ -24990,6 +25160,25 @@ function isRetryableInboxError(error) {
|
|
|
24990
25160
|
];
|
|
24991
25161
|
return retryableCodes.some((value) => code === value || code.endsWith(`/${value}`)) || /\b(network|offline|timed? out|temporar(?:y|ily)|connection)\b/.test(message);
|
|
24992
25162
|
}
|
|
25163
|
+
async function retireConfirmedDeletedChat(cloud, uid, identity, entry, options = {}) {
|
|
25164
|
+
const manifest = entry?.current?.manifest;
|
|
25165
|
+
if (!manifest?.chatId || !identity?.chatPrivateKey || !identity?.chatPK) {
|
|
25166
|
+
throw new Error("deleted chat owner entry required");
|
|
25167
|
+
}
|
|
25168
|
+
const entryId = ownChatEntryId(identity.chatPrivateKey, manifest.chatId);
|
|
25169
|
+
await cloud.user.chats.unlinkDeleted(uid, entryId, manifest.chatId);
|
|
25170
|
+
if (entry.deliveryRegistered) {
|
|
25171
|
+
const capability = deriveChatDeliveryCapability(entry.current.stateCapability, {
|
|
25172
|
+
chatId: manifest.chatId,
|
|
25173
|
+
recipientChatPK: identity.chatPK,
|
|
25174
|
+
generation: manifest.epochVersion
|
|
25175
|
+
});
|
|
25176
|
+
await cloud.delivery.revoke(capability).catch(() => false);
|
|
25177
|
+
}
|
|
25178
|
+
markDiag(options.diag, "chat.owner.unlink", { reason: "confirmed_deleted" });
|
|
25179
|
+
options.onPingDelete?.(manifest.chatId);
|
|
25180
|
+
return { removed: true, deleted: true, chatId: manifest.chatId };
|
|
25181
|
+
}
|
|
24993
25182
|
function inboxErrorReason(error) {
|
|
24994
25183
|
const message = cleanText(error?.message).toLowerCase();
|
|
24995
25184
|
if (message === "ping sender identity mismatch")
|
|
@@ -25261,7 +25450,21 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25261
25450
|
return "stale";
|
|
25262
25451
|
}
|
|
25263
25452
|
if (entry && payload.epochVersion > entry.current.manifest.epochVersion) {
|
|
25264
|
-
|
|
25453
|
+
let advanced;
|
|
25454
|
+
try {
|
|
25455
|
+
advanced = await advanceEntryToEpoch(cloud, uid, identity, entry, payload.epochVersion, options);
|
|
25456
|
+
} catch (error) {
|
|
25457
|
+
if (payload.kind !== "chat_deleted")
|
|
25458
|
+
throw error;
|
|
25459
|
+
try {
|
|
25460
|
+
await retireConfirmedDeletedChat(cloud, uid, identity, entry, options);
|
|
25461
|
+
return "deleted";
|
|
25462
|
+
} catch (confirmationError) {
|
|
25463
|
+
if (isRetryableInboxError(confirmationError))
|
|
25464
|
+
throw confirmationError;
|
|
25465
|
+
throw error;
|
|
25466
|
+
}
|
|
25467
|
+
}
|
|
25265
25468
|
if (advanced?.removed) {
|
|
25266
25469
|
options.onPingDelete?.(payload.chatId);
|
|
25267
25470
|
return "deleted";
|
|
@@ -25317,6 +25520,11 @@ async function processOpenedPing(cloud, uid, identity, ping, openedProfile, hint
|
|
|
25317
25520
|
options.onPingDelete?.(payload.chatId);
|
|
25318
25521
|
return "deleted";
|
|
25319
25522
|
}
|
|
25523
|
+
const latest = await advanceEntryToLatestEpoch(cloud, uid, identity, transitioned, options);
|
|
25524
|
+
if (latest?.removed) {
|
|
25525
|
+
options.onPingDelete?.(payload.chatId);
|
|
25526
|
+
return "deleted";
|
|
25527
|
+
}
|
|
25320
25528
|
return "transition";
|
|
25321
25529
|
}
|
|
25322
25530
|
}
|
|
@@ -25425,8 +25633,15 @@ async function processPing(cloud, uid, identity, doc, options) {
|
|
|
25425
25633
|
async function applyEpochTransitionAction(cloud, uid, identityValue, entry, action, options, confirmedValue = null) {
|
|
25426
25634
|
const parent = entry.current.manifest;
|
|
25427
25635
|
const sender = manifestMember(parent, action.s || action.certificate?.actor);
|
|
25428
|
-
|
|
25636
|
+
const reject = (stage) => {
|
|
25637
|
+
markDiag(options.diag, "chat.epoch.apply.reject", {
|
|
25638
|
+
stage,
|
|
25639
|
+
epochVersion: parent.epochVersion
|
|
25640
|
+
});
|
|
25429
25641
|
return null;
|
|
25642
|
+
};
|
|
25643
|
+
if (!sender)
|
|
25644
|
+
return reject("sender");
|
|
25430
25645
|
const engine = await options.mls?.open?.();
|
|
25431
25646
|
if (!engine)
|
|
25432
25647
|
throw new Error("mls engine required");
|
|
@@ -25434,25 +25649,25 @@ async function applyEpochTransitionAction(cloud, uid, identityValue, entry, acti
|
|
|
25434
25649
|
const expectedCommitment = transitionCommitment(action.certificate);
|
|
25435
25650
|
const confirmed = confirmedValue || await cloud.chat.state.transition(parent.chatId, parent.epochId, entry.current.stateCapability).catch(() => null);
|
|
25436
25651
|
if (!confirmed || confirmed.parentEpochVersion !== parent.epochVersion || confirmed.nextEpochId !== action.certificate?.nextEpochId || confirmed.nextEpochVersion !== action.certificate?.nextEpochVersion || confirmed.transitionCommitment !== expectedCommitment || confirmed.transitionMessageId !== messageId || confirmed.mlsPackageId !== action.mls?.packageId || confirmed.mlsPackageDigest !== action.mls?.packageDigest)
|
|
25437
|
-
return
|
|
25652
|
+
return reject("certificate");
|
|
25438
25653
|
let leaveProposalId = "";
|
|
25439
25654
|
if (action.membership?.kind === "leave") {
|
|
25440
25655
|
const leavingChatPK = cleanText(action.membership?.actor);
|
|
25441
25656
|
const proposalId = cleanText(action.membership?.proposalId);
|
|
25442
25657
|
const targetSet = new Set(Array.isArray(action.membership?.targets) ? action.membership.targets.map(cleanText) : []);
|
|
25443
25658
|
if (!leavingChatPK || !proposalId || targetSet.size !== 1 || !targetSet.has(leavingChatPK))
|
|
25444
|
-
return
|
|
25659
|
+
return reject("leave-certificate");
|
|
25445
25660
|
const proposalRecord = await cloud.chat.messages.read(parent.epochId, proposalId, {
|
|
25446
25661
|
lane: messageLaneForEpoch(entry.current)
|
|
25447
25662
|
}).catch(() => null);
|
|
25448
25663
|
const proposal = proposalRecord ? await decryptMsg(proposalRecord, "", "", "", { chatId: parent.chatId, epochState: entry.current }).catch(() => null) : null;
|
|
25449
25664
|
if (!isLeaveProposalMsg(proposal) || proposal.s !== leavingChatPK)
|
|
25450
|
-
return
|
|
25665
|
+
return reject("leave-proposal");
|
|
25451
25666
|
leaveProposalId = proposalId;
|
|
25452
25667
|
}
|
|
25453
25668
|
const packageRecord = await cloud.chat.mls.packages.read(parent.chatId, confirmed.mlsPackageId);
|
|
25454
25669
|
if (!packageRecord || packageRecord.digest !== confirmed.mlsPackageDigest || packageRecord.head.transitionCommitment !== confirmed.transitionCommitment || !verifyChatMlsEpochPackageSignature(packageRecord, sender.chatSigningPK))
|
|
25455
|
-
return
|
|
25670
|
+
return reject("mls-package");
|
|
25456
25671
|
const identity = {
|
|
25457
25672
|
...identityValue,
|
|
25458
25673
|
uid,
|
|
@@ -25463,8 +25678,11 @@ async function applyEpochTransitionAction(cloud, uid, identityValue, entry, acti
|
|
|
25463
25678
|
let parentSnapshot = null;
|
|
25464
25679
|
let nextSnapshot = null;
|
|
25465
25680
|
let epochState = null;
|
|
25681
|
+
let openStage = "owner-snapshot";
|
|
25682
|
+
const openStartedAt = Date.now();
|
|
25466
25683
|
try {
|
|
25467
25684
|
parentSnapshot = await readOwnerChatMlsSnapshot(cloud, identity, entryId, entry.current);
|
|
25685
|
+
openStage = "mls-commit";
|
|
25468
25686
|
const processed = engine.processCommit(parentSnapshot, packageRecord.commit);
|
|
25469
25687
|
nextSnapshot = processed.snapshot;
|
|
25470
25688
|
if (processed.removed) {
|
|
@@ -25483,9 +25701,10 @@ async function applyEpochTransitionAction(cloud, uid, identityValue, entry, acti
|
|
|
25483
25701
|
if (leaveProposalId) {
|
|
25484
25702
|
options.leaveProposalObservedAt?.delete(`${parent.chatId}:${leaveProposalId}`);
|
|
25485
25703
|
}
|
|
25486
|
-
options.onRemoved?.(parent.chatId);
|
|
25704
|
+
options.onRemoved?.(parent.chatId, confirmed.nextEpochVersion);
|
|
25487
25705
|
return { removed: true, chatId: parent.chatId };
|
|
25488
25706
|
}
|
|
25707
|
+
openStage = "mls-package-open";
|
|
25489
25708
|
const opened = await openChatMlsEpochPackage(processed, packageRecord, {
|
|
25490
25709
|
chatId: parent.chatId,
|
|
25491
25710
|
senderSigningPK: sender.chatSigningPK,
|
|
@@ -25500,8 +25719,13 @@ async function applyEpochTransitionAction(cloud, uid, identityValue, entry, acti
|
|
|
25500
25719
|
mlsPackageId: opened.packageId,
|
|
25501
25720
|
mlsPackageDigest: opened.packageDigest
|
|
25502
25721
|
};
|
|
25722
|
+
openStage = "settings";
|
|
25503
25723
|
epochState.settings = await readEpochChatSettings(cloud, epochState);
|
|
25504
|
-
} catch {
|
|
25724
|
+
} catch (error) {
|
|
25725
|
+
markError(options.diag, "chat.epoch.apply", openStartedAt, error, {
|
|
25726
|
+
stage: openStage,
|
|
25727
|
+
epochVersion: parent.epochVersion
|
|
25728
|
+
});
|
|
25505
25729
|
cleanBytes(parentSnapshot, nextSnapshot, epochState?.epochSecret, epochState?.stateCapability);
|
|
25506
25730
|
return null;
|
|
25507
25731
|
}
|
|
@@ -25588,6 +25812,42 @@ async function advanceEntryToEpoch(cloud, uid, identity, entry, targetEpochVersi
|
|
|
25588
25812
|
}).catch(() => null);
|
|
25589
25813
|
const action = record ? await decryptMsg(record, "", "", "", { chatId: parent.chatId, epochState: current.current }).catch(() => null) : null;
|
|
25590
25814
|
if (!isEpochTransitionMsg(action)) {
|
|
25815
|
+
markDiag(options.diag, "chat.epoch.advance.pending", {
|
|
25816
|
+
stage: record ? "transition-open" : "transition-record",
|
|
25817
|
+
epochVersion: parent.epochVersion
|
|
25818
|
+
});
|
|
25819
|
+
const error = new Error("chat epoch transition message is not available yet");
|
|
25820
|
+
error.code = "chat/epoch-pending";
|
|
25821
|
+
throw error;
|
|
25822
|
+
}
|
|
25823
|
+
const advanced = await applyEpochTransitionAction(cloud, uid, identity, current, action, options, confirmed);
|
|
25824
|
+
if (advanced?.removed)
|
|
25825
|
+
return advanced;
|
|
25826
|
+
if (!advanced?.current?.manifest || advanced.current.manifest.epochVersion <= parent.epochVersion) {
|
|
25827
|
+
const error = new Error("chat epoch transition could not be verified");
|
|
25828
|
+
error.code = "chat/epoch-pending";
|
|
25829
|
+
throw error;
|
|
25830
|
+
}
|
|
25831
|
+
current = advanced;
|
|
25832
|
+
}
|
|
25833
|
+
return current;
|
|
25834
|
+
}
|
|
25835
|
+
async function advanceEntryToLatestEpoch(cloud, uid, identity, entry, options = {}) {
|
|
25836
|
+
let current = entry;
|
|
25837
|
+
while (current?.current?.manifest) {
|
|
25838
|
+
const parent = current.current.manifest;
|
|
25839
|
+
const confirmed = await cloud.chat.state.transition(parent.chatId, parent.epochId, current.current.stateCapability);
|
|
25840
|
+
if (!confirmed?.transitionMessageId)
|
|
25841
|
+
return current;
|
|
25842
|
+
const record = await cloud.chat.messages.read(parent.epochId, confirmed.transitionMessageId, {
|
|
25843
|
+
lane: messageLaneForEpoch(current.current)
|
|
25844
|
+
}).catch(() => null);
|
|
25845
|
+
const action = record ? await decryptMsg(record, "", "", "", { chatId: parent.chatId, epochState: current.current }).catch(() => null) : null;
|
|
25846
|
+
if (!isEpochTransitionMsg(action)) {
|
|
25847
|
+
markDiag(options.diag, "chat.epoch.advance.pending", {
|
|
25848
|
+
stage: record ? "transition-open" : "transition-record",
|
|
25849
|
+
epochVersion: parent.epochVersion
|
|
25850
|
+
});
|
|
25591
25851
|
const error = new Error("chat epoch transition message is not available yet");
|
|
25592
25852
|
error.code = "chat/epoch-pending";
|
|
25593
25853
|
throw error;
|
|
@@ -25724,6 +25984,17 @@ var init_inbox2 = __esm(() => {
|
|
|
25724
25984
|
});
|
|
25725
25985
|
|
|
25726
25986
|
// ../../core/chat/list.js
|
|
25987
|
+
function chatOwnerRevision(chat) {
|
|
25988
|
+
return Number.isSafeInteger(chat?.ownEntry?.ownerRevision) ? chat.ownEntry.ownerRevision : 0;
|
|
25989
|
+
}
|
|
25990
|
+
function serverChatCoversOverlay(chat, overlay) {
|
|
25991
|
+
const serverEpoch = Number.isSafeInteger(chat?.epochVersion) ? chat.epochVersion : 0;
|
|
25992
|
+
const overlayEpoch = Number.isSafeInteger(overlay?.epochVersion) ? overlay.epochVersion : 0;
|
|
25993
|
+
if (serverEpoch !== overlayEpoch) {
|
|
25994
|
+
return serverEpoch > overlayEpoch;
|
|
25995
|
+
}
|
|
25996
|
+
return chatOwnerRevision(chat) >= chatOwnerRevision(overlay);
|
|
25997
|
+
}
|
|
25727
25998
|
function inboxRetryKey(doc) {
|
|
25728
25999
|
const id = typeof doc?.id === "string" ? doc.id : "";
|
|
25729
26000
|
const ts = timestampMs(doc?.ts, 0) || 0;
|
|
@@ -25796,13 +26067,17 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
25796
26067
|
nextAfterChat: null,
|
|
25797
26068
|
hasMore: false
|
|
25798
26069
|
};
|
|
25799
|
-
const optimisticChats = new Map;
|
|
25800
|
-
const mergedChats = (chats) => {
|
|
26070
|
+
const optimisticChats = options.optimisticChats instanceof Map ? options.optimisticChats : new Map;
|
|
26071
|
+
const mergedChats = (chats, mergeOptions = {}) => {
|
|
25801
26072
|
const nextById = new Map;
|
|
25802
26073
|
for (const chat of chats || []) {
|
|
25803
26074
|
const optimistic = optimisticChats.get(chat?.id);
|
|
25804
|
-
if (chat?.id)
|
|
26075
|
+
if (chat?.id) {
|
|
25805
26076
|
nextById.set(chat.id, mergeChatActivity(chat, optimistic, userChatPK));
|
|
26077
|
+
if (mergeOptions.settle === true && optimistic && serverChatCoversOverlay(chat, optimistic)) {
|
|
26078
|
+
optimisticChats.delete(chat.id);
|
|
26079
|
+
}
|
|
26080
|
+
}
|
|
25806
26081
|
}
|
|
25807
26082
|
for (const [id, chat] of optimisticChats.entries()) {
|
|
25808
26083
|
if (!nextById.has(id)) {
|
|
@@ -25867,7 +26142,7 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
25867
26142
|
if (!closed && runId === run) {
|
|
25868
26143
|
processedFirst = true;
|
|
25869
26144
|
const chats = page.chats;
|
|
25870
|
-
latestChats = mergedChats(chats);
|
|
26145
|
+
latestChats = mergedChats(chats, { settle: true });
|
|
25871
26146
|
latestMeta = {
|
|
25872
26147
|
nextAfterChat: snapshot.nextAfterChat,
|
|
25873
26148
|
hasMore: snapshot.hasMore
|
|
@@ -26249,6 +26524,7 @@ function createChatList({
|
|
|
26249
26524
|
localByChatRef,
|
|
26250
26525
|
pendingDeleteIdsRef,
|
|
26251
26526
|
deletedChatIdsRef,
|
|
26527
|
+
removedChatVersionsRef,
|
|
26252
26528
|
keepSelectedDeletedChatIdsRef,
|
|
26253
26529
|
readCacheRef,
|
|
26254
26530
|
lastServerChatsRef,
|
|
@@ -26266,6 +26542,7 @@ function createChatList({
|
|
|
26266
26542
|
onTransition,
|
|
26267
26543
|
onRemoved,
|
|
26268
26544
|
wasRemoved,
|
|
26545
|
+
restoreRemovedChat,
|
|
26269
26546
|
resetExternalChatState,
|
|
26270
26547
|
mutationGate,
|
|
26271
26548
|
onMlsKeyPackageConsumed
|
|
@@ -26294,6 +26571,8 @@ function createChatList({
|
|
|
26294
26571
|
const cachedHistoryEndRef = { current: false };
|
|
26295
26572
|
const cachedHistoryHeadRef = { current: [] };
|
|
26296
26573
|
const previewTimerRef = { current: null };
|
|
26574
|
+
const epochReconciliations = new Map;
|
|
26575
|
+
const inboxOverlayChats = new Map;
|
|
26297
26576
|
let listening = false;
|
|
26298
26577
|
let unsubscribeChats = null;
|
|
26299
26578
|
let snapshot = null;
|
|
@@ -26302,7 +26581,7 @@ function createChatList({
|
|
|
26302
26581
|
let blockedUidSet = new Set(blockedUids);
|
|
26303
26582
|
let schedulePreviewExpiry = () => {};
|
|
26304
26583
|
let fillVisibleChatPage = () => {};
|
|
26305
|
-
const isHiddenChatId = (chatId) => !!chatId && (pendingDeleteIdsRef.current.has(chatId) || deletedChatIdsRef.current.has(chatId));
|
|
26584
|
+
const isHiddenChatId = (chatId) => !!chatId && (pendingDeleteIdsRef.current.has(chatId) || deletedChatIdsRef.current.has(chatId) || removedChatVersionsRef.current.has(chatId));
|
|
26306
26585
|
const filterHiddenChats = (nextChats) => (nextChats || []).filter((chatItem) => chatItem?.id && !isHiddenChatId(chatItem.id));
|
|
26307
26586
|
const nextValue = (current, next) => typeof next === "function" ? next(current) : next;
|
|
26308
26587
|
const publish = () => {
|
|
@@ -26517,7 +26796,13 @@ function createChatList({
|
|
|
26517
26796
|
return shownChats;
|
|
26518
26797
|
};
|
|
26519
26798
|
const applyInboxChat = (chat) => {
|
|
26520
|
-
if (!chat?.id
|
|
26799
|
+
if (!chat?.id) {
|
|
26800
|
+
return;
|
|
26801
|
+
}
|
|
26802
|
+
if (removedChatVersionsRef.current.has(chat.id)) {
|
|
26803
|
+
restoreRemovedChat?.(chat);
|
|
26804
|
+
}
|
|
26805
|
+
if (isHiddenChatId(chat.id)) {
|
|
26521
26806
|
return;
|
|
26522
26807
|
}
|
|
26523
26808
|
const updateStartedAt = Date.now();
|
|
@@ -26543,13 +26828,113 @@ function createChatList({
|
|
|
26543
26828
|
const applyInboxDelete = (chatId) => {
|
|
26544
26829
|
if (!chatId)
|
|
26545
26830
|
return;
|
|
26831
|
+
inboxOverlayChats.delete(chatId);
|
|
26546
26832
|
pendingDeleteIdsRef.current.delete(chatId);
|
|
26547
26833
|
commitServerChats(lastServerChatsRef.current.filter((chat) => chat?.id !== chatId), {
|
|
26548
26834
|
warm: false
|
|
26549
26835
|
});
|
|
26550
26836
|
setSelectedChat((current) => current === chatId ? null : current);
|
|
26551
26837
|
};
|
|
26838
|
+
const reconcileChatEpoch = (chatId) => {
|
|
26839
|
+
if (!chatId || !uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret || !notificationPK) {
|
|
26840
|
+
return Promise.resolve({ advanced: false, chat: null, removed: false });
|
|
26841
|
+
}
|
|
26842
|
+
const active = epochReconciliations.get(chatId);
|
|
26843
|
+
if (active)
|
|
26844
|
+
return active;
|
|
26845
|
+
let task;
|
|
26846
|
+
task = (async () => {
|
|
26847
|
+
const releaseMutation = await mutationGate?.acquireExclusive(chatId) || (() => {});
|
|
26848
|
+
try {
|
|
26849
|
+
const initial = getChatEntry(chatId);
|
|
26850
|
+
if (!initial?.ownEntry)
|
|
26851
|
+
return { advanced: false, chat: initial || null, removed: false };
|
|
26852
|
+
const initialVersion = initial.epochVersion;
|
|
26853
|
+
const identity = {
|
|
26854
|
+
uid,
|
|
26855
|
+
chatPK,
|
|
26856
|
+
chatPrivateKey,
|
|
26857
|
+
chatSigningPK,
|
|
26858
|
+
chatSigningSecret,
|
|
26859
|
+
notificationPK,
|
|
26860
|
+
notificationPrivateKey
|
|
26861
|
+
};
|
|
26862
|
+
const advanceOptions = {
|
|
26863
|
+
blockedUids: blockedUidSet,
|
|
26864
|
+
mls,
|
|
26865
|
+
diag,
|
|
26866
|
+
onMlsKeyPackageConsumed,
|
|
26867
|
+
onRemoved: (removedChatId, epochVersion) => {
|
|
26868
|
+
onRemoved?.(removedChatId, epochVersion);
|
|
26869
|
+
applyInboxDelete(removedChatId);
|
|
26870
|
+
},
|
|
26871
|
+
wasRemoved,
|
|
26872
|
+
onTransition: (chat, message) => {
|
|
26873
|
+
onTransition?.(chat, message);
|
|
26874
|
+
applyInboxChat(chat);
|
|
26875
|
+
}
|
|
26876
|
+
};
|
|
26877
|
+
let working = initial;
|
|
26878
|
+
let result;
|
|
26879
|
+
while (true) {
|
|
26880
|
+
try {
|
|
26881
|
+
result = await advanceEntryToLatestEpoch(cloud, uid, identity, working.ownEntry, advanceOptions);
|
|
26882
|
+
break;
|
|
26883
|
+
} catch (error) {
|
|
26884
|
+
const code = String(error?.code || "").trim().toLowerCase();
|
|
26885
|
+
if (code === "permission-denied" || code.endsWith("/permission-denied")) {
|
|
26886
|
+
result = await retireConfirmedDeletedChat(cloud, uid, identity, working.ownEntry, {
|
|
26887
|
+
diag,
|
|
26888
|
+
onPingDelete: applyInboxDelete
|
|
26889
|
+
});
|
|
26890
|
+
break;
|
|
26891
|
+
}
|
|
26892
|
+
if (code !== "chat/epoch-pending")
|
|
26893
|
+
throw error;
|
|
26894
|
+
const canonical = await getChat(cloud, uid, chatId, chatPK, chatPrivateKey);
|
|
26895
|
+
if (!canonical?.ownEntry || canonical.epochVersion <= working.epochVersion)
|
|
26896
|
+
throw error;
|
|
26897
|
+
markDiag(diag, "chat.epoch.reconcile.rebase", {
|
|
26898
|
+
fromEpochVersion: working.epochVersion,
|
|
26899
|
+
toEpochVersion: canonical.epochVersion
|
|
26900
|
+
});
|
|
26901
|
+
applyInboxChat(canonical);
|
|
26902
|
+
working = canonical;
|
|
26903
|
+
}
|
|
26904
|
+
}
|
|
26905
|
+
if (result?.removed) {
|
|
26906
|
+
markDiag(diag, "chat.epoch.reconcile.done", {
|
|
26907
|
+
fromEpochVersion: initialVersion,
|
|
26908
|
+
removed: true
|
|
26909
|
+
});
|
|
26910
|
+
return { advanced: true, chat: null, removed: true };
|
|
26911
|
+
}
|
|
26912
|
+
const latest = getChatEntry(chatId);
|
|
26913
|
+
const advanced = Number(latest?.epochVersion || 0) > Number(initialVersion || 0);
|
|
26914
|
+
if (advanced) {
|
|
26915
|
+
markDiag(diag, "chat.epoch.reconcile.done", {
|
|
26916
|
+
fromEpochVersion: initialVersion,
|
|
26917
|
+
toEpochVersion: latest.epochVersion,
|
|
26918
|
+
removed: false
|
|
26919
|
+
});
|
|
26920
|
+
}
|
|
26921
|
+
return {
|
|
26922
|
+
advanced,
|
|
26923
|
+
chat: latest || null,
|
|
26924
|
+
removed: false
|
|
26925
|
+
};
|
|
26926
|
+
} finally {
|
|
26927
|
+
releaseMutation();
|
|
26928
|
+
}
|
|
26929
|
+
})().finally(() => {
|
|
26930
|
+
if (epochReconciliations.get(chatId) === task)
|
|
26931
|
+
epochReconciliations.delete(chatId);
|
|
26932
|
+
});
|
|
26933
|
+
epochReconciliations.set(chatId, task);
|
|
26934
|
+
return task;
|
|
26935
|
+
};
|
|
26552
26936
|
const resetChatList = (ready = false) => {
|
|
26937
|
+
inboxOverlayChats.clear();
|
|
26553
26938
|
chatSourceReady = ready;
|
|
26554
26939
|
syncChatDataReady();
|
|
26555
26940
|
setIsChatListLive(false);
|
|
@@ -26673,7 +27058,15 @@ function createChatList({
|
|
|
26673
27058
|
};
|
|
26674
27059
|
const unsubscribe = listenToChats(cloud, uid, chatPK, chatPrivateKey, (nextChats, _nextPeers, meta = {}) => {
|
|
26675
27060
|
const updateStartedAt = Date.now();
|
|
26676
|
-
const
|
|
27061
|
+
const incomingChats = Array.isArray(nextChats) ? nextChats : [];
|
|
27062
|
+
if (meta?.source === "inbox") {
|
|
27063
|
+
for (const chat of incomingChats) {
|
|
27064
|
+
if (removedChatVersionsRef.current.has(chat?.id)) {
|
|
27065
|
+
restoreRemovedChat?.(chat);
|
|
27066
|
+
}
|
|
27067
|
+
}
|
|
27068
|
+
}
|
|
27069
|
+
const rawNextChats = filterHiddenChats(incomingChats);
|
|
26677
27070
|
hydrateReadCache(rawNextChats, readCacheRef.current);
|
|
26678
27071
|
const readAppliedChats = applyReadCache(rawNextChats, chatPK, readCacheRef.current);
|
|
26679
27072
|
const trimmedChats = trimExpiredChatPreviews(readAppliedChats, { skipChatId: selectedChatIdRef.current });
|
|
@@ -26778,6 +27171,7 @@ function createChatList({
|
|
|
26778
27171
|
blockedUids: blockedUidSet,
|
|
26779
27172
|
localCache,
|
|
26780
27173
|
diag,
|
|
27174
|
+
optimisticChats: inboxOverlayChats,
|
|
26781
27175
|
getCurrentChat: (chatId) => lastServerChatsRef.current.find((chatItem) => chatItem?.id === chatId) || null,
|
|
26782
27176
|
mutationGate,
|
|
26783
27177
|
onMlsKeyPackageConsumed
|
|
@@ -26956,12 +27350,16 @@ function createChatList({
|
|
|
26956
27350
|
};
|
|
26957
27351
|
const hasChat = (chatId) => !!chatId && (serverChatIdsSet.has(chatId) || chatLoadsRef.current.has(chatId)) && !isHiddenChatId(chatId);
|
|
26958
27352
|
const setSources = (nextSources) => {
|
|
27353
|
+
const accountChanged = cloud !== nextSources.cloud || uid !== nextSources.uid || chatPK !== nextSources.chatPK || chatPrivateKey !== nextSources.chatPrivateKey;
|
|
26959
27354
|
const nextBlocked = sortedUniqueValues(Array.isArray(nextSources.blocked) ? nextSources.blocked : []);
|
|
26960
27355
|
const nextBlockedUidsKey = nextBlocked.join("|");
|
|
26961
27356
|
const nextBlockedReady = nextSources.blockedReady === true;
|
|
26962
27357
|
const blockedFilterChanged = blockedUidsKey !== nextBlockedUidsKey || blockedReady !== nextBlockedReady;
|
|
26963
27358
|
const listenerChanged = cloud !== nextSources.cloud || uid !== nextSources.uid || chatPK !== nextSources.chatPK || chatSigningPK !== nextSources.chatSigningPK || chatSigningSecret !== nextSources.chatSigningSecret || chatPrivateKey !== nextSources.chatPrivateKey || notificationPK !== nextSources.notificationPK || notificationPrivateKey !== nextSources.notificationPrivateKey || mls !== nextSources.mls || chatBanned !== nextSources.chatBanned || localCache !== nextSources.localCache || isActive !== nextSources.isActive;
|
|
26964
27359
|
const selectedChatChanged = selectedChatId !== nextSources.selectedChatId;
|
|
27360
|
+
if (accountChanged) {
|
|
27361
|
+
inboxOverlayChats.clear();
|
|
27362
|
+
}
|
|
26965
27363
|
({
|
|
26966
27364
|
cloud,
|
|
26967
27365
|
uid,
|
|
@@ -26983,6 +27381,7 @@ function createChatList({
|
|
|
26983
27381
|
localByChatRef,
|
|
26984
27382
|
pendingDeleteIdsRef,
|
|
26985
27383
|
deletedChatIdsRef,
|
|
27384
|
+
removedChatVersionsRef,
|
|
26986
27385
|
keepSelectedDeletedChatIdsRef,
|
|
26987
27386
|
readCacheRef,
|
|
26988
27387
|
lastServerChatsRef,
|
|
@@ -27000,6 +27399,7 @@ function createChatList({
|
|
|
27000
27399
|
onTransition,
|
|
27001
27400
|
onRemoved,
|
|
27002
27401
|
wasRemoved,
|
|
27402
|
+
restoreRemovedChat,
|
|
27003
27403
|
resetExternalChatState,
|
|
27004
27404
|
mutationGate,
|
|
27005
27405
|
onMlsKeyPackageConsumed
|
|
@@ -27043,6 +27443,8 @@ function createChatList({
|
|
|
27043
27443
|
previewTimerRef.current = null;
|
|
27044
27444
|
}
|
|
27045
27445
|
flushChatCacheWrite();
|
|
27446
|
+
epochReconciliations.clear();
|
|
27447
|
+
inboxOverlayChats.clear();
|
|
27046
27448
|
subscribers.clear();
|
|
27047
27449
|
};
|
|
27048
27450
|
const subscribe = (subscriber) => {
|
|
@@ -27068,6 +27470,7 @@ function createChatList({
|
|
|
27068
27470
|
getChatPreviewKey: getChatPreviewKey2,
|
|
27069
27471
|
getChatRetention,
|
|
27070
27472
|
getChatEntry,
|
|
27473
|
+
reconcileChatEpoch,
|
|
27071
27474
|
sendOptionsForPeer,
|
|
27072
27475
|
hasChat
|
|
27073
27476
|
};
|
|
@@ -27078,6 +27481,7 @@ var init_listsession = __esm(() => {
|
|
|
27078
27481
|
init_localdata();
|
|
27079
27482
|
init_chats();
|
|
27080
27483
|
init_list();
|
|
27484
|
+
init_inbox2();
|
|
27081
27485
|
init_ttl();
|
|
27082
27486
|
init_ids();
|
|
27083
27487
|
init_time();
|
|
@@ -27282,7 +27686,6 @@ function createChatSession({
|
|
|
27282
27686
|
const closeMessageBatchRef = { current: null };
|
|
27283
27687
|
const localMessageDiagRef = { current: "" };
|
|
27284
27688
|
const epochHistoryRef = { current: new Map };
|
|
27285
|
-
const epochRecoveryRef = { current: new Map };
|
|
27286
27689
|
const settingsUpdateStatesRef = { current: new Map };
|
|
27287
27690
|
const membershipUpdateStatesRef = { current: new Map };
|
|
27288
27691
|
const directChatOpensRef = { current: new Map };
|
|
@@ -27321,6 +27724,7 @@ function createChatSession({
|
|
|
27321
27724
|
kickChatMember,
|
|
27322
27725
|
leaveChat,
|
|
27323
27726
|
updateChatSettings: updateChatSettings2,
|
|
27727
|
+
updateChatAvatar,
|
|
27324
27728
|
dropChat,
|
|
27325
27729
|
dropUnavailableChat,
|
|
27326
27730
|
deleteChat,
|
|
@@ -27779,51 +28183,15 @@ function createChatSession({
|
|
|
27779
28183
|
return true;
|
|
27780
28184
|
};
|
|
27781
28185
|
const handleBatchMessages = (chatId, messages) => {
|
|
27782
|
-
|
|
27783
|
-
const nextEpochId = transition?.certificate?.nextEpochId;
|
|
27784
|
-
const chat = getChatEntry(chatId);
|
|
27785
|
-
if (!nextEpochId || !chat?.ownEntry || chat.epochId === nextEpochId || epochRecoveryRef.current.has(nextEpochId))
|
|
28186
|
+
if (![...messages || []].reverse().some(isEpochTransitionMsg))
|
|
27786
28187
|
return;
|
|
27787
|
-
const
|
|
27788
|
-
|
|
27789
|
-
|
|
27790
|
-
|
|
27791
|
-
|
|
27792
|
-
|
|
27793
|
-
|
|
27794
|
-
try {
|
|
27795
|
-
releaseMutation = await mutationGate.acquireExclusive(chatId);
|
|
27796
|
-
const current = getChatEntry(chatId);
|
|
27797
|
-
if (!current?.ownEntry || current.epochId === nextEpochId)
|
|
27798
|
-
return;
|
|
27799
|
-
const advanced = await processEpochTransitionMessages(cloud, uid, {
|
|
27800
|
-
chatPK,
|
|
27801
|
-
chatPrivateKey,
|
|
27802
|
-
notificationPK
|
|
27803
|
-
}, current.ownEntry, messages, {
|
|
27804
|
-
blockedUids: new Set(blocked),
|
|
27805
|
-
notificationPrivateKey,
|
|
27806
|
-
diag,
|
|
27807
|
-
onRemoved: (removedChatId) => deleteActions.dropRemovedChat(removedChatId),
|
|
27808
|
-
wasRemoved: (removedChatId) => deleteActions.deletedChatIdsRef.current.has(removedChatId),
|
|
27809
|
-
onTransition: stageCommittedMembershipMessage
|
|
27810
|
-
});
|
|
27811
|
-
if (advanced)
|
|
27812
|
-
epochHistoryRef.current.delete(chatId);
|
|
27813
|
-
else if (attempt < 3)
|
|
27814
|
-
return recover(attempt + 1);
|
|
27815
|
-
} catch {
|
|
27816
|
-
if (attempt < 3)
|
|
27817
|
-
return recover(attempt + 1);
|
|
27818
|
-
} finally {
|
|
27819
|
-
releaseMutation();
|
|
27820
|
-
if (epochRecoveryRef.current.get(nextEpochId) === (timer || true)) {
|
|
27821
|
-
epochRecoveryRef.current.delete(nextEpochId);
|
|
27822
|
-
}
|
|
27823
|
-
}
|
|
27824
|
-
}
|
|
27825
|
-
};
|
|
27826
|
-
recover();
|
|
28188
|
+
const startedAt = Date.now();
|
|
28189
|
+
chatListOwner.reconcileChatEpoch(chatId).then((result) => {
|
|
28190
|
+
if (result.advanced)
|
|
28191
|
+
epochHistoryRef.current.delete(chatId);
|
|
28192
|
+
}).catch((error) => {
|
|
28193
|
+
markError(diag, "chat.epoch.reconcile", startedAt, error);
|
|
28194
|
+
});
|
|
27827
28195
|
};
|
|
27828
28196
|
const setChats = (...args) => chatListOwner.setChats(...args);
|
|
27829
28197
|
const setLastChat = (...args) => chatListOwner.setLastChat(...args);
|
|
@@ -27841,11 +28209,6 @@ function createChatSession({
|
|
|
27841
28209
|
liveActions?.close?.();
|
|
27842
28210
|
liveActivityByChat = new Map;
|
|
27843
28211
|
messageBatchOwner.clear();
|
|
27844
|
-
for (const pending of epochRecoveryRef.current.values()) {
|
|
27845
|
-
if (typeof pending === "number")
|
|
27846
|
-
clearTimeout(pending);
|
|
27847
|
-
}
|
|
27848
|
-
epochRecoveryRef.current.clear();
|
|
27849
28212
|
epochHistoryRef.current.clear();
|
|
27850
28213
|
membershipUpdateStatesRef.current.clear();
|
|
27851
28214
|
directChatOpensRef.current.clear();
|
|
@@ -28299,10 +28662,10 @@ function createChatSession({
|
|
|
28299
28662
|
await discardOwnerChatMlsSnapshot(cloud, transitionIdentity(), chat.entryId, chat.epochState);
|
|
28300
28663
|
await cloud.user.chats.unlink(uid, chat.entryId);
|
|
28301
28664
|
if (retirementStarted) {
|
|
28302
|
-
deleteActions.
|
|
28665
|
+
deleteActions.commitRemovedRetirement(chatId, chat.epochVersion + 1);
|
|
28303
28666
|
retirementStarted = false;
|
|
28304
28667
|
} else {
|
|
28305
|
-
deleteActions.
|
|
28668
|
+
deleteActions.dropRemovedChat(chatId, chat.epochVersion + 1);
|
|
28306
28669
|
}
|
|
28307
28670
|
return { left: true, message: proposal.message };
|
|
28308
28671
|
} catch (error) {
|
|
@@ -28364,35 +28727,55 @@ function createChatSession({
|
|
|
28364
28727
|
clearLocalMembershipMessage(chatId, cid);
|
|
28365
28728
|
};
|
|
28366
28729
|
state.promise = (async () => {
|
|
28367
|
-
const releaseMutation = await mutationGate.acquireExclusive(chatId);
|
|
28368
28730
|
try {
|
|
28369
|
-
|
|
28370
|
-
|
|
28371
|
-
|
|
28372
|
-
|
|
28373
|
-
|
|
28374
|
-
|
|
28375
|
-
|
|
28376
|
-
|
|
28377
|
-
|
|
28378
|
-
|
|
28379
|
-
|
|
28380
|
-
|
|
28381
|
-
|
|
28382
|
-
|
|
28383
|
-
|
|
28384
|
-
|
|
28385
|
-
|
|
28386
|
-
|
|
28387
|
-
|
|
28388
|
-
|
|
28389
|
-
|
|
28390
|
-
|
|
28391
|
-
|
|
28731
|
+
while (true) {
|
|
28732
|
+
const releaseMutation = await mutationGate.acquireExclusive(chatId);
|
|
28733
|
+
let attemptedEpochVersion = 0;
|
|
28734
|
+
try {
|
|
28735
|
+
const chat = requireTransitionChat(chatId);
|
|
28736
|
+
attemptedEpochVersion = chat.epochVersion;
|
|
28737
|
+
const members = prepareMembers ? await prepareMembers(chat) : resolveMembers(chat);
|
|
28738
|
+
if (!members) {
|
|
28739
|
+
restoreAuthoritativeChat();
|
|
28740
|
+
return { entry: chat.ownEntry, unchanged: true };
|
|
28741
|
+
}
|
|
28742
|
+
if (!members.length) {
|
|
28743
|
+
restoreAuthoritativeChat();
|
|
28744
|
+
await deleteActions.deleteChat(chat);
|
|
28745
|
+
return { deleted: true };
|
|
28746
|
+
}
|
|
28747
|
+
const result = await transitionChatEpoch(cloud, transitionIdentity(), chat.ownEntry, { members, cid, mls });
|
|
28748
|
+
if (result.entry) {
|
|
28749
|
+
const projected = projectOwnChatEntry(result.entry, chat.entryId, chatPK, chat.ts);
|
|
28750
|
+
chatListOwner.commitServerChats(sortedChats([projected], lastServerChatsRef.current));
|
|
28751
|
+
} else {
|
|
28752
|
+
chatListOwner.commitServerChats(lastServerChatsRef.current.filter((chatItem) => chatItem?.id !== chatId), { warm: false });
|
|
28753
|
+
setSelectedChat((current) => current === chatId ? null : current);
|
|
28754
|
+
}
|
|
28755
|
+
if (optimisticRoster && result.message) {
|
|
28756
|
+
stageCommittedMembershipMessage(chat, result.message);
|
|
28757
|
+
} else if (optimisticRoster) {
|
|
28758
|
+
clearLocalMembershipMessage(chatId, cid);
|
|
28759
|
+
}
|
|
28760
|
+
if (optimisticRoster)
|
|
28761
|
+
setPendingChatMembers(chatId, null);
|
|
28762
|
+
return result;
|
|
28763
|
+
} catch (error) {
|
|
28764
|
+
releaseMutation();
|
|
28765
|
+
if (!attemptedEpochVersion || !isChatEpochConflict(error))
|
|
28766
|
+
throw error;
|
|
28767
|
+
await chatListOwner.reconcileChatEpoch(chatId);
|
|
28768
|
+
const currentEpochVersion = getChatEntry(chatId)?.epochVersion || 0;
|
|
28769
|
+
if (currentEpochVersion <= attemptedEpochVersion)
|
|
28770
|
+
throw error;
|
|
28771
|
+
markDiag(diag, "chat.membership.epoch.advance", {
|
|
28772
|
+
fromEpochVersion: attemptedEpochVersion,
|
|
28773
|
+
toEpochVersion: currentEpochVersion
|
|
28774
|
+
});
|
|
28775
|
+
} finally {
|
|
28776
|
+
releaseMutation();
|
|
28777
|
+
}
|
|
28392
28778
|
}
|
|
28393
|
-
if (optimisticRoster)
|
|
28394
|
-
setPendingChatMembers(chatId, null);
|
|
28395
|
-
return result;
|
|
28396
28779
|
} catch (error) {
|
|
28397
28780
|
restoreAuthoritativeChat();
|
|
28398
28781
|
throw error;
|
|
@@ -28400,7 +28783,6 @@ function createChatSession({
|
|
|
28400
28783
|
if (membershipUpdateStatesRef.current.get(chatId) === state) {
|
|
28401
28784
|
membershipUpdateStatesRef.current.delete(chatId);
|
|
28402
28785
|
}
|
|
28403
|
-
releaseMutation();
|
|
28404
28786
|
}
|
|
28405
28787
|
})();
|
|
28406
28788
|
membershipUpdateStatesRef.current.set(chatId, state);
|
|
@@ -28523,6 +28905,26 @@ function createChatSession({
|
|
|
28523
28905
|
}
|
|
28524
28906
|
return state.promise;
|
|
28525
28907
|
};
|
|
28908
|
+
const updateChatAvatar = async (chatId, data) => {
|
|
28909
|
+
assertChatAvatarBytes(data);
|
|
28910
|
+
const chat = requireTransitionChat(chatId);
|
|
28911
|
+
if (chat.lineage !== "group")
|
|
28912
|
+
throw new Error("group chat required");
|
|
28913
|
+
const epochId = chat?.epochState?.manifest?.epochId;
|
|
28914
|
+
if (!epochId)
|
|
28915
|
+
throw new Error("current chat epoch unavailable");
|
|
28916
|
+
const uploadChatMedia = cloud?.chat?.media?.upload;
|
|
28917
|
+
if (typeof uploadChatMedia !== "function")
|
|
28918
|
+
throw new Error("chat media upload unavailable");
|
|
28919
|
+
const cid = makeCid();
|
|
28920
|
+
const file = typeof media?.uploadChatAvatar === "function" ? await media.uploadChatAvatar({ cid, data, epochId, uploadChatMedia }) : await putChatFile(epochId, cid, data, {
|
|
28921
|
+
uploadChatMedia,
|
|
28922
|
+
cacheControl: "private, max-age=31536000, immutable, no-transform"
|
|
28923
|
+
});
|
|
28924
|
+
const avatarRef = makeChatAvatarRef(file);
|
|
28925
|
+
await updateChatSettings2(chatId, { avatarRef });
|
|
28926
|
+
return avatarRef;
|
|
28927
|
+
};
|
|
28526
28928
|
const adoptCommittedSettingsMessage = ({ chatId, message }) => {
|
|
28527
28929
|
if (!chatId || !message?.cid)
|
|
28528
28930
|
return;
|
|
@@ -28645,7 +29047,13 @@ function createChatSession({
|
|
|
28645
29047
|
const ackDeletedChat = (...args) => deleteActions.ackDeletedChat(...args);
|
|
28646
29048
|
const markChatReadState = (...args) => seenActions.markChatReadState(...args);
|
|
28647
29049
|
const markChatRead = (...args) => seenActions.markChatRead(...args);
|
|
28648
|
-
const enterChatActivity = (
|
|
29050
|
+
const enterChatActivity = (chatId) => {
|
|
29051
|
+
const reconcileStartedAt = Date.now();
|
|
29052
|
+
chatListOwner.reconcileChatEpoch(chatId).catch((error) => {
|
|
29053
|
+
markError(diag, "chat.epoch.reconcile", reconcileStartedAt, error);
|
|
29054
|
+
});
|
|
29055
|
+
return liveActions.enterChatActivity(chatId);
|
|
29056
|
+
};
|
|
28649
29057
|
const leaveChatActivity = (chatId) => {
|
|
28650
29058
|
const flush = seenActions.flushChatRead(chatId);
|
|
28651
29059
|
liveActions.leaveChatActivity(chatId);
|
|
@@ -28892,6 +29300,7 @@ function createChatSession({
|
|
|
28892
29300
|
listActionsRef: deleteListActionsRef,
|
|
28893
29301
|
closeMessageBatchRef,
|
|
28894
29302
|
mutationGate,
|
|
29303
|
+
reconcileEpoch: (chatId) => chatListOwner?.reconcileChatEpoch(chatId),
|
|
28895
29304
|
diag
|
|
28896
29305
|
});
|
|
28897
29306
|
const createActionOwners = () => {
|
|
@@ -28971,6 +29380,7 @@ function createChatSession({
|
|
|
28971
29380
|
adoptLocalMessageMedia,
|
|
28972
29381
|
readMessageFile,
|
|
28973
29382
|
mutationGate,
|
|
29383
|
+
reconcileEpoch: (chatId) => chatListOwner.reconcileChatEpoch(chatId),
|
|
28974
29384
|
diag
|
|
28975
29385
|
});
|
|
28976
29386
|
reactionActions = createChatReaction({
|
|
@@ -29051,6 +29461,7 @@ function createChatSession({
|
|
|
29051
29461
|
localByChatRef,
|
|
29052
29462
|
pendingDeleteIdsRef: deleteActions.pendingDeleteIdsRef,
|
|
29053
29463
|
deletedChatIdsRef: deleteActions.deletedChatIdsRef,
|
|
29464
|
+
removedChatVersionsRef: deleteActions.removedChatVersionsRef,
|
|
29054
29465
|
keepSelectedDeletedChatIdsRef: deleteActions.keepSelectedDeletedChatIdsRef,
|
|
29055
29466
|
readCacheRef,
|
|
29056
29467
|
lastServerChatsRef,
|
|
@@ -29066,8 +29477,9 @@ function createChatSession({
|
|
|
29066
29477
|
reconcileMessageBatches: (...args) => messageBatchOwner.reconcileChats(...args),
|
|
29067
29478
|
warmChats: (...args) => messageBatchOwner.warm(...args),
|
|
29068
29479
|
onTransition: stageCommittedMembershipMessage,
|
|
29069
|
-
onRemoved: (removedChatId) => deleteActions.dropRemovedChat(removedChatId),
|
|
29070
|
-
wasRemoved: (removedChatId) => deleteActions.
|
|
29480
|
+
onRemoved: (removedChatId, epochVersion) => deleteActions.dropRemovedChat(removedChatId, epochVersion),
|
|
29481
|
+
wasRemoved: (removedChatId) => deleteActions.removedChatVersionsRef.current.has(removedChatId),
|
|
29482
|
+
restoreRemovedChat: (chat) => deleteActions.restoreRemovedChat(chat),
|
|
29071
29483
|
resetExternalChatState,
|
|
29072
29484
|
mutationGate,
|
|
29073
29485
|
onMlsKeyPackageConsumed: markMlsKeyPackageConsumed
|
|
@@ -29208,6 +29620,8 @@ function createChatSession({
|
|
|
29208
29620
|
var DEFAULT_READ_WRITE_INTERVAL_MS, DIAG_KEY_LEN = 12, EMPTY_LOCAL_MESSAGES;
|
|
29209
29621
|
var init_session3 = __esm(() => {
|
|
29210
29622
|
init_config();
|
|
29623
|
+
init_files();
|
|
29624
|
+
init_avatar2();
|
|
29211
29625
|
init_ids();
|
|
29212
29626
|
init_direct();
|
|
29213
29627
|
init_create();
|
|
@@ -38087,7 +38501,8 @@ var init_links = __esm(() => {
|
|
|
38087
38501
|
])
|
|
38088
38502
|
]);
|
|
38089
38503
|
storageCorsOrigins = Object.freeze([
|
|
38090
|
-
origins.veyl
|
|
38504
|
+
origins.veyl,
|
|
38505
|
+
origins.veylDevWeb
|
|
38091
38506
|
]);
|
|
38092
38507
|
devStorageCorsOrigins = Object.freeze([
|
|
38093
38508
|
origins.veylDev,
|
|
@@ -38103,12 +38518,19 @@ var init_links = __esm(() => {
|
|
|
38103
38518
|
});
|
|
38104
38519
|
|
|
38105
38520
|
// ../../product/passkeylabel.js
|
|
38521
|
+
function twoWordLabelAt(index) {
|
|
38522
|
+
if (!Number.isSafeInteger(index) || index < 0) {
|
|
38523
|
+
throw new Error("two-word label index must be a non-negative integer");
|
|
38524
|
+
}
|
|
38525
|
+
const normalized = index % TWO_WORD_LABEL_COUNT;
|
|
38526
|
+
const adjective = adjectives[normalized % adjectives.length];
|
|
38527
|
+
const noun = nouns[Math.floor(normalized / adjectives.length)];
|
|
38528
|
+
return `${adjective} ${noun}`;
|
|
38529
|
+
}
|
|
38106
38530
|
function randomPasskeyLabel() {
|
|
38107
|
-
|
|
38108
|
-
const noun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
38109
|
-
return `${adj} ${noun}`;
|
|
38531
|
+
return twoWordLabelAt(Math.floor(Math.random() * TWO_WORD_LABEL_COUNT));
|
|
38110
38532
|
}
|
|
38111
|
-
var adjectives, nouns;
|
|
38533
|
+
var adjectives, nouns, TWO_WORD_LABEL_COUNT;
|
|
38112
38534
|
var init_passkeylabel = __esm(() => {
|
|
38113
38535
|
adjectives = [
|
|
38114
38536
|
"Ancient",
|
|
@@ -38175,7 +38597,6 @@ var init_passkeylabel = __esm(() => {
|
|
|
38175
38597
|
"Grim",
|
|
38176
38598
|
"Holy",
|
|
38177
38599
|
"Infernal",
|
|
38178
|
-
"Ivory",
|
|
38179
38600
|
"Jade",
|
|
38180
38601
|
"Light",
|
|
38181
38602
|
"Midnight",
|
|
@@ -38319,6 +38740,7 @@ var init_passkeylabel = __esm(() => {
|
|
|
38319
38740
|
"Wizard",
|
|
38320
38741
|
"Wyvern"
|
|
38321
38742
|
];
|
|
38743
|
+
TWO_WORD_LABEL_COUNT = adjectives.length * nouns.length;
|
|
38322
38744
|
});
|
|
38323
38745
|
|
|
38324
38746
|
// src/auth.js
|
|
@@ -38684,6 +39106,7 @@ function simplifyAccountChat(chat, profiles = null) {
|
|
|
38684
39106
|
members,
|
|
38685
39107
|
title: chat.settings?.title || null,
|
|
38686
39108
|
avatarRef: chat.settings?.avatarRef || null,
|
|
39109
|
+
retention: chat.settings?.retention || null,
|
|
38687
39110
|
peerChatPK: chat.peerChatPK,
|
|
38688
39111
|
peerUid: chat.peerUid || directPeer?.uid || null,
|
|
38689
39112
|
username: directPeer?.username || null,
|
|
@@ -44191,6 +44614,7 @@ function deriveMessageRouteProjection({
|
|
|
44191
44614
|
routeActive: routeRetained
|
|
44192
44615
|
}));
|
|
44193
44616
|
const { rawMessages, messages, visibleMessages } = routeMessages;
|
|
44617
|
+
const memberStatesByEpoch = activeState.latestServerPage?.memberStatesByEpoch || EMPTY_MEMBER_STATES_BY_EPOCH;
|
|
44194
44618
|
return {
|
|
44195
44619
|
activeExists: activeState.exists,
|
|
44196
44620
|
activeHasOlder: activeState.hasOlder,
|
|
@@ -44199,7 +44623,7 @@ function deriveMessageRouteProjection({
|
|
|
44199
44623
|
activeLatestMessages: activeState.latestMessages,
|
|
44200
44624
|
activeLatestServerPage: activeState.latestServerPage,
|
|
44201
44625
|
activeReady: activeState.ready,
|
|
44202
|
-
memberStatesByEpoch
|
|
44626
|
+
memberStatesByEpoch,
|
|
44203
44627
|
leaveState: {
|
|
44204
44628
|
chatId,
|
|
44205
44629
|
exists: activeState.exists,
|
|
@@ -44210,7 +44634,7 @@ function deriveMessageRouteProjection({
|
|
|
44210
44634
|
selfChatPublicKey,
|
|
44211
44635
|
peerChatPublicKey,
|
|
44212
44636
|
memberChatPKs,
|
|
44213
|
-
memberStatesByEpoch
|
|
44637
|
+
memberStatesByEpoch,
|
|
44214
44638
|
conversationKeys,
|
|
44215
44639
|
routeMemory,
|
|
44216
44640
|
keepKeys: getRouteKeepKeys(refs.visibleMessageKeys.current, refs.heldMessages.current),
|
|
@@ -44316,7 +44740,7 @@ function recordRouteProjectionDiagnostics({
|
|
|
44316
44740
|
markDiag(diag, "chat.messages.route.visible", visibleData);
|
|
44317
44741
|
}
|
|
44318
44742
|
}
|
|
44319
|
-
var PASSIVE_OLDER_OVERFLOW_PAGES = 3, ROUTE_VISIBLE_DETAIL_SCAN_LIMIT = 160, DIAG_KEY_LEN2 = 12;
|
|
44743
|
+
var PASSIVE_OLDER_OVERFLOW_PAGES = 3, ROUTE_VISIBLE_DETAIL_SCAN_LIMIT = 160, DIAG_KEY_LEN2 = 12, EMPTY_MEMBER_STATES_BY_EPOCH;
|
|
44320
44744
|
var init_routeprojection = __esm(() => {
|
|
44321
44745
|
init_messagekeys();
|
|
44322
44746
|
init_state();
|
|
@@ -44324,6 +44748,7 @@ var init_routeprojection = __esm(() => {
|
|
|
44324
44748
|
init_history();
|
|
44325
44749
|
init_payloadlog();
|
|
44326
44750
|
init_window();
|
|
44751
|
+
EMPTY_MEMBER_STATES_BY_EPOCH = new Map;
|
|
44327
44752
|
});
|
|
44328
44753
|
|
|
44329
44754
|
// ../../core/chat/messages/expirycleanup.js
|
|
@@ -46706,6 +47131,9 @@ function positiveLimit2(value, fallback = 30) {
|
|
|
46706
47131
|
const limit = Math.floor(Number(value) || fallback);
|
|
46707
47132
|
return Math.max(1, Math.min(limit, 500));
|
|
46708
47133
|
}
|
|
47134
|
+
function messageIsPermanent(message) {
|
|
47135
|
+
return message?.permanent === true || Object.prototype.hasOwnProperty.call(message || {}, "ttl") && message.ttl == null;
|
|
47136
|
+
}
|
|
46709
47137
|
function waitFor2(owner, predicate, timeoutMs = DEFAULT_TIMEOUT_MS4) {
|
|
46710
47138
|
const current = owner.getSnapshot();
|
|
46711
47139
|
if (predicate(current))
|
|
@@ -46873,7 +47301,7 @@ function createRuntimeChatActions({
|
|
|
46873
47301
|
const profiles2 = await resolveTargetProfiles(peers, session, runtime);
|
|
46874
47302
|
const settings = {
|
|
46875
47303
|
title: cleanText(options.title) || null,
|
|
46876
|
-
avatarRef:
|
|
47304
|
+
avatarRef: null,
|
|
46877
47305
|
retention: cleanText(options.retention) || "seen"
|
|
46878
47306
|
};
|
|
46879
47307
|
const initialText = typeof options.initialMessage === "string" ? options.initialMessage : "";
|
|
@@ -46903,11 +47331,25 @@ function createRuntimeChatActions({
|
|
|
46903
47331
|
return { left: result?.left === true || result?.deleted === true, chatId: chat.id };
|
|
46904
47332
|
}
|
|
46905
47333
|
async function updateSettings(chatId, settings = {}) {
|
|
47334
|
+
if (Object.prototype.hasOwnProperty.call(settings || {}, "avatarRef")) {
|
|
47335
|
+
throw new Error("use chat.updateAvatar for chat pictures");
|
|
47336
|
+
}
|
|
46906
47337
|
const { runtime, chat } = await chatForId(chatId);
|
|
46907
47338
|
await runtime.chat.getSnapshot().updateChatSettings(chat.id, settings);
|
|
46908
47339
|
const next = await chatForId(chat.id);
|
|
46909
47340
|
return summarizeChat(runtime, next.chat);
|
|
46910
47341
|
}
|
|
47342
|
+
async function updateAvatar(chatId, data = null) {
|
|
47343
|
+
const { runtime, chat } = await chatForId(chatId);
|
|
47344
|
+
const snapshot2 = runtime.chat.getSnapshot();
|
|
47345
|
+
if (data == null) {
|
|
47346
|
+
await snapshot2.updateChatSettings(chat.id, { avatarRef: null });
|
|
47347
|
+
} else {
|
|
47348
|
+
await snapshot2.updateChatAvatar(chat.id, data);
|
|
47349
|
+
}
|
|
47350
|
+
const next = await chatForId(chat.id);
|
|
47351
|
+
return summarizeChat(runtime, next.chat);
|
|
47352
|
+
}
|
|
46911
47353
|
async function enterById(chatId, options = {}) {
|
|
46912
47354
|
const session = await getSession();
|
|
46913
47355
|
const { runtime, chat, profile } = await chatForId(chatId);
|
|
@@ -47078,6 +47520,13 @@ function createRuntimeChatActions({
|
|
|
47078
47520
|
}
|
|
47079
47521
|
async function saveInChat(chatId, messageId, saved = true, options = {}) {
|
|
47080
47522
|
const target = await messageForChat(chatId, messageId, options);
|
|
47523
|
+
if (saved === messageIsPermanent(target.message)) {
|
|
47524
|
+
return {
|
|
47525
|
+
chat: summarizeChat(target.runtime, target.chat),
|
|
47526
|
+
saved,
|
|
47527
|
+
updated: 0
|
|
47528
|
+
};
|
|
47529
|
+
}
|
|
47081
47530
|
const result = saved ? await target.runtime.chat.getSnapshot().makeMessagePermanent(target.chat.id, target.message) : await target.runtime.chat.getSnapshot().makeMessageTemporary(target.chat.id, target.message);
|
|
47082
47531
|
if (!result?.updated)
|
|
47083
47532
|
throw new Error("message no longer available");
|
|
@@ -47572,6 +48021,7 @@ function createRuntimeChatActions({
|
|
|
47572
48021
|
addMembers,
|
|
47573
48022
|
kickMember,
|
|
47574
48023
|
leaveMembership,
|
|
48024
|
+
updateAvatar,
|
|
47575
48025
|
updateSettings,
|
|
47576
48026
|
readById,
|
|
47577
48027
|
enterById,
|
|
@@ -48012,7 +48462,7 @@ var package_default;
|
|
|
48012
48462
|
var init_package2 = __esm(() => {
|
|
48013
48463
|
package_default = {
|
|
48014
48464
|
name: "veyl",
|
|
48015
|
-
version: "0.
|
|
48465
|
+
version: "0.60.0",
|
|
48016
48466
|
private: true,
|
|
48017
48467
|
license: "UNLICENSED",
|
|
48018
48468
|
workspaces: {
|
|
@@ -48341,6 +48791,7 @@ class AccountRuntime {
|
|
|
48341
48791
|
addMembers: (chatId, peers) => this.runtimeChat.addMembers(chatId, peers),
|
|
48342
48792
|
kickMember: (chatId, memberChatPK) => this.runtimeChat.kickMember(chatId, memberChatPK),
|
|
48343
48793
|
leaveMembership: (chatId) => this.runtimeChat.leaveMembership(chatId),
|
|
48794
|
+
updateAvatar: (chatId, data) => this.runtimeChat.updateAvatar(chatId, data),
|
|
48344
48795
|
updateSettings: (chatId, payload) => this.runtimeChat.updateSettings(chatId, payload),
|
|
48345
48796
|
readById: (chatId, payload) => this.runtimeChat.readById(chatId, payload),
|
|
48346
48797
|
enterById: (chatId, payload) => this.runtimeChat.enterById(chatId, payload),
|
|
@@ -48806,7 +49257,9 @@ class AccountRuntime {
|
|
|
48806
49257
|
}
|
|
48807
49258
|
const vault = await this.cloud.user.vault.read(profile.uid);
|
|
48808
49259
|
if (!vault) {
|
|
48809
|
-
throw new Error("vault not found")
|
|
49260
|
+
throw Object.assign(new Error("vault not found"), {
|
|
49261
|
+
code: "not-found"
|
|
49262
|
+
});
|
|
48810
49263
|
}
|
|
48811
49264
|
return {
|
|
48812
49265
|
mnemonic: await this.ports.vaultCrypto.decryptWalletMnemonic(vault, vaultSecret),
|
|
@@ -48824,7 +49277,9 @@ class AccountRuntime {
|
|
|
48824
49277
|
}
|
|
48825
49278
|
const vault = await this.cloud.user.vault.read(profile.uid);
|
|
48826
49279
|
if (!vault) {
|
|
48827
|
-
throw new Error("vault not found")
|
|
49280
|
+
throw Object.assign(new Error("vault not found"), {
|
|
49281
|
+
code: "not-found"
|
|
49282
|
+
});
|
|
48828
49283
|
}
|
|
48829
49284
|
const session = await this.bootSession(vault, vaultSecret, profile);
|
|
48830
49285
|
const next = await this.saveProfile({
|
|
@@ -181895,7 +182350,7 @@ function firebaseFunctionTarget(environment, name7) {
|
|
|
181895
182350
|
if (!/^[A-Za-z][A-Za-z0-9]*$/u.test(target)) {
|
|
181896
182351
|
throw new Error("Invalid Firebase function target");
|
|
181897
182352
|
}
|
|
181898
|
-
return
|
|
182353
|
+
return resolved.name === "dev" ? `dev-${target}` : target;
|
|
181899
182354
|
}
|
|
181900
182355
|
var firebaseConfig, firebaseEnvironments, defaultFirebaseEnvironment;
|
|
181901
182356
|
var init_config3 = __esm(() => {
|
|
@@ -182864,14 +183319,14 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
|
|
|
182864
183319
|
const { entryId, expectedEpochId, stateCapability, cleanup = true } = options2 || {};
|
|
182865
183320
|
if (!chatId || !expectedEpochId || !stateCapability)
|
|
182866
183321
|
throw new Error("current chat state required");
|
|
182867
|
-
await callFunction("deleteChat", {
|
|
183322
|
+
const result = await callFunction("deleteChat", {
|
|
182868
183323
|
chatId,
|
|
182869
183324
|
expectedEpochId,
|
|
182870
183325
|
stateCapabilityWitness: stateCapabilityWitness(stateCapability),
|
|
182871
183326
|
...entryId ? { entryId } : {},
|
|
182872
183327
|
cleanup: cleanup !== false
|
|
182873
183328
|
});
|
|
182874
|
-
return true;
|
|
183329
|
+
return { newlyDeleted: result?.newlyDeleted === true };
|
|
182875
183330
|
}
|
|
182876
183331
|
function cleanOpaqueState(value) {
|
|
182877
183332
|
const chat = value?.chat || value || {};
|
|
@@ -183013,10 +183468,10 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
|
|
|
183013
183468
|
]).catch(() => []);
|
|
183014
183469
|
const [epochSnap, settingsSnap, packageSnap, ownerSnap, ownerMlsSnap] = snapshots;
|
|
183015
183470
|
const outboxSnaps = snapshots.slice(5);
|
|
183016
|
-
const
|
|
183471
|
+
const currentEpoch2 = epochSnap?.data?.() || {};
|
|
183017
183472
|
const currentSettings = settingsSnap?.data?.() || {};
|
|
183018
183473
|
const currentPackage = packageSnap?.data?.() || {};
|
|
183019
|
-
const matches = epochSnap?.exists?.() && settingsSnap?.exists?.() && packageSnap?.exists?.() &&
|
|
183474
|
+
const matches = epochSnap?.exists?.() && settingsSnap?.exists?.() && packageSnap?.exists?.() && currentEpoch2.protocol === CHAT_PROTOCOL_VERSION && currentEpoch2.chatId === chatId && currentEpoch2.epochVersion === 1 && currentEpoch2.settingsId === payload.settings.id && currentEpoch2.mlsPackageId === payload.mlsPackage.head.packageId && currentEpoch2.mlsPackageDigest === payload.mlsPackage.digest && currentEpoch2.status === "open" && sameChatSettingsHead(currentSettings.head, settings.data.head) && sameCloudBytes(currentSettings.body, settings.data.body) && currentPackage?.head?.packageId === payload.mlsPackage.head.packageId && currentPackage?.digest === payload.mlsPackage.digest && ownerSnap?.exists?.() && sameCloudBytes(ownerSnap.data()?.body, ownerEntry.data.body) && ownerMlsSnap?.exists?.() && sameCloudBytes(ownerMlsSnap.data()?.body, ownerMlsState.data.body) && membershipOutbox.every((outbox, index) => {
|
|
183020
183475
|
const current = outboxSnaps[index]?.data?.() || {};
|
|
183021
183476
|
return outboxSnaps[index]?.exists?.() && sameCloudBytes(current.body, outbox.data.body) && timestampMs(current.ttl, null) === timestampMs(outbox.data.ttl, null);
|
|
183022
183477
|
});
|
|
@@ -183928,6 +184383,15 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
|
|
|
183928
184383
|
await callFunction("unlinkChatOwner", { entryId });
|
|
183929
184384
|
return true;
|
|
183930
184385
|
}
|
|
184386
|
+
async function unlinkDeletedUserChat(uid, entryId, chatId) {
|
|
184387
|
+
requireUid(uid);
|
|
184388
|
+
if (!entryId)
|
|
184389
|
+
throw new Error("chat entry id required");
|
|
184390
|
+
if (!chatId)
|
|
184391
|
+
throw new Error("chat id required");
|
|
184392
|
+
await callFunction("unlinkChatOwner", { entryId, deletedChatId: chatId });
|
|
184393
|
+
return true;
|
|
184394
|
+
}
|
|
183931
184395
|
function userChatEpochCollection(uid, entryId) {
|
|
183932
184396
|
requireUid(uid);
|
|
183933
184397
|
if (!entryId)
|
|
@@ -184231,6 +184695,7 @@ function createFirebaseCloud({ db, auth: auth2, getAuth: getAuth2, functions: fu
|
|
|
184231
184695
|
mutate: mutateUserChat,
|
|
184232
184696
|
delete: deleteUserChat,
|
|
184233
184697
|
unlink: unlinkUserChat,
|
|
184698
|
+
unlinkDeleted: unlinkDeletedUserChat,
|
|
184234
184699
|
membershipOutbox: {
|
|
184235
184700
|
list: listUserChatMembershipOutbox,
|
|
184236
184701
|
delete: deleteUserChatMembershipOutbox
|
|
@@ -186599,6 +187064,7 @@ class FleetOwner {
|
|
|
186599
187064
|
this.manifest = options2.manifest;
|
|
186600
187065
|
this.seed = options2.seed;
|
|
186601
187066
|
this.homeDir = options2.homeDir || defaultHomeDir();
|
|
187067
|
+
this.profileHomeDir = options2.profileHomeDir || this.homeDir;
|
|
186602
187068
|
this.openClient = options2.openClient || createAccountRuntime;
|
|
186603
187069
|
this.policies = [...options2.policies || []];
|
|
186604
187070
|
this.policyLoader = options2.policyLoader || null;
|
|
@@ -186645,7 +187111,7 @@ class FleetOwner {
|
|
|
186645
187111
|
try {
|
|
186646
187112
|
return {
|
|
186647
187113
|
...await this.baseClientOptions(profile),
|
|
186648
|
-
homeDir: this.
|
|
187114
|
+
homeDir: this.profileHomeDir,
|
|
186649
187115
|
activate: false,
|
|
186650
187116
|
credential: material.credential,
|
|
186651
187117
|
vaultKey: material.vaultSecret,
|
|
@@ -186781,6 +187247,36 @@ class FleetOwner {
|
|
|
186781
187247
|
await this.lock.release();
|
|
186782
187248
|
}
|
|
186783
187249
|
}
|
|
187250
|
+
profileStore(profile) {
|
|
187251
|
+
return new ProfileStore({
|
|
187252
|
+
dir: this.profileHomeDir,
|
|
187253
|
+
profile,
|
|
187254
|
+
activate: false
|
|
187255
|
+
});
|
|
187256
|
+
}
|
|
187257
|
+
async hasLocalProfile(profile) {
|
|
187258
|
+
const name7 = cleanProfileName(profile, "");
|
|
187259
|
+
if (!name7)
|
|
187260
|
+
return false;
|
|
187261
|
+
return Boolean(await this.profileStore(name7).load(name7));
|
|
187262
|
+
}
|
|
187263
|
+
async ensureLocalProfile(record, credential) {
|
|
187264
|
+
const store = this.profileStore(record.profile);
|
|
187265
|
+
if (await store.load(record.profile))
|
|
187266
|
+
return store;
|
|
187267
|
+
await store.save({
|
|
187268
|
+
profile: record.profile,
|
|
187269
|
+
username: record.username,
|
|
187270
|
+
network: record.network,
|
|
187271
|
+
credentialId: credential.credentialId,
|
|
187272
|
+
publicKeyPem: credential.publicKeyPem,
|
|
187273
|
+
privateKeyPem: null,
|
|
187274
|
+
auth: "machine",
|
|
187275
|
+
hasVault: false,
|
|
187276
|
+
createdAt: Date.now()
|
|
187277
|
+
});
|
|
187278
|
+
return store;
|
|
187279
|
+
}
|
|
186784
187280
|
provision(input = {}) {
|
|
186785
187281
|
return this.mutate(async () => {
|
|
186786
187282
|
if (this.fleet) {
|
|
@@ -186815,32 +187311,75 @@ class FleetOwner {
|
|
|
186815
187311
|
});
|
|
186816
187312
|
});
|
|
186817
187313
|
}
|
|
186818
|
-
|
|
186819
|
-
|
|
186820
|
-
|
|
186821
|
-
|
|
186822
|
-
|
|
186823
|
-
|
|
187314
|
+
prepare(profile) {
|
|
187315
|
+
return this.mutate(async () => {
|
|
187316
|
+
if (this.fleet) {
|
|
187317
|
+
throw new Error("stop the fleet owner before preparing an account");
|
|
187318
|
+
}
|
|
187319
|
+
return this.withStoppedOwnerLock(async () => {
|
|
187320
|
+
const record = this.slot(profile);
|
|
187321
|
+
if (!record)
|
|
187322
|
+
throw new Error("fleet profile not found");
|
|
187323
|
+
if (record.state !== "ready") {
|
|
187324
|
+
throw new Error("fleet profile must be ready before preparation");
|
|
187325
|
+
}
|
|
187326
|
+
return this.finishProvision(record, {
|
|
187327
|
+
create: false,
|
|
187328
|
+
recoverMissing: false,
|
|
187329
|
+
saveManifest: false,
|
|
187330
|
+
runHook: false
|
|
187331
|
+
});
|
|
187332
|
+
});
|
|
186824
187333
|
});
|
|
186825
|
-
|
|
186826
|
-
|
|
186827
|
-
|
|
186828
|
-
|
|
186829
|
-
|
|
186830
|
-
|
|
186831
|
-
|
|
186832
|
-
|
|
186833
|
-
|
|
186834
|
-
|
|
186835
|
-
|
|
186836
|
-
|
|
187334
|
+
}
|
|
187335
|
+
retire(profile, options2 = {}) {
|
|
187336
|
+
return this.mutate(async () => {
|
|
187337
|
+
if (options2.confirm !== true) {
|
|
187338
|
+
throw new Error("fleet account retirement requires confirm: true");
|
|
187339
|
+
}
|
|
187340
|
+
if (this.fleet) {
|
|
187341
|
+
throw new Error("stop the fleet owner before retiring an account");
|
|
187342
|
+
}
|
|
187343
|
+
return this.withStoppedOwnerLock(async () => {
|
|
187344
|
+
const record = this.slot(profile);
|
|
187345
|
+
if (!record)
|
|
187346
|
+
throw new Error("fleet profile not found");
|
|
187347
|
+
if (record.state !== "ready") {
|
|
187348
|
+
throw new Error("fleet profile must be ready before retirement");
|
|
187349
|
+
}
|
|
187350
|
+
const clientOptions = await this.derivedClientOptions(record);
|
|
187351
|
+
await this.ensureLocalProfile(record, clientOptions.credential);
|
|
187352
|
+
const client2 = await this.openClient({
|
|
187353
|
+
...clientOptions,
|
|
187354
|
+
profile: record.profile,
|
|
187355
|
+
network: record.network
|
|
187356
|
+
}, record);
|
|
187357
|
+
try {
|
|
187358
|
+
await client2.account.login({
|
|
187359
|
+
credential: clientOptions.credential
|
|
187360
|
+
});
|
|
187361
|
+
await client2.ensureUnlocked();
|
|
187362
|
+
const result = await client2.account.delete({ confirm: true });
|
|
187363
|
+
await this.saveProfiles(this.manifest.profiles.filter((item) => item.profile !== record.profile), this.manifest.nextAccountIndex);
|
|
187364
|
+
return {
|
|
187365
|
+
profile: record.profile,
|
|
187366
|
+
username: record.username,
|
|
187367
|
+
deleted: result?.deleted !== false
|
|
187368
|
+
};
|
|
187369
|
+
} finally {
|
|
187370
|
+
await client2.close();
|
|
187371
|
+
}
|
|
186837
187372
|
});
|
|
186838
|
-
}
|
|
187373
|
+
});
|
|
187374
|
+
}
|
|
187375
|
+
async finishProvision(record, options2) {
|
|
187376
|
+
const material = deriveFleetAccount(this.seed, record.accountIndex);
|
|
187377
|
+
await this.ensureLocalProfile(record, material.credential);
|
|
186839
187378
|
const client2 = await this.openClient({
|
|
186840
187379
|
...await this.baseClientOptions(record),
|
|
186841
187380
|
profile: record.profile,
|
|
186842
187381
|
network: record.network,
|
|
186843
|
-
homeDir: this.
|
|
187382
|
+
homeDir: this.profileHomeDir,
|
|
186844
187383
|
activate: false,
|
|
186845
187384
|
credential: material.credential,
|
|
186846
187385
|
vaultKey: material.vaultSecret,
|
|
@@ -186866,6 +187405,8 @@ class FleetOwner {
|
|
|
186866
187405
|
} catch (error) {
|
|
186867
187406
|
if (error?.code !== "not-found")
|
|
186868
187407
|
throw error;
|
|
187408
|
+
if (options2.recoverMissing === false)
|
|
187409
|
+
throw error;
|
|
186869
187410
|
await client2.account.create({
|
|
186870
187411
|
username: record.username,
|
|
186871
187412
|
network: record.network,
|
|
@@ -186880,23 +187421,38 @@ class FleetOwner {
|
|
|
186880
187421
|
credential: material.credential
|
|
186881
187422
|
});
|
|
186882
187423
|
}
|
|
186883
|
-
|
|
187424
|
+
try {
|
|
187425
|
+
await client2.vault.unlock({
|
|
187426
|
+
key: material.vaultSecret,
|
|
187427
|
+
saveKey: false
|
|
187428
|
+
});
|
|
187429
|
+
} catch (error) {
|
|
187430
|
+
if (error?.code !== "not-found")
|
|
187431
|
+
throw error;
|
|
187432
|
+
if (options2.recoverMissing === false)
|
|
187433
|
+
throw error;
|
|
186884
187434
|
await client2.vault.create({
|
|
186885
187435
|
key: material.vaultSecret,
|
|
186886
187436
|
masterSeed: material.masterSeed,
|
|
186887
187437
|
registry: material.registry,
|
|
186888
187438
|
saveKey: false
|
|
186889
187439
|
});
|
|
186890
|
-
account = await client2.account.me();
|
|
186891
|
-
} else {
|
|
186892
|
-
await client2.ensureUnlocked();
|
|
186893
|
-
account = await client2.account.me();
|
|
186894
187440
|
}
|
|
187441
|
+
account = await client2.account.me();
|
|
186895
187442
|
const ready2 = Object.freeze({
|
|
186896
187443
|
...record,
|
|
186897
187444
|
state: "ready"
|
|
186898
187445
|
});
|
|
186899
|
-
|
|
187446
|
+
if (options2.runHook !== false && typeof this.options.onProvisioned === "function") {
|
|
187447
|
+
await this.options.onProvisioned({
|
|
187448
|
+
account,
|
|
187449
|
+
client: client2,
|
|
187450
|
+
profile: ready2
|
|
187451
|
+
});
|
|
187452
|
+
}
|
|
187453
|
+
if (options2.saveManifest !== false) {
|
|
187454
|
+
await this.saveProfiles(this.manifest.profiles.map((item) => item.profile === ready2.profile ? ready2 : item), this.manifest.nextAccountIndex);
|
|
187455
|
+
}
|
|
186900
187456
|
return {
|
|
186901
187457
|
account,
|
|
186902
187458
|
accountIndex: ready2.accountIndex,
|