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